Merged in jmathison/demo-table (pull request #213)

Add demo dashboard settings table and CRUD API

* Add demo dashboard settings table and CRUD API

- Migration 00000000000123: create demoDashboardSettings table (id, guid, key, value JSONB, isDeleted, timestamps)
- SQLC queries and repository for create, list, listByGuid, get, update, soft delete
- OpenAPI paths and schemas in queryAPI.yaml; regenerate api.gen.go (embedded spec)
- Handlers in api/queryAPI/demodashboardsettings.go; ConfigProvider GetDBQueries for repo access
- Unit tests for demo dashboard settings CRUD

* Fix sqlc-generated demo dashboard setting types

Align demo dashboard settings handlers with sqlc output (DemoDashboardSetting model + pointer slices) so CI codegen/build succeeds.

* Fix CI gosec and gofmt issues

- Suppress gosec G115 for TotalCount len->int32 cast (consistent with other list endpoints)
- gofmt mock GetDBQueries() in test helpers

* gofmt query API demo settings

* Tighten demo dashboard settings OpenAPI constraints

* Regenerate query API embedded spec

* refactor: generic UI settings (table, API, service) and three-layer architecture

- Replace demo-dashboard-settings with generic UI settings:
  - Table uiSettings with namespace (was guid), key, value JSONB; UNIQUE(namespace, key)
  - Migration 123: create_ui_settings (single migration, no rename)
  - OpenAPI: /ui-settings, UISettingsService, UISetting schemas with namespace
  - API types and handlers: CreateUISetting, ListUISettings, GetUISetting, UpdateUISetting, DeleteUISetting

- Add service layer (architecture fix):
  - internal/uisettings: Service with Create, List, ListByNamespace, Get, Update, SoftDelete
  - Controllers call s.svc.UISettings only; remove GetDBQueries from queryAPI ConfigProvider
  - Wire UISettings in Services, main.go, and testutils

- Repository: uisettings.sql + uisettings.sql.go, UISetting model; remove demodashboardsettings
- Controller: uisettings.go (replaces demodashboardsettings.go), ErrNotFound from uisettings package
- Tests: uisettings_test.go with namespace-based list/cre…
* Add UISetting model and generated queries for uiSettings table

- models.go: add UISetting struct (sqlc 1.30.0)
- uisettings.sql.go: generated CRUD for ui_settings feature

Made-with: Cursor

* Use repository.UiSetting to match sqlc-generated type

- service and API use repository.UiSetting (sqlc struct name for uiSettings table)
- uisettings.sql.go: keep CreateUISetting/GetUISetting etc. from query names, return *UiSetting
- Fixes Docker/CI build undefined repository.UISetting

* Fix import formatting for golangci-lint

* Lint fix

* uisettings list test

* tests fixes

* Merge main: resolve api.gen.go conflicts (UI settings + DeleteDocument)

* Doc updates and elimination of index redundancy


Approved-by: Jay Brown
This commit is contained in:
Jacob Mathison
2026-03-04 23:00:50 +00:00
parent 09c61ea9b4
commit 8a4029058b
17 changed files with 1693 additions and 289 deletions
+436 -278
View File
@@ -534,6 +534,41 @@ type CollectorSet struct {
MinimumCleanerVersion *CodeVersion `json:"minimum_cleaner_version,omitempty"`
}
// UISetting A UI setting record
type UISetting struct {
CreatedAt time.Time `json:"createdAt"`
Id openapi_types.UUID `json:"id"`
IsDeleted bool `json:"isDeleted"`
Key string `json:"key"`
Namespace string `json:"namespace"`
UpdatedAt time.Time `json:"updatedAt"`
Value map[string]interface{} `json:"value"`
}
// UISettingCreate Request body for creating a UI setting
type UISettingCreate struct {
// Key Setting key within the namespace
Key string `json:"key"`
// Namespace Scope for the setting (e.g. demo-dashboard:{userId}, feature-flags:global)
Namespace string `json:"namespace"`
// Value JSON value (object) for the setting
Value map[string]interface{} `json:"value"`
}
// UISettingListResponse List of UI settings
type UISettingListResponse struct {
Settings []UISetting `json:"settings"`
TotalCount int32 `json:"totalCount"`
}
// UISettingUpdate Request body for updating a UI setting
type UISettingUpdate struct {
// Value New JSON value for the setting
Value map[string]interface{} `json:"value"`
}
// DeleteResponse Response for hard-delete operations when verbose=true
type DeleteResponse struct {
// DeletedDocuments S3 paths of orphaned source documents (only populated when verbose=true)
@@ -1200,6 +1235,9 @@ type SingleFields struct {
// Version The desired version.
type Version = int32
// UISettingID defines model for UISettingID.
type UISettingID = openapi_types.UUID
// EulaVersionID defines model for EulaVersionID.
type EulaVersionID = openapi_types.UUID
@@ -1345,6 +1383,12 @@ type ListClientFoldersParams struct {
Metrics *bool `form:"metrics,omitempty" json:"metrics,omitempty"`
}
// ListUISettingsParams defines parameters for ListUISettings.
type ListUISettingsParams struct {
// Namespace Filter by namespace (omit to list all).
Namespace *string `form:"namespace,omitempty" json:"namespace,omitempty"`
}
// DeleteDocumentParams defines parameters for DeleteDocument.
type DeleteDocumentParams struct {
// Verbose When true, returns S3 paths of orphaned source documents in the response body
@@ -1438,6 +1482,12 @@ type UploadDocumentBatchMultipartRequestBody UploadDocumentBatchMultipartBody
// TriggerExportJSONRequestBody defines body for TriggerExport for application/json ContentType.
type TriggerExportJSONRequestBody = ExportTrigger
// CreateUISettingJSONRequestBody defines body for CreateUISetting for application/json ContentType.
type CreateUISettingJSONRequestBody = UISettingCreate
// UpdateUISettingJSONRequestBody defines body for UpdateUISetting for application/json ContentType.
type UpdateUISettingJSONRequestBody = UISettingUpdate
// ApplyLabelJSONRequestBody defines body for ApplyLabel for application/json ContentType.
type ApplyLabelJSONRequestBody = LabelApplication
@@ -1542,6 +1592,21 @@ type ServerInterface interface {
// List all clients
// (GET /clients)
ListClients(ctx echo.Context) error
// List UI settings
// (GET /ui-settings)
ListUISettings(ctx echo.Context, params ListUISettingsParams) error
// Create a UI setting
// (POST /ui-settings)
CreateUISetting(ctx echo.Context) error
// Soft-delete a UI setting
// (DELETE /ui-settings/{id})
DeleteUISetting(ctx echo.Context, id UISettingID) error
// Get a UI setting by ID
// (GET /ui-settings/{id})
GetUISetting(ctx echo.Context, id UISettingID) error
// Update a UI setting
// (PATCH /ui-settings/{id})
UpdateUISetting(ctx echo.Context, id UISettingID) error
// Hard-delete a document
// (DELETE /document/{id})
DeleteDocument(ctx echo.Context, id DocumentID, params DeleteDocumentParams) error
@@ -2280,6 +2345,91 @@ func (w *ServerInterfaceWrapper) ListClients(ctx echo.Context) error {
return err
}
// ListUISettings converts echo context to params.
func (w *ServerInterfaceWrapper) ListUISettings(ctx echo.Context) error {
var err error
ctx.Set(JwtAuthScopes, []string{})
// Parameter object where we will unmarshal all parameters from the context
var params ListUISettingsParams
// ------------- Optional query parameter "namespace" -------------
err = runtime.BindQueryParameter("form", true, false, "namespace", ctx.QueryParams(), &params.Namespace)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter namespace: %s", err))
}
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.ListUISettings(ctx, params)
return err
}
// CreateUISetting converts echo context to params.
func (w *ServerInterfaceWrapper) CreateUISetting(ctx echo.Context) error {
var err error
ctx.Set(JwtAuthScopes, []string{})
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.CreateUISetting(ctx)
return err
}
// DeleteUISetting converts echo context to params.
func (w *ServerInterfaceWrapper) DeleteUISetting(ctx echo.Context) error {
var err error
// ------------- Path parameter "id" -------------
var id UISettingID
err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
}
ctx.Set(JwtAuthScopes, []string{})
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.DeleteUISetting(ctx, id)
return err
}
// GetUISetting converts echo context to params.
func (w *ServerInterfaceWrapper) GetUISetting(ctx echo.Context) error {
var err error
// ------------- Path parameter "id" -------------
var id UISettingID
err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
}
ctx.Set(JwtAuthScopes, []string{})
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.GetUISetting(ctx, id)
return err
}
// UpdateUISetting converts echo context to params.
func (w *ServerInterfaceWrapper) UpdateUISetting(ctx echo.Context) error {
var err error
// ------------- Path parameter "id" -------------
var id UISettingID
err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
}
ctx.Set(JwtAuthScopes, []string{})
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.UpdateUISetting(ctx, id)
return err
}
// DeleteDocument converts echo context to params.
func (w *ServerInterfaceWrapper) DeleteDocument(ctx echo.Context) error {
var err error
@@ -2759,6 +2909,11 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.GET(baseURL+"/client/:id/folders", wrapper.ListClientFolders)
router.GET(baseURL+"/client/:id/status", wrapper.GetStatusByClientId)
router.GET(baseURL+"/clients", wrapper.ListClients)
router.GET(baseURL+"/ui-settings", wrapper.ListUISettings)
router.POST(baseURL+"/ui-settings", wrapper.CreateUISetting)
router.DELETE(baseURL+"/ui-settings/:id", wrapper.DeleteUISetting)
router.GET(baseURL+"/ui-settings/:id", wrapper.GetUISetting)
router.PATCH(baseURL+"/ui-settings/:id", wrapper.UpdateUISetting)
router.DELETE(baseURL+"/document/:id", wrapper.DeleteDocument)
router.GET(baseURL+"/document/:id", wrapper.GetDocument)
router.GET(baseURL+"/documents/:documentId/labels", wrapper.GetDocumentLabels)
@@ -2788,284 +2943,287 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"H4sIAAAAAAAC/+y9C3MbOZIw+FcQ3L1oaYakSEryQ19s3KoluVt7fugkeea+aflTgFUgiXGxwAFQkukO",
"//cLJIAqVBXqQYqUZY92J6ItFh6JRGYikcjHn52AzRcsJrEUnaM/OzOCQ8Lhn5dYkrd0TqX6IyQi4HQh",
"KYs7R/AJReobovGE8TlWHxCNkf4D3XTg6/+6p3HI7v9L0jnp6X/fdDrdDvmC54uIdI46w8HANhoOOt2O",
"CGZkjtWM3jYvVJs5/vKWxFM56xztj7qdBZaScAXW//lj0Hv96a+2sf7rPzvdjlwu1EBCchpPO9++fVO9",
"OJ4Tadb6K5bB7Py0vNLrGUFj9REli4jhEJ2f9jvdDlXfFljOOt1OjOdqcGh1S8NOt8PJvxLKSdg5kjwh",
"7qL+k5NJ56jzH3sZ1vf0V7FnYVDQnUSUxLIKoAC+VoPyACDSiRUUpyxI5jVwhOb7ViBxJlewnCUR/hvh",
"grK4Cpyzj2+P0Z1uUw2SadC0U5qQO0edJIGWLtG9KFNUt3P2ZcF4JaoIfN0KotKJFRQfBeFnc0yjMhgf",
"L9/2SBywkIQoEYQjotohHIacCFEBF7RphyjbNMeeAy/zcSIWLBbE8F74G5bkHi/VXwGLJYlB5uDFIqIB",
"iJa9fwq1hj/booRzxt8RIfCU6BnzqDj7oiQGjpAg/I4GBBHVQaGgSv75ZjNt97KGMNUJiycRDeSjreaS",
"CJbwgCAccYLDJSJfqJACMY6EVJI6MBBtaIFvGB/TMCTxo63wPBbJZEIDEHsLwudUKB4WSDL1pyJBJGdU",
"IByoHhta53msqQSge8S1OrSpuHSDpHke3+GIhpfkXwkR8hGXBNMirudFYxYuN7Si90y+YUkcPj6zxUyi",
"iZp6Qyu5Zuwdjpdmb8RjLghkMUoWLEaSMTTH8dLuldjA6rqdSyL5snc8kYSXz6X3yXxMOGITJEjA4lCg",
"MZkwTpDkSxpPEZ5iCiydaoRDdax4TiAay/2RPoHoPJl3jl69OBgMup05jfXf2WlEY0mmhCuEqFMzxomc",
"MU6/kvDxEX8/IzFKHBA2QlHfLIpgjONwTmOlHByDhLRze1R7C9WEcaMmxHgckb2QCvVfI2IFuqdyhkQS",
"BEQIOGcSgXAcIqXrC4nni063s+BsQbik+qDXPctTapCsICdKEyKx2q4/OmZS+AX+8cm9PGRfCzpGtxOw",
"aUwluxXJ+J8kkErbK817otsg0wbRkMSSTijhsHg5s4tFVl/JXV3wcDwK9sODHjmcvOi9fPV60MPjIOyR",
"yXC0f3D4Qv2SV4dGhy9y15W+73LSNYpUCVxQ6xSbKMBgZ3AglS63YHEOsH+yWdwPGflv81M/YPNOd2VF",
"rdsxu1sG5e8zImckhyJoS0K9exYUrSuacceMRQTHauCMRsrKsv1kV2qoxl3gaDA67A0HveGL6+HB0ejw",
"aH//H+4CQyxJT82RX+ThwKe3Z5rtH+mCuymOzOyf0p4MaEUtImWpE06w9LJSdtwBRQWqIYg0FJN7vYfA",
"RjBbFymduwtMxJniNCHoNJ7D1bzISxU08rGk16OdRJAQYYEsuatp1Uy7GyEa9/aNe1+Pe/8Y9F73b/+v",
"v97c9D799b+d3+CHm5u++enTn6PuNy/9TygX8lZfQHwL/EWgKb0jMeALEIuDgCWxNAguUMv/sFmcB3xo",
"DoX07zZcGeEmoCZ4TqNlS6hOGdkAUIpOPPz5lgqp+AfI6DNZgqKsyQnRGF0oDVr2KUM7pD/td5FIFoTf",
"YkXQXaAP+2+chFQy3jUmh1v1bbePLtW0aJ4Iqe8aatAlS7gzMonvKGdAvX2UKoDQb6LIU9CIxDJaoh4K",
"ZiT4rL/daiBJqEa0Ujd39v/RyeBT/KkBVAxKJZkDLopIbUbjHH85170P9SaYv4ZpW8w5XpbkhWUKh2Jd",
"QrH700J6tDiQsVKfXBmiENFFNA6iJFS/FM+z81OQJWIZB+aALomRVsckAKhtBr84YiSm/0qI5/B8nEMy",
"0FDdYll3hoBqlZ6X91gg00/Rl/pdLIUk87rTZf/o4HDN06XbXkxv5wDXSlNYf4AHzv4iKoy+Bxgye93m",
"SK8T2gUKyoR3Gzn9IMlcmNmR0C2EcfPMYIqQt4rD6rCcEp/RLyZJFC1dSszEpr0EaIO6ps4+Op+gCY4E",
"6RqNXNt3sg1COwGOoa9iwgBLsovGiYQ7al7ez7BAcWGaPceisttH73Cc4EiLDcYRV3c3NMdLNCbICr9+",
"G5LIS3SPZIPTQM6wRPeEkzx23IMghV8BB3BgJXcE0YecVrOIPV7oBAmmjmBMIxL20QmbLzAnWtMqNga5",
"rk7HkEglxjKdC/onnAhAf5xEkcIGmS/ksqtQqPsD4CmsO7A9Bsd5rH6IoyVacCLU0HSCcqSj2E5h8bFP",
"uvzh1u2YU6LypgTLS4+SjH/efLg8Obs9+f34/W9ntxfHV1d//3B5WhaPTeAVTlfP4ZQp5tmBluPB2qP2",
"lESk1VGb3n1D1UNxor7t6lvuhLM5IjiYGf5EOxY/6rhNiXV3vdO2+lJqLkQAk5Fpj3PWmhlv1cLLEJ9a",
"HFWgpwoNLeSlXSoMucJhpGli1QnSnWuewk+pKTV6ybDNtb5ybx/3Tp+S/dZu9XaGOs3r5dHwcFv3+hxJ",
"NwgNiWnkFYoKbGl0S/cd3CXXvExwtHVz/rgWs/X0848t9XINlqMkZBrG01XZ7R3aUd39JHN4PXh9NDw8",
"2h9sWlk/y5lT8vY3AO6RdfeThHOlRFg93YhdF7DNqe5le4vSBym5q5HKW9HkPTaWFpCsq9hX2FW0xupV",
"TfOam1Xa7ii5J99JZzNkktPdLDt5dLiTD+/fnF++O1tDb+t2kkXYgrXZBKkdRrp1negfHA0HmxH97ZRI",
"y2y1J8FbKjzLu8BTGsNFLjJWN4Vp8z4imcQR0jhXon6hGyvJOycSh1jiksyfYXE7Z5xUH9HqqxoK7i8E",
"4TtMI/tM0qga4SmpJhb1FcXwIJd7cOvWPLINB4PcI9uw/Mimp70V9Cupe//TiFsQDnC4ACgJWQtB0/yw",
"ER7ahP2JCxDMsQxm6oye0EgWMHHwsg6S0fDg5cGr/ReqVd2rYxfudjWWWw0IPBo4W9Nx5Ejd02NJeclJ",
"EUBXrV1TA2exZojG3cRaPvmo2bv5PQTkgMKzlk1ScjpOJBFoB44k8x4CIiP/NCLKd6vaQ0xLp+KrgT0u",
"C2dXjOUMb/vFwEBUfDLwgnTKSO9qTsEta0sPBqkuG7V9Oeihy7OLt8cnZ1cIR5E2SWVq7U7MpFKZqKR3",
"ZLePrpn6C2H4bG3WBHpa+tYdF1EiAAsxuUcsJtCVkzm7I2lvBn6nUp/68ESoiNj/KvGQBwn7/GAaMY4m",
"FBxzJJakjz4oMMDraEJJFCKlVXMSgTUpIviOmNGTOJjheFowmn33d4wy/6pPb9RSVK8yjVzReBqpRd0r",
"AtGGM71ydYyhneFwpP8GXM0VMbAY2pe5FWNOJDklnN6R8BjohMWXWJITQJVSGSIakwIWRgOfopwb6yTC",
"dH69XJCT0IfC+s5vCLkKZiRMojVmdjobP9XVAfiNs2RBuL9/CxjesvHqnd4Tec/459U7XnAWJtrPceWO",
"U47na3W8u4YWq/a8JHQ+fkfkjIXtOhuaPCUiKO9jXY93+MsbQhQtn8dBzj0pZIlW0lJ14XX6f/3Xr70a",
"g9HF8uNfBLL1+H01etPQiZyp01sq3HpWfOhd8JhG0QqcljVvPUOA+R1hiWw5g21+rt0By7pvoIRDutC2",
"o6pOV3BRWKdH+8Uu5MEFZ0HbSdLmK8/wjq02xTvWfo6QTHASVW9BSPG05Qp10/Yzi5nhuw0yXShm1UsR",
"M8OIm+FC8mVGx1RemAtaI35M+2sqS8eVH0MTHNCIyuVx+M9ESKUIXRM+b9V1OiebR+50TiqRO52TjSJ3",
"CrZD/mFSPaM+e48jcL9WeutbckeiD5MTzNsdOGaEX7HYOKb0yC0ZJ23dmndMj99x0ISeDW8KjHlJlBas",
"X0qb5r+k4vOHyTvGJVakfJWMgwgLsQpersgd4VQu1+nTBN41x7GYEN7YrqzE+Oen22A8WsN4dMOMFxEh",
"avkuYmOjSF6SCExjYkYXrRhOd1Wq5Mpd47DtQQstW3NSLPFi8xumRq1EoPq40S1jiYwo4WdfgihRTNkS",
"UeVurbFmur6hXMhTFkW4moHSpl/UdUeI92N+ipfiesaJmLEobIn1flsspBOtOsMKc7yzfbc08BvweomD",
"5aaRsw2ya62TLLCkJJbHU/IOfyn1qO9A41YdIhyQD5MrHSTYkg2KnVozwYKzu6sFCSiO5LLtZPk+K811",
"jb+wmM1XmSrr0nomjuMpMUzaah6OJTkTAY6wZHyFaZxelcIj1+od/mJu0RfaiPFwEs6N7zdnbYT9OKHz",
"8QmLTSzzGxxIHaHYbvR2459NJiRQevBpATv67a6JeWCMzR+FMOxG5Y4ekbO798ZU30xsaY8FLWkbde2v",
"ady6fWs5mLY2L4vrbtcdiZO2Qi5t3ZpBTaR161UJyRbq2D3FNFoaVt0gGdnhV1VySv3aI8D0bKPkZG23",
"qHzYSTavfRRG3rz6YScwcuBDfPYlIEKczDCfmufAB4sFO8dl4QDb6AJas4PkdDrVtoVt0IIZ/gQv0tGP",
"53KDEyTB5o+CpNpakQQbPSKSmMoPk3cEi4S3ubf7ntfap5ih+ZfCzmD4+vDVIJz0yOTFYe/li5cveq9C",
"8rr3+uBg/xC/3t/ff4ldX5lWGUsMQFcV3kMKqMyxDOcAdOJzF5wpxlMjdiGTEHhVKmDA+V79iOOARHnf",
"mgIMH2FUx88SR9GHSefojxb5cnTfq2Q+x3zZ+db9s+gWAHDcTmhEYjyvi5VLm+iIBN0R5RboPN6GLNjv",
"L8JJp6v+OXwJ/654vD0YvH6x2uutcalperP91C25YksNtuuNiscskYUtdPxQzUIzDOW3xe/0ZJHmjqmf",
"yLEJFSw9+UJT/c9WPize7a33YjEOK7faK7PR2cdAZBwvqMggz/yODjfh7VPwrrGYyIP7qUpmaBy0iFrg",
"RCY8JqF2oS1uuEA4CMgCvE4YzxO2Z6eMq3GrhFWuE2JjByNx0k63CfcnK0KSGU8MvRCPz+Ke3rG94+Pj",
"PZuMag9a760sNDntcTIhnMRBUV8ejpq8DJ3kXymYzvIa9tbSd9nlQn9oZOdNbKGJ8G3uk6UIc0S+1/Pz",
"79aVO0eJaZ88DbZx9YyTSDs65t0b27mY+6HJO5av5TduzhhLgKLOxTFt1OKUGW3G05BqX6db9aUdkDGL",
"exenb+BMEDrJDaIxwjyY0TvS2jG0PYiM0ymNcZSe02XoPpgm6UGVOrzDPpIQ/eP8wgdiJ11y/ytd5Lez",
"7cls9mf1TU47IsHQBOf8+g42tL8LzqacCHG7IDwgvoPvIiUvZBsj07jgZ1sP07Apmc6aB4E+BmtQWzy5",
"MwRTh6VzhDkYbOvk1mI+E5c+8nWOgeLi/NTkkSKVnOvZ8pzk8502Wmif4Phqqb2GChm6NC+ZxJJKV4gi",
"dg9pBsAECNHAouNzKDcjV+RAUfeI7GhysqDoLG35U2u1s8fKieb2YNgr7iXsHIxRja+GBJzEphGk+aCk",
"4+Pj8v2rWcrYOX9l4bJ2Xn1BXB93ZUxUo0Bp+i10z4DFEtNYJ6awTsTg3gujiDLA5kP13cI0yGdq6Ld1",
"gT9lgV5Ai5uVYdKTdtcGZ0UbFzAWKTmIqjfnvfeodCillOng+JiToj24ZfienrLKYACvUBNKtPe2azrQ",
"oPQdq8H5+9ur//3+pNPtvP9wDf+EqCP7x/n737zGAhcAP48c23Wb+V29WeOuTIftTqzc4kvhpfrn6n2q",
"iogoSEdQCU2mBRNR5eEbHEOwezuIrcRfT16Wl8NC17vZkxKYCIUVFLCQ2Fy8RU55cZAzwY1G+/svR4P9",
"F68OD16+fNEimOiERRGxT17FQAbzCc1ZSKI+es8kQT1kBryV5Iu8tTmCZ1igMSGxCTAIkaBxQJBqo2Q7",
"N6nNqEAhWXAS+DdEn5B20CYMW+yte+OKsCRCrjGbxUCgDm7C247g7rhfWmkdqGr4EsTdIsK8bGO3EeIR",
"qsUcS+QikSYMQRF4fz3RZqe7It4IvyVcFXMhSxilnVYjsg0S0Ia2tIT9lfJkzDAPezqcHqlVYZMeUt22",
"7wgfM0H+S93WS8s2Ifin1Wr/1T5aYDmDc4TxxQzHcJGC9KvZFWCHQWoVtkgiiKkqzbzr6gwFGGyScU+c",
"9flpMSuDbb2GsV3sX2D1sTjJmySKkFkn2hH7R3t74yT4TOTeZ7LctQAUF6/uGcVgsNFBk5HKWWwKkI/5",
"CtpSU7BgaR99Y2bqWNMhWNAZNnT4faerRTeDuAIrgLOzmNNg5gv2t1+yXPuhfifR8cU2KARH1fHEax0z",
"isKu6Ffy61L6Hk3e0IggQb8SpZ2PlxCzCSmSlESIyBQHy4xDd2vP/4PR64PXL16OXh822RbWMA45HFsy",
"+5TteCwKCfeJggsMIYq6ATo/7ep8UDSzRCglwWTbwqZdvZTwWhJnWFyTL/KSBIzX5BPDJRVlx4Qgpr/s",
"Ig6D2Fxh6RuHg5HyXX6GxayJSn5XbVpxlFvBQakBY+JLo3IcRUh/Q5BrmYRIshKsre58b9UwBnklOVa+",
"9FmzjV8ypzQFonnB2R0NFR8mHCKWtfV4h87nicTjCA6ZZhKTud2tWwsoPmfpfqaHsVfWuIoY7GGRlFL0",
"10mhxjIflQ/T4/3Jq97L/YNXvdd4POwNyeHheH8yfEXCl2uclSk88RvNSOWUUxYiJQQdljPZdK3MtCIR",
"7UR0OpM6r/Mcyy4iXyAA2T753dIQYst1+KovuPxZGlZLw8cUdM9S7d9Hqq0vySofVHPiTJjX1YzX+75k",
"LFuincoV+xaWqyXgycCX/qVYHSqGuIJatTfONboYEWS0MqW6qEDmnUF1UV1vzfQmScKFrZSlaMGC0Fmy",
"RKdaSHM5IPvxW/mmV5FC7ThTX2HmVLmdwL/HyXSqyHJH4JhK+pWEu/kDCLyUjOHRJLANGdGaWJoCIssL",
"1fCmn19+EdZ3OJjRmPQ4wSEUQtAQQ/gZndh8asa1YsrxfI4lDdAMx2FE42ke8I+C8LR0SQ6wFwc+z6Cq",
"nTckgfSvY4UseMfBSSFtdrZfU3pH0JygKWM5P6E1DCcFErZQegk4ifDxlBMy994BoXQXtt+NlC8bSlSD",
"8LjubT8b4x4LM045ZdyL3mDYGx5eDwdH+4Ojwfop4zREbzibn3uyHp5fpGnjIFfJ/YwGMyd9HHTOl8N7",
"PeoPX7zqD/vDQSHJ3sFhdZmLK53c6rw5O2CWG/yRallkZdsqdt3ayUyfHDb6g3WSkTlz+jBSKBO3hoLq",
"TJBGX9fMIVUbtJMmlZ1RIRlfojtK7vNFGK4Jn4MFxEQPoTsPCrxhop51HhfYqbjnW3IrTWoq0EFuwiwh",
"JZaQ+xJMPimwO6YYhi1T0YU6ZinZ5hGmGhUyLTaVn/Ndn4pc5K6iSE95mu5mQqkgDRqlYNt0cnnZKPxS",
"cV7/lloeo5XqmZfaze6X/76p6wCd3zV1Xba7tfnrDrfhF5OjrJWSxuUorHURDC3Q4Bkmzhb+A+gLP+Vx",
"+GQOH59sbym8q2gTEuNRHAfkIk3dWSm0dQ1kJw+0uh+kAyBOFoyXqfQ7irWrtaXa4Uak2rk9hOpFmz6r",
"Ni/FDBQXeEpaQAGnlgvF/ip70CxEHXkJ+5KDLoewZmK91KTmZ/YSSWqzaXYnmRM3RXCh8FhegjVpD6ap",
"E8GxyPFRU38v/0Ea+9TC034IB4qK9KsfY21K8KTy9WCmtQ6VgaBU4FWzseZFll15N83S6mC0mTIaYw3U",
"6qiQNBAt5ZeWoRXOc8Xkvvczhmb4jnguv8PRhpg6A/kic3Iuy+z0WyvoXrnATSKGZRvPaCeFC5PHayEK",
"MqmWwNnfpAD86GcGf3LkysJZ21EtHQC7OVIrobRi45tZAnjSf1k1yguNW/NBfWGNtD6Qbpy+CbhKWJur",
"UAvN1jExmdcoOnGIaXdTOm5jTM4GbGTNC2gwnDUHDj11Q5pJk1xjWflFpLmUjYHfLUOyY/xKoYAVgkDN",
"mMU93TKzyZTNKz3jibqamaW+9N0DbUD/CzWRQwu7UANJ+MsIuCai3I5YMq8UNxfJOKJB9Q1Xf0+TTudS",
"vxekQ7FETFqr2uNaBl3hEZXG6B3mn0N2H5unnxzG/gMVjY838U38H/+Broh+eR2qv39dokToVwYqkMnp",
"0e/3i85oB68OX3otp2lOGa8/tBFeVKQX0jEJ8JygtB/aYQv9YuQXYIPh9UBJr00KMLqVW7P0W45/T+Y4",
"zh6ZdKt1bMTNIuWuyhDxtw3bIHz38oyc7RItIVdxkHa+r+YgIwMLT0kV15gf7wD9W9NuFWFWQr8RbN92",
"Nh+WWi5VQnTikVs+YhqtZ9DKT78dm9YMi+N19TmSE9wrF5PLpi5h2rP2Km6p3JzsWLAIss/zXr90dfTX",
"sIkjqu8VGmyXRxPO6Yy/eu6057niiVBrAS5Waaf8ClZ6eoLRHqZjdH+sw9uEmPqo4cTEdGa17DzVnUYb",
"MaEbKFbZbltpd7ubvRFlh82J+Bm1HSrMoVAnT6mAQriOADXRxyvcjH8YtcrW3r9ROtVNR/1jNBgd9AbD",
"m87uY+tcLnO7LOZuXMNJUxUBXqr65FSwj8n9j3u5yZde+km4f9vss2q9qtbMBL5tY2LKn7ZgoNUgKbDT",
"6rcXA+9KTh9mFrGlEoG6pPyzo8WKjhbFrcneRDdjkk+HrvfmcSBo/Q7lxo6u8ADlTLWyc0fhAbDsKDAe",
"c3JHgfhzagbETlDhcXh61que9arvb65qrRitXg4zRxaVcZ0PZ4MqC9aqtL9dLQLtmGqZuw/WJ3zB7mdf",
"FozLyirvbvA/gabIJMvNCkRuJuRW5zK4jVhQ4WV0PSPIfkVwtbVPZQawr3QBMV/onkaRUoYgdVs+DgBC",
"zPHe8Z7us6eu5oPRcH9PCZx+IO4Kr1ODg1c5vEL//l/V/8wIfwx6rz/9+erbXv+vNzdqBC/3tkuzojej",
"Is2KG+VYk3JFj1EVzGhQVRHKSHoH4eGw93K4H/RevyKkd/DigLzafzUcTob7a4jn3Hr8ke9MCArhJBow",
"KC4q3Gw5blpdGt/a3F9Zkt1PlRNfa0qtp2pDzsabUoPR94RAkgjy6Eqvr8Ab+KA9AwK2IGiMBQmROsch",
"Mtkk6rjDUaJX10pngXAxPXReZ4FH06L3MY2nRCh41gEz7YwWNt7JEw5G4vC2OpUPVCpWn0FRtQP2W0jR",
"/ZGfZbisnA7qzvINTlgWjj5xWYrgg0Os+nST1vxmrvvFQHXtXCV0bVkch25hWY9JOq1R64smg56Qyrkw",
"h7407OAo0jFQ4FgTsFhQIfUjEU8CmfB8qpDaCtv5YrltcozVGC7hDdm62GQGEi3a7SqaHrjTLbfV7RsN",
"lPnsJ+2DXvVuZdtQ1/PKbVufkcRt2c1ttYu8T22IsuqN8I2X+qyClI+G2xzlbZ+o1rbJD3rDF9fDg6PR",
"y6Ph4TZs8nnSLlnjnyJ5+25qJcLZuD+7F7Hrc1qL+5IxaewMe3AK7ra25hQqJgwbnfhgrTlmz+5Tbdi+",
"YJVuIQKqnz+LG5m+Txv/V8hppSP0ynr9D85tnvv+M6u1NS88DXZZkSOM8lyhCOtXmIBFydz79qKj81fQ",
"0k/SPpChRF0ZWuXSKqf7UzsClwW/qq2/KTKfJJF1u9Bqf+52sbqj5SqGUWeFXQdfKegN23LiYrjmaqi1",
"ikm6ZelMuXtiRIS4lTOs5jflju2fQcQECW8VvfE7sN+yBYndvyMykbflZpxOZ77fTdIHYAH9L98ltCp9",
"j/69FCrF+BTH9KtaYSHZdcm0cr5alYIfRmynPkUl0Z3mFNuw5PZKYCezz9bl7pxIToMV6v5o6N7pbucx",
"FFYsl8Jxct6bGbJsQRqXffTBkzjSNIbEkTcxFU5+ERP5wPXNto/eJ1GEmJwRfk8F6d8oTss/A8CjFq/I",
"L+lJo7QSssfNyG58lFj4k1JqkHROSrgwgylCX5b2dvvokrEU8BkWuuVNZ++mk7eqpUVZxN5oMDooPqQX",
"7P57/b+0tMYD2N1MFqx0LALYTd4KJbOFXuxjiKlV9TqyNdlQTbsfzPMZWjyQiNeRGGsS7UdBNI3CLvKM",
"hLdOs5XkWkui9ZW3NOgNNbdMo8ZR+ugMBzO7i+ZwF4hKAckuFfxdMM5ZiujfxEoG9CJyR6IUFjCr2SZI",
"EGAjJYP66MQk8w9wrGj4Jk6bSQZRVrG2wTn0jGaUcMyD2XJPckIyI52WtO0MyJozWhVXyyl2BnHVu/Mu",
"O7ZaHTw4j11XeiAoAFaWIeMlpH6DkzFNlHVR2OBaj4vmei0VeRU1RGi81Ens8snETrGYjRnm4e0lweHS",
"KMrnYH5W6INAwA8nl7cXttAJBHfqTXDhdxXsDL/ViQc/gm+PGz2kj3SvBNyS8AEHiNM1S9WUwRw2BtKv",
"HEee4q8EbDclqUa6NnpV2cUWfncKVvmJfCdNsGmhMbn1qfxFIP3IQmK5+0zzGvdPn6rWIKXLivykjo7F",
"dX5SeHekAh490tUUs2T4Dv33mWq26YO/p2ELN6YA+BD1u0kvWZOl0mTBrHuTr7BknIeVGSOP7YsbvPpX",
"FBNqI3zVH3LZ30CqFh96tEO/XK5WgMheon8RiLOICNBc5IxQjhaEz6nwO1VC22pVyQwlBJ3GWaSP1scV",
"pfkmaKWjpItkEVnVAKVhrkUdi7z7r3rWrEapfnXIyg1XeTsI84lJ4fbMCj4/Sp72oIQDDpTkVLDcUXLv",
"yDi1f9ovQbSoetDtfCYVqV9h0Z/Jci+j4nyYRxJSCblT3UlGeW77A/e+Dnqvb3uf/GznF3oF3yeApFyd",
"yTd/S7ulu1uVROw0QlOOY3WDHC+1VaS4M9nz7JEC2zFJH0XqaqKaqj3RXytqH9cg76gJkRkjHDYygtry",
"tOaCiwofb8DBdbxYRLTK1co5ovBioWgT3TP+eRKxe60dwBc3lXPhtVjnfG6+ysPoVmiV9I7NXOMjq1EV",
"aALWAefvDo4WMxwnc8JpAOyWxOr+EzBOkOLNvJtsXs/plqqhu/t93PuH2eZPLXwtLQYy/FXuX1XucP27",
"LiOit8pJwN28ZT4j8bW1DWsToR4Vwh11n62ZjNuQUc5knC5VMfSWyGmTb32aBreVIu5wBW74e569nxZb",
"lB8Ey4zSdSjYyzRUyLobxoyUy6pmZXja1lzMpV9vdtm7Kvg3FFJSwdcePKYZjzBzzUy1Y7cGyvBoiDiJ",
"dAGqGV2Ub5cYcyLJKeH0joTHcxKHapD36gZU0pDsV3vrCnUvdVweY1NEsfbNt+kamgPmLHXm9j1SsRhW",
"6QQwgFdgQewc6NCForBpI2dcUK7V6anzidUCI7N2XnAg4nd/LXASyS5JTO5xdB77kksmkqnrmWqAaByq",
"Yxy0Jk+ipGwota6GsdSSRPGy5X3Lqym/+d6puJHaZRszOAcGrRUJpeHndFTTts24ExqR981lQsq6qJ3k",
"VlFWfxFO2s6mRrqmvjdt/AXZEG3jxW7iuiBTkVM8OYd/7wvAkvBm7EOzNmBDwyvp97S9Z72IKMmtfbN1",
"aUvI82+Gz3B2Uqj7OyoJ/U9/jr5VlRi/+42zZKEW8CaJooqYV1vPxZTu4GiqOrVaZDrDgnrN1s546P3F",
"eZt9sEN697sw5PX5+5ZDfpAzvb0t8ADvryk22uJBz+DDw4fceCvgATp68VAYsj0eVPPVqTJDRUaY7//3",
"moTpcwFvVQC2ovbr2jkBBQkSTuXySuka1kEJcnIdJz7LoPpViRojaHRg9/Ecf2VpRRB0fnrRR+cKRVrr",
"uXxz8url6BD9z9+v0Vhd/xagWwSmcLkFAdYUsXutUiRyxjj9CtOcsJCUfvzIo85RZyblQhzt7YUs+Lrs",
"qwb9RPQIFrI37GOAy6xHKeZ7TLUY7dmBoKJ8wBYmCkGnVuscpwYTY+6yOjxbkFip253fiEQfzk9PkGSf",
"iXHxguKRns72E5ijP5MHwa2nc6kHfldj//Ne2h0bE8wJf2PJ43/+ft0pWufVVsBgiI0lpjEJTQpwu4c4",
"t8/rbidosKA6AEgZH6jld759g+iSCbMpCdTZ6+zDgnI8xXeY40Xy31qXMrcrbQvqaIURnceBmizJIdZp",
"X3qbOL44T1XdzE9KHW3/b0L4En3gwYwIqWuu2owF4BQW0YAYg2kZBnDkUid/IkgDPCaSr+Ob7xhkY+o3",
"2Rn0B/0hRLItSIyVcO3s9wf9feOwAaS7B5lU9kgSYfXnlHhDR2TCY4EwWpTi4nEheREsNi07q66kcNFx",
"wi91hlwb0gPeRaXgexvPbo71NJ2u2vXOUedfavHZbprYZ33r0eBPcBLJh8fAf+uuEPheBZqOx/bC98AQ",
"+W+f1LVUG9xhN0eDQSFLB86sanv/FPqQcJ7RssQFJvmATjUwzAX/w3moY/FHbjj8H4WUVFXh2rmUUN7U",
"PSnAD88M4vgWVqU5cvx6vNAUAnmrlgUCvaVRxAkLN55fho+rwrEzLlZ/KzqswxHa+ZVIvOtD1bWJNsdo",
"TCRO1YAKXA02gKvD3nBtXI3zuLIZMaqQpZddEHqve2qlnW+fvrk81zIVAzj5wPnSItUD4kRySqBSfQLH",
"9ySJIjjAZgRbT59LLMlbOqeyCgzTdi9rCAAcaGb29UiZfs/UhLNhf9Bt2NztY5xqMqHutN/c6Q3jYxqG",
"BC7yB6PXzT2uGXuH46WBDqJgDtutSh+IUGYvp26C1ElVlj8+KRGY5p7Xu1Q6kpT2gKdKYOm8lZp4Ooo+",
"FkxUOSET4UmI1EfX5r4PYYozlkQhGhNPvqPyOahHPcsljze+q7+ycLma4K6VBnej/mBFsemXeaMiH1eL",
"rVFBbI1AbK3OgMYR1MOCFTkSM7us8fAtnIjD74vYgoyFg2Q0uh4ePPQ8Gq0rY8PVZOzGdrZxT60L7b+f",
"LB20kKUnLJ5EVN/4n6TwPXH9swtZ5Pzy91vXvX/s5QunrXwVyddLEdrPyIBwS+G5UYfsoPHyJsY2kCAo",
"CHjVSw1luoDrg9PYXM5tEoibuJDf2IQqap9Y/0XoOFc77vkqtJGrULcitcR46d9o/brpAzQjmRykq/pT",
"VcNTLlmgCE0QkqouokhxWT7jCqANxeYgLvqsFEF88O3RZdg/3BzeVem386UnSkUiykUf2tZvyFVBMlmm",
"CnXW2l7VysVEKy9pbU/cIFfAUm/WMHfIK3Lxoe/F9eD10fCwAn2Dvvr/wwrUacSRw97kBX7Ze/V6MOyN",
"g5D0HMwNnzzqiAd1ozzqPrl1LvXtusGKMTwcrKTA5EuE1twOnZqPz1fDJ3g1LJddbaWYZLWGWigmqjEn",
"MxILCkltirXdxIzdq6uCzhamazjpGldxmFaYuomzBPaVmsqpPjtFMct9PpkgopObOGauJkSFfZUKQd/h",
"KMQSq58nRAYzEuZL1YCLRxAkHAdeveY3IvMFnJ7VmhS+w02rNVdeZQZc94G84M1lgticSklCSKcuKiij",
"/70UoGI1kD665ol+LTVkz+Jo2UVwNYWfYyZ7zqcqyNO6bBnUpeIOD9Z6ChUXn6K1uFDUMXcc6hqjh4N8",
"yc9h+oOpvrmfL+qYKyoIlQH9Vf1eDcql9fYH+bp2+vg1FR//yAq16bV+Ry0yX9PLo6alVbRqVbjUprK+",
"UuaBZFQ5WzXewOX1oF59HB36cabhnLzo4ZfjV73Xg+GoF4Rk0nMAHfkh3e/F5L4Gb/s9FoUFHW4lfaxU",
"V9Wjkp2UDt4pidWp9e9pYDpo7vGeyTcsicMnq8L9RowG56v92EqR+zM72r7VqXKguYsqzUsdYToUu1+l",
"C7kW/gfdsB//ffUIHQcBWUhAL5vo9pVG7i5askSf2kYNFUS7bf7Uz7FbsH7/O18YfxrxVCEx6l4g8xcl",
"H2hZE5egzk/B9LHAMvC42+kU3QK0Zbgc2rTlaAdoXcfw5dzXd8GCERcumSfmrTPAccygosechZB/pyz8",
"9KQbeuGsYvtCPvRazkU7l+SOQnK4dXjWZIr3cK6OkABnvYVxom96h3yW/BuT/AUS2J7kd+jnUc4AU9r3",
"+QT4MU8ALS8qy0WsrKLuWf7Wl/gHHhReV5crIkW+KgX2ViZRawJbodKK1XrGWLE7x7Ew4V6SISzZnAY4",
"ipY3cRARrF1jF5zcUZZkVqhJhKdw/kBGohmBB2MWkz6yVtSDwWtE1WGkowkAPTdxViUSo5BOJgRGS70H",
"WGwmiLxWymPTe/vq+Wh0DU82GxHSXp+TOkmto/aVtGbciG2W8MwZ+cfwSmkSzo/mlJIR3bNQbhTKP4Uf",
"ixUURV24hQQ3Ns0Kw8JbKqQwLz7wwHL896v0kQWyfGRm226WXlZnehOMQzoekSxsQZCyd8mxgkJbWX+O",
"JxhOBLxwPbVHGIJ5MANtV2fdmWMa7UEIXLptFaAK6Frrp9FcNan6aQWcRdIa614A7EcPojo4ipwMxiTG",
"4wjeU0Iq7D9Vk0+tYCIRvF8qykXjZRU4jMtb+OqDxwZQpRCZv83hdAt1RSMspE763AauKwVOSDkJaqgd",
"oGJch+15ESUCByz9l5rHB8J6L07tjrKU45tcE0DsPLsffFf3g8RIZnuMwOat6o4OHE5jNGZyljtB1Clx",
"Qficyj5lSCwFPF/DuWKrK7pZpoyT5HlI5gsGJp4e4kb/Hg0G6MP/o1Rw7XwWcYLDpc4JZ4bUGZsCHCEs",
"JafjRBLRVyroX/6S5vB+E7H7o7/85SYeGjcDKlInXxrngN9ZcArlHvLxg2YZuzfxCJ61bfPATjHBNBJd",
"lB6F+gcN4u/X1xfocDBCOzFzKvmQcPcm3vdClKFvJxcyugd4S2E5AFiyxkVojBCmUWRRRrMg13Eiddon",
"eSuWcUDC/4LXwpv4sI8udU4z7mQCy0M1JkL2yGTCuOxCGAKNE7DywdQJJ2JX74EaCR2nG41+JTN8RxmH",
"7eiZiSA5H0Coplmqa0I2F4nvKGe6tymNLZIF4beg6uhF2n+bPF1dk1/hVn3bVfMYIbAXs7gHE6nRdOY2",
"BTASNIILm6k0RVDEplOlaxN+R3hP0JCoYSAxuKt/Z/gxaeAWC3XdtBnB1W+3WRObkw5SlqjxrsuNdJmr",
"OV6iMUEYiWSsbqc2SZoWE9l0qjGsT0dAZivqFse1pf9iSE6uVIWFXKIdIJEZFihmbhIws3sXmEuKI3Sl",
"l4yuAhJjTpmw22f4FCh8NBgickdixa4emmQcCTYnJdYHcNVgJzMSfM6TpMbUkXHAMBk9dHkHDomWYia9",
"fAPjsflCbWUBEXeihEbJUEgkCWQRuJSa1XjApwGOXdFA0I7hp11gKLVvEQ4+oxzbop0UsF2FHbU4zaMV",
"wTnpabqy4XrF07o6yuXapo8yES7I2pVzKdCTDMaH2pzXgjxNAOlZwceqI2OOZTCDEifpiYF2aHr+7G5I",
"O2kX8rOdZT+HsDzFq7/qNWru9SsOf8OS3Jt0hmtEvRi2rNDvCnaCvT/hNvNNK3wR8aUVUfILx/qI1G2E",
"4X5tQahXAvsIon8z5YgKRDknYNAYR2BwBQEilC4xoXwOZTYQXIayuptlW8MpgOJKy1pjwzulZ4yJzUAP",
"cyhppufUC6u+i5lmJXG3BS+/FYWCRkOjUNAbtxWhMBq8/G7LWmglJaXMEO3Mk0jSnrYw7D7bSH+Yhyu9",
"41quLDKRU3dVbXCdyqkwLUQVGi+1FQ3hMORECK9rVV4/2z576xjjar6G7890/kO56ABlWmqro3C1Tx5j",
"jLotCXid1EOVb/g4DveYe5VW2i9LpDGvQFJ9dRt06KdwGVFzpGR4BhNUULyHLDVAG6DKbuf/66kRexqC",
"nlmfJ1frjMA7rR8f9e74xUk01s4/tJ4lRXP9PD8Mi/kEDSP6yp3aaggFVGiL1Ibkz5OMKQfbhLVDEssJ",
"1QbUlZwkPqaBhS086bQl1Lm5QrV3eHfoovQJYrcgCNAcx3hKRNn84pJuH525Z5/jbBfMcDxVqlUiiDkg",
"obBGugqlSJvs+fr1Z9dYYY8tqMiswNiOMrABvhRyML9ZB6S8dRZMcHMqbc7fJUtQyOJfJLrHsa7NBmCm",
"dkIS6zidzM5jGsDfronyHaBHGxkvzy7eHp+coSvJsSTT5a6BWOet1LVRHfsbMu2vwASX1ivRLXYU/nTF",
"mjsCpshrpv42QxzZolro+O3b1DFFd714+/EqdUtRP5nenMzZHSkNkHpY6t4KNxYrnwlZFDpbY+GRRhKO",
"jUFQr+iPTxbVab15Y1qEx7SI4DsfKSWxIZMt2H43aMW1N4q+uyYxY/fCVIuNcWSyZ+qcgtmFyV1uzsaY",
"H7LWxpjZFBWSzrQzC+qhY6AKhRVA0IlLDEfoj5uOY9S+6Xy6iQ0l3djyFDcds5sVXbpu00838SU8aje3",
"y4N5SRYRDgg6jiK9hRXQpiOo4TygG06UDP1TkcBNx7Hqe1aS+5oHPv8J7ZiZgQqciQ3tgw+lz1/40Yyu",
"1e68qdHVke+b9u3d8OXj2VX0Z3AVLRIdtry3shVxz7horO4qWtCCvE/Qp3rw1PiY1w+64OOpdBB1RhcS",
"wGbx5eni4IxYcALnQ+i+QXtMjXrmx735H4NvSLNhz3jFPDPhD2r20vu3ntl+T3tHbYffLklPDw+prowD",
"dbTMKM7DhX2lYEnGVZ+8E0WAF3hMIyqX9bx2Fj9ZVjOuaM+c9mNymqasFoxmCnQopmrhC6Vbp/X+0qwm",
"NaY16H9iy4BsQ9/Ug9e/8Nc+7gcudA9NbbkKzOengAhfNL1G9M/yvP3k0yemJGBZRW+Aj1f2/jTB7Ku9",
"IRvWgRt7BPdrFlDYXJ0LqFQVW3RtXfEuClgUkUAy9e8xlsEMJYuI4VAYX/VlHJgiYqKPrljCg0Ixq6t9",
"0AHff7hOHw/HibQ1O7GcCceOsOVn7FQYPLU37K7HAh7DzN3UU/Nq36CLTRDjixmOFXt6MK7d2Iwj3Fix",
"eWXynzETFY70NlfsY762N79GG+Hke2ZHO2Y9aM5CsjkPI4+lvgaKH1JR+RnUjt8xD3t6R8oij0qBCpGZ",
"RSnbXSFDiBm9PjeIo3hsi11YYCap45Qf9uH66SZ+yPYfMstWk9Rqt0WrmbV5onLLtruqsdIoKjVi3fkR",
"NOJ686vViFc2u3op/Gexjj5h0yVeWUndS9XG1XIvpd2scDW0XSVjbfNfl4Z5wm0K3HQ6fxYyC/qzzN2G",
"zHUpI6UKlyJtg+0K4Csi7fQZSM2i96qaWLcghDNUyBZC2Fx1bH2zdFn9FnLZpx2naFGjPovlzXPEVZ4j",
"6pmgKJnTKuzNghkK45ug0uyGOV469XW9kjlX7/pxRHO+xHaNPpwuQy3smSA3GPIqc2XMdbHwkt6Q7tGm",
"pLTXdPwRTFRKsYAy5rrMskmdrT1VCsGyTuqeosqsRrJQ18pr8IhfYC73JozPezDc0Z8dEgcspPEU8irT",
"XOWha1OS3aF6XeyZfFlEUM/UWGGEXEJyFDWw3paUKfJV1u0EZXlvUaCtd25p2DGNMZiFckXzX+6/PBi+",
"Gh3U1pr2FNy1GA0SIfOVpbOSuJZKyqWtDwavc1kR+n9tLNAPSy5X3S9LAL1yjQhjFVvj1mFJwSBS58Z7",
"liGbuGsYnoUNrZUYVUfa3tjqbI03Dnus5UzaqQpk7iMkzARY9Qn3qxqCNKZheaeTkNhMLGnSecjnLhNe",
"ZU+OgEYep5DO+zJs4jNdVEDGJhNBKkAbbKT29DaNzbBtmuiaEmrkiOSZ3TenMpS57/urDP84v0CYBzNd",
"0iOWmEIYhz7dI4IuTt8U1Rx4BNNrWXBmcrI1qBJAfg/VJwyc9SrFV2DgtVUKZ45qrWJM0vMQYZHHYntd",
"o+motwO2Oe0rtjG3ey00gNE25E3dC9evDktAgr+FJKHO2+WS1rMI2pjGUcHZWBSIeG2FZO9P+E/LTPTa",
"MENCk9oL0Vhzjy1B71hLXfHpM4+WZc32z9IaZ+YcaWdVXZ+fS7+TUdUlnyyR3FaO3W5jWyAOOKGLzES+",
"QN2HlZ0vWxz65zGVFN7TlNavJ7JytmzWuuZ0OiX87IupQ7EN060e3MxUZbs1gJqzS2f3elQfsvPwHREC",
"T/3FmzV0Uq/h+WVuK/xrKES/AxfrosAP1aeT8e1qLnAXRdYPDI1JxOIpRKqxVEnXJdJTVyMTJicQwcHM",
"9PxF3MTnp13wXNIeYwusM0PDZ6S+4SjSdfL0qOZKHLBYSJ4E2rBoms8o4epEXvZv4kvGZC8idySDEjJw",
"6Qp7OguXnuw8hEBJ8Kmaq4M2ENptjIrsqt91wdYj2bi/TPOy3W/iHXvGo4AloC9AhOWYRGjMCYb68LvG",
"nQ2HoUDsjnBFzUgwW00e4gnH5CZOBAnRvQMeuMTFhIQkrCovrMXbG7OXDcYHx52selE6v6mDhNIax0u9",
"xN3+TeyWJMyqty0IB3VFF+zBgsVCL8BnQDDzPh0HNI3NJnOApbb0wUwzw7Mq8x0tCe6eeGwIemM3YkAo",
"SlOjOLV7TQNLgVHsW3k4XEHbR3Jv0D4desZ673CzhGdPh40r5YYcHELZjodZRsZtSvBbc7lSCewpbTyN",
"dWKKfs0ZJbZPtGqyOquKFd0W9p+nAteTlcgOqTT4jaXWkvXCG1IlxXr7hmRB4hBe+/PxDSRWuy66KFC6",
"hOgiSb5IRL5IrgubiK7JVJD7CTQeE+5gktRvItKhIkTBeWpuq9E9Bwh4nme/c4hALRzPquKTCBIIM1ar",
"NXq10OusN506IjOBZDQ8GnYdIZRW5xNJMENY3MRad+2mvhJ5kWN9VEzqGUgnVpBbJgCromD7GhIlvck3",
"zFgUJhVyRHW+hA5PSJQYrJzFnAYzReS1wuTHTf/3s1irw8JmZNy1WaO1HSLVktOjc+9P+8/z8Nue5tE2",
"ziZRZBgayRmW2j41JiRGQL06IjiTGl0ExUiU8rBEcyak4jWwmFEuZL+Ow99qkB7INfnn32yZVJePb/DD",
"VM0Nr3+DZ15bdX7geJJBCizY/8Z3XKu5azieOfB7OuFnhKzNLJ4DFPa/hvk8juiWr8GbH44PpcVmp0fG",
"dLUxtamPQZKASHA8DA4H3ko9/jehY2BKdbG4Z/zzJGL3xqyb41Jt/OM4+KzOdMeWCknNylyqRl0Ccrb0",
"cARjH2fdq96OsrUoiB711SgnGzycDpBZmfisLz+F+niKRhD2MUA1z6szkyQRbjQr+WqPxqHOIvWWBkTd",
"To+nnOickWmZanhSIXG4YDSWiAq0SMYRDUBZTtOnGqoupqjyBpFpEM4UyA+tEvr9ajBvoXT+FivlX8CW",
"1Ub6G8L4WQvn/zDioWyh9uxMbcFMKHYMlFud7EcfC1omJILwX4SmdWB9E7nhm7h/E1+mlcyGcCqDnpz1",
"7kKJs2J9M/gc5mvS3MTqgDy/SHPzUkivxeZY6lLHKMALmSjVHNLcO+WivFWI1RTXbCNyRYObq+FbKLBO",
"nEK3miFzv52vUji9ZdtgNZ5PRXnrikJmkyRLt94Kgh1r80yD4NP9ftzaQvV7VKiz/JPsUXYmaytQycpa",
"4MDd53eVzahjWkgaQeZKR1cyNovi5hfrknpWEsnmATh1gfCL5lNiKsLZdhMO7AQ8/a+EBp/hJUepMTQm",
"Skhb+Q/OLzY6ecHZHNyglQaqQKiwdMJ67Zvp+gJXZNwMl5vMYdhW8isLJoP5OxwlLQW2blEUB2a8+p9X",
"ERQzLI7NUuB+963biZk8rlsd5FNvXGEJ0tEDIB3nIQWD8LdvK4kuvfF1cguI0xDusyb5BIs/GCkD+1QU",
"NfViDTwe0/djr1gz1VxcaaUT0LNJ5kXpSZSqnSmlTVOzpecIPU2N87zxqlX8CbZjNQa8LD4frpskQeNa",
"nVrxqnxqV31V0P3TNwVwM+g5bgYt/cfy7wF5V4WCUbbO1gEF28/Snp31zLSFV76t2Wm35+6ZR0Mbc0QR",
"6c8nxvf0lavYFNfxM7/Fq1ZgL/EYpI3QWSV6oAqZQigcL23JnCY21DOUWXAbrwIlCjek438bKK3WpMR4",
"vCeCFTjyTRHYn6Yg70+h0LkpjtfhT+8puTejQjK+bPnqbi6iYLsuOfYV+HSNZ/cC7L8b2H7iwzTvGmDR",
"29o5oIAwe3XciJ9ASRwYUnmWAN/xgC4dKNZ+O0t5ZU1BkL49rZJd0s7uEQdtlOcCiL8u/5Y+fjxZju/6",
"gLF4MKlodAYaQFe1I7DIlJQGwGgsXxy46V5ej0b7+y9Hg/0Xrw4PXr58MWhMTPNElP43PiLJ5weyts5n",
"KfOEpMx46XmXrBcwWUhsq2uBjo9U5MD4FMf0a658QqXOD922perD4A+oRTJxoXskdV9P6WM9jeHnKiSP",
"paLbza+KmHS4ZO9P/Y/zdaJ1DOu4mfmDGY3CNIKTK+AFvSPR0sSXM5aGi+9ARdxf9n7ZdQrnmhCLPjqf",
"uLHikpM09Y9wa5sY7/bb9Kc0GDzlC1AGDgav0QmLJxENpP4qEx6ricBV3j9MV4cgFcNtABjMCcKRYG7h",
"/Qz6rYUUpWKnrfs/wJhumW85DoorVIYSdlZz/X8uiNIu3umNQ+vfOeSpCZR/Cx1p0ELQW5nyg4RJ5Xm9",
"dUS9x7xoU374/bztobJpL+/6QhqONFPg7ClgyoL0EgKzHkF/0xNV6W+O7mlw93jliht1NQ6gP9tfn4ir",
"kC70v65il0U9tbS1ZgfuPZUzGrt2FxvgmAVB2lDHLGuO6N/EH7JQx9YRiGnOmtR0k3mEYhTR6UwS3rsn",
"6h9ISw8kZzhGv51do3zoOdohX8yspqY5DXWFczZfJJCGEV54divcjzRmTx2FZ+PxlqXVPrGIy7yBOEdD",
"rSzEafBdbMVN0Tb8oCCynFaoqfJZQj2BmE7Yjxay6okqGRUi1Ka3ahagTvzYgrMpOJ+7Sbm8eOpXy6B3",
"aWKtLasDdqJqrcAs45nNvqeB1KiMpYxvPyarzZiuPNHovfz79bu3aE7iBC3wVOenc+LBTGl14eWj39mc",
"XOBpC58/ddTuzeQ8yvNOHiqAxHRKXxHUOjRkCkY3TTYMV0iSffDq8GWuNsb/6f/lPz3VMUqM+Hs6jYLi",
"2Xi6Mbby7mFa8DyRs7ySTUNFeHLZinTLdPqLQFgYd3rOIiJ0ENIF4XMq+5S56jUUSFcfhPZ/mHIcS+3a",
"ANqj6t9Hx/HSMw8KcIwCpdLLXKClZCikImB3hBv7I7uPNSReDjq3y93iIWTnaIwo0giD9KMOWp6zgK3B",
"IqrX5rLiw6A1yYTf6FzozhO1l/jXdBwEerd8qRN8qcFr2VjfVvf+hP++x3PykLtyISGIxNMpCbWToXN/",
"hqlqM3+IX53kAk/g/nZl0L2t6xtgCORTiptnlt1clo8Coouk+KB8HzqXgKkJ5lEbU76q1RtzqtEgpxb9",
"cdz7B+59HfRe3376q09D8vrEpGlYIZ8xjSThuUIh3lL/+TKefkibk1een1rNNmJTWu3TlM9WD20R5CcZ",
"LxEnIeUkkCZD9wmbxlQydH56YRoqHcWTpBMmLAiNfS3fixJMT+COno2c57+3zOQgKVvdL9/aaG7vIL6L",
"Aad5VXg0OHiV2/CZlAtxtLfX/4u3WNyzNKiVBoodAr0XRigoGonhNkbmmCpuX3Cmy+wV8nzC1pn9dA6W",
"+vNTdeopBXOMg8+VxP47jsPIkPoHNcoI2T4oTLjVcg31mJIN6JIEhNpgGYtiXTslYCHRWoPLHOrMJ1+C",
"GY6nRCBqLmfsM4lFBbecWMgbZN1x/exVIoWFrQVfiREyyXdz02sr/CC0DaVLAQScXF2+UUiVxPpr+2AV",
"0le7vVJKjw5WBvbTqpLJIUKEJ2o5mWavaWVFSWXlavVo64us/7tKZv07SZ+cQCkyur18NgkUllSXM37L",
"pllaEcQSnZZTIw5LK0cEgTuhSfLrPUv1PClMXvGgAHnAaarGB2MC4zlSTq0MD6PeyAL4TLNbSnyttzDN",
"DtPqTITR+Z3/FLngLEz0E6Bu1Ol2Eh6BjVBrPSELvi77AZvv3Q2h1JGZplyZ19CqQJxE2KRTyDJ0G6Ge",
"T9BdPi4qhrF1x92RirXI2w7mqt1mrFJez1XHSt0LIaWu9tm1+2LmyFvAV57AZEZU46eJA22CwGyS3H1p",
"9UUUg5gc6P3uzW2n0HHPznj5gOe2w+TTrWWjuSTfdiyQ13Mc4ynJ0ruHcxpTIXlxfPX7qhPUJJsrTFve",
"RzcHwbdP3/7/AAAA///pcQ5AzMEBAA==",
"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",
}
// GetSwagger returns the content of the embedded swagger specification file
+4 -1
View File
@@ -16,6 +16,7 @@ import (
"queryorchestration/internal/label"
"queryorchestration/internal/serviceconfig/auth"
"queryorchestration/internal/serviceconfig/objectstore"
"queryorchestration/internal/uisettings"
)
const Name = "queryAPI"
@@ -31,13 +32,15 @@ type Services struct {
Document *document.Service
DocumentUpload *documentupload.Service
DocumentBatch *documentbatch.Service
UISettings *uisettings.Service
FieldExtraction *fieldextraction.Service
Folder *folder.Service
Label *label.Service
Eula *eula.Service
}
// ConfigProvider combines auth and objectstore interfaces for the Controllers
// ConfigProvider combines auth, objectstore, and background runner for the Controllers.
// Database access is intentionally excluded; controllers use the service layer.
type ConfigProvider interface {
auth.ConfigProvider
objectstore.ConfigProvider
+3
View File
@@ -17,6 +17,7 @@ import (
"queryorchestration/internal/label"
"queryorchestration/internal/server/api"
"queryorchestration/internal/serviceconfig/queue/clientsync"
"queryorchestration/internal/uisettings"
)
// ControllerConfig provides test configuration for API controllers.
@@ -51,6 +52,7 @@ func createControllerServices(cfg *ControllerConfig) *queryapi.Services {
fld := folder.New(cfg)
lbl := label.New(cfg)
eul := eula.New(cfg)
uiSvc := uisettings.New(cfg)
return &queryapi.Services{
Export: exp,
@@ -61,6 +63,7 @@ func createControllerServices(cfg *ControllerConfig) *queryapi.Services {
Document: doc,
DocumentUpload: docup,
DocumentBatch: docbatch,
UISettings: uiSvc,
FieldExtraction: fieldext,
Folder: fld,
Label: lbl,
+148
View File
@@ -0,0 +1,148 @@
package queryapi
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"queryorchestration/internal/database/repository"
"queryorchestration/internal/uisettings"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
openapi_types "github.com/oapi-codegen/runtime/types"
)
// uiSettingFromRow maps a repository row to the API response type.
func uiSettingFromRow(r *repository.UiSetting) (UISetting, error) {
var valueMap map[string]interface{}
if len(r.Value) > 0 {
if err := json.Unmarshal(r.Value, &valueMap); err != nil {
return UISetting{}, fmt.Errorf("value json: %w", err)
}
}
if valueMap == nil {
valueMap = make(map[string]interface{})
}
createdAt := r.CreatedAt.Time
updatedAt := r.UpdatedAt.Time
return UISetting{
Id: openapi_types.UUID(r.ID),
Namespace: r.Namespace,
Key: r.Key,
Value: valueMap,
IsDeleted: r.IsDeleted,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}, nil
}
// CreateUISetting creates a new UI setting.
// POST /ui-settings
func (s *Controllers) CreateUISetting(ctx echo.Context) error {
var req UISettingCreate
if err := ctx.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
valueBytes, err := json.Marshal(req.Value)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid value: %v", err))
}
row, err := s.svc.UISettings.Create(ctx.Request().Context(), req.Namespace, req.Key, valueBytes)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("create setting: %v", err))
}
resp, err := uiSettingFromRow(row)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return ctx.JSON(http.StatusCreated, resp)
}
// ListUISettings lists UI settings, optionally filtered by namespace.
// GET /ui-settings
func (s *Controllers) ListUISettings(ctx echo.Context, params ListUISettingsParams) error {
var rows []*repository.UiSetting
var err error
if params.Namespace != nil && *params.Namespace != "" {
rows, err = s.svc.UISettings.ListByNamespace(ctx.Request().Context(), *params.Namespace)
} else {
rows, err = s.svc.UISettings.List(ctx.Request().Context())
}
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("list settings: %v", err))
}
settings := make([]UISetting, 0, len(rows))
for _, row := range rows {
item, err := uiSettingFromRow(row)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
settings = append(settings, item)
}
return ctx.JSON(http.StatusOK, UISettingListResponse{
Settings: settings,
//nolint:gosec // settings count is expected to fit in int32 for this endpoint
TotalCount: int32(len(settings)),
})
}
// GetUISetting returns a single setting by id.
// GET /ui-settings/{id}
func (s *Controllers) GetUISetting(ctx echo.Context, id UISettingID) error {
uid := uuid.UUID(id)
row, err := s.svc.UISettings.Get(ctx.Request().Context(), uid)
if err != nil {
if errors.Is(err, uisettings.ErrNotFound) {
return echo.NewHTTPError(http.StatusNotFound, "setting not found")
}
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("get setting: %v", err))
}
resp, err := uiSettingFromRow(row)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return ctx.JSON(http.StatusOK, resp)
}
// UpdateUISetting updates a setting's value.
// PATCH /ui-settings/{id}
func (s *Controllers) UpdateUISetting(ctx echo.Context, id UISettingID) error {
uid := uuid.UUID(id)
var req UISettingUpdate
if err := ctx.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
valueBytes, err := json.Marshal(req.Value)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid value: %v", err))
}
err = s.svc.UISettings.Update(ctx.Request().Context(), uid, valueBytes)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("update setting: %v", err))
}
row, err := s.svc.UISettings.Get(ctx.Request().Context(), uid)
if err != nil {
if errors.Is(err, uisettings.ErrNotFound) {
return echo.NewHTTPError(http.StatusNotFound, "setting not found")
}
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("get setting: %v", err))
}
resp, err := uiSettingFromRow(row)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return ctx.JSON(http.StatusOK, resp)
}
// DeleteUISetting soft-deletes a setting.
// DELETE /ui-settings/{id}
func (s *Controllers) DeleteUISetting(ctx echo.Context, id UISettingID) error {
uid := uuid.UUID(id)
err := s.svc.UISettings.SoftDelete(ctx.Request().Context(), uid)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("delete setting: %v", err))
}
return ctx.NoContent(http.StatusNoContent)
}
+165
View File
@@ -0,0 +1,165 @@
package queryapi_test
import (
"encoding/json"
"net/http"
"testing"
queryapi "queryorchestration/api/queryAPI"
"queryorchestration/internal/database/repository"
"queryorchestration/internal/test"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCreateUISetting(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
body := queryapi.UISettingCreate{
Namespace: "demo-dashboard:user1",
Key: "theme",
Value: map[string]interface{}{"dark": true},
}
ctx, rec := createContextWithJSONBody(t, body)
err := cons.CreateUISetting(ctx)
require.NoError(t, err)
assert.Equal(t, http.StatusCreated, rec.Code)
var resp queryapi.UISetting
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
assert.Equal(t, "demo-dashboard:user1", resp.Namespace)
assert.Equal(t, "theme", resp.Key)
assert.False(t, resp.IsDeleted)
assert.NotEqual(t, uuid.Nil, uuid.UUID(resp.Id))
}
func TestListUISettings(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
_, err := cfg.GetDBQueries().CreateUISetting(t.Context(), &repository.CreateUISettingParams{
Namespace: "list-ns",
Key: "k1",
Value: []byte(`{"a":1}`),
})
require.NoError(t, err)
ctx, rec := createContext(t)
params := queryapi.ListUISettingsParams{Namespace: strPtr("list-ns")}
err = cons.ListUISettings(ctx, params)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
var resp queryapi.UISettingListResponse
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
require.Len(t, resp.Settings, 1)
assert.Equal(t, "list-ns", resp.Settings[0].Namespace)
assert.Equal(t, int32(1), resp.TotalCount)
}
func TestGetUISetting(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
row, err := cfg.GetDBQueries().CreateUISetting(t.Context(), &repository.CreateUISettingParams{
Namespace: "get-ns",
Key: "key",
Value: []byte(`{}`),
})
require.NoError(t, err)
ctx, _ := createContext(t)
err = cons.GetUISetting(ctx, queryapi.UISettingID(row.ID))
require.NoError(t, err)
}
func TestGetUISetting_NotFound(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
ctx, rec := createContext(t)
err := cons.GetUISetting(ctx, queryapi.UISettingID(uuid.New()))
require.Error(t, err)
httperr, ok := err.(*echo.HTTPError)
require.True(t, ok)
assert.Equal(t, http.StatusNotFound, httperr.Code)
_ = rec
}
func TestUpdateUISetting(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
row, err := cfg.GetDBQueries().CreateUISetting(t.Context(), &repository.CreateUISettingParams{
Namespace: "upd-ns",
Key: "k",
Value: []byte(`{"old":true}`),
})
require.NoError(t, err)
body := queryapi.UISettingUpdate{
Value: map[string]interface{}{"new": true},
}
ctx, rec := createContextWithJSONBody(t, body)
err = cons.UpdateUISetting(ctx, queryapi.UISettingID(row.ID))
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
var resp queryapi.UISetting
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
assert.Equal(t, "upd-ns", resp.Namespace)
}
func TestDeleteUISetting(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
row, err := cfg.GetDBQueries().CreateUISetting(t.Context(), &repository.CreateUISettingParams{
Namespace: "del-ns",
Key: "k",
Value: []byte(`{}`),
})
require.NoError(t, err)
ctx, rec := createContext(t)
err = cons.DeleteUISetting(ctx, queryapi.UISettingID(row.ID))
require.NoError(t, err)
assert.Equal(t, http.StatusNoContent, rec.Code)
_, err = cfg.GetDBQueries().GetUISetting(t.Context(), row.ID)
require.Error(t, err) // not found or no rows (soft-deleted)
}
func strPtr(s string) *string { return &s }