From 788690086ecc1a77f024ba7778733520dc1dd760 Mon Sep 17 00:00:00 2001 From: Christian Beutel <> Date: Sat, 4 Jan 2025 14:24:00 +0100 Subject: [PATCH] adds email notifications --- db/main.go | 12 +- .../1713027641_created_trails_bounding_box.go | 2 +- db/migrations/1735935107_updated_lists.go | 76 +++++ db/templates/mail/notification.html | 122 +++++++ db/util/email_templates.go | 67 ++++ db/util/notification.go | 85 ++--- .../docs/getting-started/Installation.md | 18 +- docs/wanderer.openapi.yaml | 308 +++++++++--------- .../trail/map_with_elevation_maplibre.svelte | 8 +- web/src/lib/models/api/base_schema.ts | 2 +- web/src/routes/map/+page.svelte | 17 +- web/src/routes/profile/[id]/+layout.svelte | 12 +- web/src/routes/register/+page.svelte | 2 +- web/static/docs/api/wanderer.openapi.yaml | 308 +++++++++--------- web/static/imgs/logo_text_light.png | Bin 0 -> 3732 bytes 15 files changed, 666 insertions(+), 373 deletions(-) create mode 100644 db/migrations/1735935107_updated_lists.go create mode 100644 db/templates/mail/notification.html create mode 100644 db/util/email_templates.go create mode 100644 web/static/imgs/logo_text_light.png diff --git a/db/main.go b/db/main.go index 96e97bbd..cc1ecca8 100644 --- a/db/main.go +++ b/db/main.go @@ -114,7 +114,7 @@ func createTrailHandler(app *pocketbase.PocketBase, client meilisearch.ServiceMa if e.Record.GetBool("public") { notification := util.Notification{ Type: util.TrailCreate, - Metadata: map[string]interface{}{ + Metadata: map[string]string{ "id": e.Record.Id, "trail": e.Record.GetString("name"), }, @@ -163,7 +163,7 @@ func createTrailShareHandler(app *pocketbase.PocketBase, client meilisearch.Serv notification := util.Notification{ Type: util.TrailShare, - Metadata: map[string]interface{}{ + Metadata: map[string]string{ "id": shareTrail.Id, "trail": shareTrail.GetString("name"), "author": shareTrailAuthor.GetString("username"), @@ -185,7 +185,7 @@ func createListShareHandler(app *pocketbase.PocketBase) func(e *core.RecordCreat notification := util.Notification{ Type: util.ListShare, - Metadata: map[string]interface{}{ + Metadata: map[string]string{ "id": shareList.Id, "list": shareList.GetString("name"), "author": shareListAuthor.GetString("username"), @@ -211,7 +211,7 @@ func createListHandler(app *pocketbase.PocketBase) func(e *core.RecordCreateEven } notification := util.Notification{ Type: util.ListCreate, - Metadata: map[string]interface{}{ + Metadata: map[string]string{ "id": e.Record.Id, "list": e.Record.GetString("name"), }, @@ -231,7 +231,7 @@ func createFollowHandler(app *pocketbase.PocketBase) func(e *core.RecordCreateEv notification := util.Notification{ Type: util.NewFollower, - Metadata: map[string]interface{}{ + Metadata: map[string]string{ "follower": follower.GetString("username"), }, Seen: false, @@ -252,7 +252,7 @@ func createCommentHandler(app *pocketbase.PocketBase) func(e *core.RecordCreateE notification := util.Notification{ Type: util.TrailComment, - Metadata: map[string]interface{}{ + Metadata: map[string]string{ "id": commentTrail.Id, "author": commentAuthor.GetString("username"), "trail": commentTrail.GetString("name"), diff --git a/db/migrations/1713027641_created_trails_bounding_box.go b/db/migrations/1713027641_created_trails_bounding_box.go index 261f4d1b..1fc4366e 100644 --- a/db/migrations/1713027641_created_trails_bounding_box.go +++ b/db/migrations/1713027641_created_trails_bounding_box.go @@ -86,7 +86,7 @@ func init() { return daos.New(db).SaveCollection(collection) }, func(db dbx.Builder) error { - dao := daos.New(db); + dao := daos.New(db) collection, err := dao.FindCollectionByNameOrId("urytyc428mwlbqq") if err != nil { diff --git a/db/migrations/1735935107_updated_lists.go b/db/migrations/1735935107_updated_lists.go new file mode 100644 index 00000000..d35a1601 --- /dev/null +++ b/db/migrations/1735935107_updated_lists.go @@ -0,0 +1,76 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("r6gu2ajyidy1x69") + if err != nil { + return err + } + + // update + edit_author := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "kwm6zdet", + "name": "author", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }`), edit_author); err != nil { + return err + } + collection.Schema.AddField(edit_author) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("r6gu2ajyidy1x69") + if err != nil { + return err + } + + // update + edit_author := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "kwm6zdet", + "name": "author", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }`), edit_author); err != nil { + return err + } + collection.Schema.AddField(edit_author) + + return dao.SaveCollection(collection) + }) +} diff --git a/db/templates/mail/notification.html b/db/templates/mail/notification.html new file mode 100644 index 00000000..6a1e84f4 --- /dev/null +++ b/db/templates/mail/notification.html @@ -0,0 +1,122 @@ + + + + + + + + +
+ +
+ wanderer +
+ +
+

Hello {{.RecipientName}},

+

You have a new notification:

+
+ +
+ {{.Content}} +
+ + Check it out! + +
+
+ +
+

Kind regards,

+

Your wanderer team

+
+ + +
+ + + \ No newline at end of file diff --git a/db/util/email_templates.go b/db/util/email_templates.go new file mode 100644 index 00000000..817340cd --- /dev/null +++ b/db/util/email_templates.go @@ -0,0 +1,67 @@ +package util + +import ( + "bytes" + "errors" + t "html/template" + + "github.com/pocketbase/pocketbase/tools/template" +) + +type EmailData struct { + AppUrl string + RecipientName string + Content t.HTML + Link string + FollowerName string +} + +var notificationTemplates = map[NotificationType]string{ + TrailCreate: "{{.Author}} has created a new trail: {{.trail}}.", + TrailShare: "{{.Author}} has shared a trail with you: {{.trail}}.", + ListCreate: "{{.Author}} has created a new list: {{.list}}.", + ListShare: "{{.Author}} has shared a list with you: {{.list}}.", + NewFollower: "Good news! You have a new follower: {{.Author}}.", + TrailComment: "{{.Author}} commented on your trail '{{.trail}}': '{{.comment}}'.", +} + +func GenerateHTML(appUrl string, recipientName string, authorName string, notificationType NotificationType, metadata map[string]string) (string, error) { + registry := template.NewRegistry() + + // Get custom content template for the notification type + customTemplate, exists := notificationTemplates[notificationType] + if !exists { + return "", errors.New("unknown notification type") + } + + // Create content template with dynamic data + contentTmpl, err := t.New("content").Parse(customTemplate) + if err != nil { + return "", err + } + + // Render the custom content with data + metadata["Author"] = authorName + var contentBuffer bytes.Buffer + err = contentTmpl.Execute(&contentBuffer, metadata) + if err != nil { + return "", err + } + + // Assemble the final email with boilerplate and custom content + content := EmailData{ + AppUrl: appUrl, + RecipientName: recipientName, + Content: t.HTML(contentBuffer.String()), + } + + html, err := registry.LoadFiles( + "templates/mail/notification.html", + ).Render(content) + + if err != nil { + return "", err + } + + return html, nil +} diff --git a/db/util/notification.go b/db/util/notification.go index 0d552bb6..20c305a7 100644 --- a/db/util/notification.go +++ b/db/util/notification.go @@ -2,10 +2,12 @@ package util import ( "fmt" + "net/mail" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/models" + "github.com/pocketbase/pocketbase/tools/mailer" ) type NotificationType string @@ -20,10 +22,10 @@ const ( ) type Notification struct { - Type NotificationType `json:"type"` - Metadata interface{} `json:"metadata,omitempty"` - Seen bool `json:"seen"` - Author string `json:"author"` + Type NotificationType `json:"type"` + Metadata map[string]string `json:"metadata,omitempty"` + Seen bool `json:"seen"` + Author string `json:"author"` } type NotificationSettings struct { @@ -59,22 +61,50 @@ func SendNotification(app *pocketbase.PocketBase, notification Notification, rec if err != nil { return err } - if !permissions.Web { - return nil - } + notifications, err := app.Dao().FindCollectionByNameOrId("notifications") if err != nil { return err } - n := models.NewRecord(notifications) - n.Set("type", string(notification.Type)) - n.Set("metadata", notification.Metadata) - n.Set("seen", notification.Seen) - n.Set("recipient", recipient) - n.Set("author", notification.Author) - if err := app.Dao().SaveRecord(n); err != nil { - return err + if permissions.Web { + n := models.NewRecord(notifications) + n.Set("type", string(notification.Type)) + n.Set("metadata", notification.Metadata) + n.Set("seen", notification.Seen) + n.Set("recipient", recipient) + n.Set("author", notification.Author) + + if err := app.Dao().SaveRecord(n); err != nil { + return err + } + } + + if permissions.Email { + recipientUser, err := app.Dao().FindRecordById("users", recipient) + if err != nil { + return err + } + authorUser, err := app.Dao().FindRecordById("users", notification.Author) + if err != nil { + return err + } + html, err := GenerateHTML(app.Settings().Meta.AppUrl, recipientUser.Username(), authorUser.Username(), notification.Type, notification.Metadata) + if err != nil { + return err + } + + message := &mailer.Message{ + From: mail.Address{ + Address: app.Settings().Meta.SenderAddress, + Name: app.Settings().Meta.SenderName, + }, + To: []mail.Address{{Address: recipientUser.Email()}}, + Subject: "wanderer - New Notification", + HTML: html, + } + + app.NewMailClient().Send(message) } return nil } @@ -85,32 +115,11 @@ func SendNotificationToFollowers(app *pocketbase.PocketBase, notification Notifi if err != nil { return err } - notifications, err := app.Dao().FindCollectionByNameOrId("notifications") - if err != nil { - return err - } + for _, f := range followers { recipient := f.GetString("follower") - if notification.Author == recipient { - continue - } - permissions, err := getNotificationPermissions(app, recipient, notification.Type) - if err != nil { - continue - } - if !permissions.Web { - continue - } - n := models.NewRecord(notifications) - n.Set("type", string(notification.Type)) - n.Set("metadata", notification.Metadata) - n.Set("seen", notification.Seen) - n.Set("recipient", recipient) - n.Set("author", notification.Author) + SendNotification(app, notification, recipient) - if err := app.Dao().SaveRecord(n); err != nil { - continue - } } return nil } diff --git a/docs/src/content/docs/getting-started/Installation.md b/docs/src/content/docs/getting-started/Installation.md index b7b36c18..e0ec5731 100644 --- a/docs/src/content/docs/getting-started/Installation.md +++ b/docs/src/content/docs/getting-started/Installation.md @@ -115,9 +115,10 @@ To update all containers to a new version simply run `docker compose pull && doc While not recommended it is absolutely possible to install wanderer from source. ### Prerequisites -1. git installed && `git clone https://github.com/Flomp/wanderer.git` +1. git installed && `git clone https://github.com/Flomp/wanderer.git --branch v0.12.0 --single-branch` 2. go >= 1.21.1 installed 3. node >= 18.17.0 installed +4. npm >= 8.15.0 installed ### meilisearch wanderer uses meilisearch without any further modifications. As a result, you can simply head over to [their website](https://www.meilisearch.com/docs/learn/getting_started/installation) and follow the instructions for your preferred platform. We assume that you put the binary in the `wanderer/search` directory. If you did not, adapt the launch script below accordingly. @@ -157,15 +158,26 @@ Caution: Ensure that you change the `MEILI_MASTER_KEY` to a different value if y ```bash trap "kill 0" EXIT +# learn more about the configuration: +# https://wanderer.to/getting-started/configuration/ +# required export ORIGIN=http://localhost:3000 export MEILI_URL=http://127.0.0.1:7700 -export MEILI_MASTER_KEY=CHANGE_ME! +export MEILI_MASTER_KEY=YOU_SHOULD_DEFINITELY_CHANGE_ME export PUBLIC_POCKETBASE_URL=http://127.0.0.1:8090 export PUBLIC_VALHALLA_URL=https://valhalla1.openstreetmap.de +# optional +# export MEILI_NO_ANALYTICS=true +# export BODY_SIZE_LIMIT=Infinity +# export PUBLIC_DISABLE_SIGNUP=false +# export UPLOAD_FOLDER=/app/uploads +# export UPLOAD_USER= +# export UPLOAD_PASSWORD= + cd search && ./meilisearch --master-key $MEILI_MASTER_KEY & cd db && ./pocketbase serve & -cd web/build && node build & +cd web && node build & wait ``` diff --git a/docs/wanderer.openapi.yaml b/docs/wanderer.openapi.yaml index e7f5882b..82b03983 100644 --- a/docs/wanderer.openapi.yaml +++ b/docs/wanderer.openapi.yaml @@ -325,7 +325,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -348,13 +348,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 0 type: number @@ -583,7 +583,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -602,13 +602,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: undefined @@ -794,7 +794,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -817,13 +817,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 0 type: number @@ -1232,7 +1232,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -1255,13 +1255,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 0 type: number @@ -1659,7 +1659,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -1678,13 +1678,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -1834,7 +1834,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -1857,13 +1857,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -2028,7 +2028,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -2051,13 +2051,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -2163,7 +2163,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -2186,13 +2186,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -2283,7 +2283,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -2306,13 +2306,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -2442,7 +2442,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -2465,13 +2465,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -2755,7 +2755,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -2778,13 +2778,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 0 type: number @@ -3060,7 +3060,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -3083,13 +3083,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -3192,7 +3192,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -3215,13 +3215,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -3379,7 +3379,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -3402,13 +3402,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -3514,7 +3514,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -3537,13 +3537,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -4044,7 +4044,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -4067,7 +4067,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -4449,7 +4449,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -4468,13 +4468,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -4577,7 +4577,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -4600,13 +4600,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -5006,7 +5006,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5029,7 +5029,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -5197,7 +5197,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5220,13 +5220,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -5421,7 +5421,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5444,13 +5444,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -5556,7 +5556,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5579,13 +5579,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -5810,7 +5810,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5829,13 +5829,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -5949,7 +5949,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5972,13 +5972,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -6135,7 +6135,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -6158,13 +6158,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -6366,7 +6366,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -6389,13 +6389,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -6501,7 +6501,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -6524,13 +6524,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -6985,7 +6985,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -7008,7 +7008,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -7246,7 +7246,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -7265,13 +7265,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -7449,7 +7449,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -7472,13 +7472,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -7666,7 +7666,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -7689,13 +7689,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -7801,7 +7801,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -7824,13 +7824,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -8245,7 +8245,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -8268,13 +8268,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 0 type: number @@ -8509,7 +8509,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -8528,13 +8528,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -8644,7 +8644,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -8667,13 +8667,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -8812,7 +8812,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -8835,13 +8835,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -8999,7 +8999,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -9022,13 +9022,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -9134,7 +9134,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -9157,13 +9157,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -9515,7 +9515,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -9534,13 +9534,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -9925,7 +9925,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -9948,7 +9948,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -10440,7 +10440,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -10463,7 +10463,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -10636,7 +10636,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -10659,13 +10659,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -11135,7 +11135,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -11158,7 +11158,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -11402,7 +11402,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -11423,13 +11423,13 @@ paths: - path required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_string validation: date message: Invalid date @@ -11624,7 +11624,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -11647,13 +11647,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -11861,7 +11861,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -11884,13 +11884,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -11996,7 +11996,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -12019,13 +12019,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -12202,7 +12202,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -12225,13 +12225,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -13027,7 +13027,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -13050,7 +13050,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -13388,7 +13388,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -13409,13 +13409,13 @@ paths: - path required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_string validation: date message: Invalid date @@ -13657,7 +13657,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -13680,13 +13680,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -13967,7 +13967,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -13990,7 +13990,7 @@ paths: type: string required: - message - - details + - detail headers: {} '404': description: '' @@ -14079,7 +14079,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -14102,13 +14102,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -14333,7 +14333,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -14356,13 +14356,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string diff --git a/web/src/lib/components/trail/map_with_elevation_maplibre.svelte b/web/src/lib/components/trail/map_with_elevation_maplibre.svelte index 013e3158..6303f672 100644 --- a/web/src/lib/components/trail/map_with_elevation_maplibre.svelte +++ b/web/src/lib/components/trail/map_with_elevation_maplibre.svelte @@ -140,7 +140,7 @@ const r: GeoJSON[] = []; trails.forEach((t) => { - if (t.expand.gpx_data) { + if (t.expand?.gpx_data) { r.push(toGeoJson(t.expand.gpx_data) as GeoJSON); } else if (t.lat && t.lon) { r.push({ @@ -165,7 +165,7 @@ if (data[activeTrail] && showElevation) { epc?.setData( data[activeTrail]!, - trails.at(activeTrail)!.expand.waypoints, + trails.at(activeTrail)!.expand?.waypoints, ); epc?.showProfile(); } @@ -409,7 +409,7 @@ if (data[activeTrail] && showElevation) { epc?.setData( data[activeTrail]!, - trails.at(activeTrail)!.expand.waypoints, + trails.at(activeTrail)!.expand?.waypoints, ); epc?.showProfile(); } @@ -490,7 +490,7 @@ if (!map) { return; } - for (const waypoint of trails[activeTrail]?.expand.waypoints ?? []) { + for (const waypoint of trails[activeTrail]?.expand?.waypoints ?? []) { const marker = createMarkerFromWaypoint(waypoint, onMarkerDragEnd); marker.addTo(map); markers.push(marker); diff --git a/web/src/lib/models/api/base_schema.ts b/web/src/lib/models/api/base_schema.ts index 3af4b5b8..845c5081 100644 --- a/web/src/lib/models/api/base_schema.ts +++ b/web/src/lib/models/api/base_schema.ts @@ -7,7 +7,7 @@ const RecordOptionsSchema = z.object({ }) const RecordListOptionsSchema = RecordOptionsSchema.extend({ - page: z.number({ coerce: true }).int().positive().optional(), + page: z.number({ coerce: true }).int().nonnegative().optional(), perPage: z.number({ coerce: true }).int().optional(), sort: z.string().optional(), filter: z.string().optional(), diff --git a/web/src/routes/map/+page.svelte b/web/src/routes/map/+page.svelte index d2f528ac..df9934ab 100644 --- a/web/src/routes/map/+page.svelte +++ b/web/src/routes/map/+page.svelte @@ -132,6 +132,15 @@ function handleMapInit() { if ( + $page.url.searchParams.has("lat") && + $page.url.searchParams.has("lon") + ) { + const lat = $page.url.searchParams.get("lat"); + const lon = $page.url.searchParams.get("lon"); + map.setCenter([parseFloat(lon!), parseFloat(lat!)]); + map.setZoom(14); + console.log("Set map center to: " + lat + " " + lon); + } else if ( $page.url.searchParams.has("tl_lat") && $page.url.searchParams.has("tl_lon") && $page.url.searchParams.has("br_lat") && @@ -148,14 +157,6 @@ ], ]; map.fitBounds(boundingBox, { animate: false }); - } else if ( - $page.url.searchParams.has("lat") && - $page.url.searchParams.has("lon") - ) { - const lat = $page.url.searchParams.get("lat"); - const lon = $page.url.searchParams.get("lon"); - map.setCenter([parseFloat(lon!), parseFloat(lat!)]); - map.setZoom(14); } else if (settings && settings.mapFocus == "trails") { const boundingBox: M.LngLatBoundsLike = [ [maxBoundingBox.min_lon, maxBoundingBox.max_lat], diff --git a/web/src/routes/profile/[id]/+layout.svelte b/web/src/routes/profile/[id]/+layout.svelte index 523a6fbb..d91cf6d9 100644 --- a/web/src/routes/profile/[id]/+layout.svelte +++ b/web/src/routes/profile/[id]/+layout.svelte @@ -17,7 +17,7 @@ text: $_("trail", { values: { n: 2 } }), value: `/profile/${$page.params.id}/trails`, }, - { text: $_('statistics'), value: `/profile/${$page.params.id}/stats` }, + { text: $_("statistics"), value: `/profile/${$page.params.id}/stats` }, ]; $: activeIndex = profileLinks.findIndex( @@ -80,14 +80,14 @@ href="/profile/{data.user.id}/users/followers" >

{data.followers}

-

{$_('followers')}

+

{$_("followers")}

{data.following}

-

{$_('following')}

+

{$_("following")}

{#if !data.isOwnProfile} @@ -113,6 +113,12 @@ > {/each} + {#if data.isOwnProfile} +
+ + {$_("settings")} +
+ {/if} diff --git a/web/src/routes/register/+page.svelte b/web/src/routes/register/+page.svelte index d17a20b0..bf9ea942 100644 --- a/web/src/routes/register/+page.svelte +++ b/web/src/routes/register/+page.svelte @@ -28,7 +28,7 @@ $_("must-be-at-least-n-characters-long", { values: { n: 3 }, }), - ), + ).regex(/^[\w][\w\.]*$/, $_("invalid-username")), email: z .string() .min(1, "required") diff --git a/web/static/docs/api/wanderer.openapi.yaml b/web/static/docs/api/wanderer.openapi.yaml index 12c8377f..90874e1e 100644 --- a/web/static/docs/api/wanderer.openapi.yaml +++ b/web/static/docs/api/wanderer.openapi.yaml @@ -325,7 +325,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -348,13 +348,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 0 type: number @@ -583,7 +583,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -602,13 +602,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: undefined @@ -794,7 +794,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -817,13 +817,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 0 type: number @@ -1232,7 +1232,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -1255,13 +1255,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 0 type: number @@ -1659,7 +1659,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -1678,13 +1678,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -1834,7 +1834,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -1857,13 +1857,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -2028,7 +2028,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -2051,13 +2051,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -2163,7 +2163,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -2186,13 +2186,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -2283,7 +2283,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -2306,13 +2306,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -2442,7 +2442,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -2465,13 +2465,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -2755,7 +2755,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -2778,13 +2778,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 0 type: number @@ -3060,7 +3060,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -3083,13 +3083,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -3192,7 +3192,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -3215,13 +3215,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -3379,7 +3379,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -3402,13 +3402,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -3514,7 +3514,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -3537,13 +3537,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -4044,7 +4044,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -4067,7 +4067,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -4449,7 +4449,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -4468,13 +4468,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -4577,7 +4577,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -4600,13 +4600,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -5006,7 +5006,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5029,7 +5029,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -5197,7 +5197,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5220,13 +5220,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -5421,7 +5421,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5444,13 +5444,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -5556,7 +5556,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5579,13 +5579,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -5810,7 +5810,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5829,13 +5829,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -5949,7 +5949,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -5972,13 +5972,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -6135,7 +6135,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -6158,13 +6158,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -6366,7 +6366,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -6389,13 +6389,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -6501,7 +6501,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -6524,13 +6524,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -6985,7 +6985,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -7008,7 +7008,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -7246,7 +7246,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -7265,13 +7265,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -7449,7 +7449,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -7472,13 +7472,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -7666,7 +7666,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -7689,13 +7689,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -7801,7 +7801,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -7824,13 +7824,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -8245,7 +8245,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -8268,13 +8268,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 0 type: number @@ -8509,7 +8509,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -8528,13 +8528,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -8644,7 +8644,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -8667,13 +8667,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -8812,7 +8812,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -8835,13 +8835,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -8999,7 +8999,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -9022,13 +9022,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -9134,7 +9134,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -9157,13 +9157,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -9515,7 +9515,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -9534,13 +9534,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_type expected: string received: number @@ -9925,7 +9925,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -9948,7 +9948,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -10440,7 +10440,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -10463,7 +10463,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -10636,7 +10636,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -10659,13 +10659,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -11135,7 +11135,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -11158,7 +11158,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -11402,7 +11402,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -11423,13 +11423,13 @@ paths: - path required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_string validation: date message: Invalid date @@ -11624,7 +11624,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -11647,13 +11647,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -11861,7 +11861,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -11884,13 +11884,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -11996,7 +11996,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -12019,13 +12019,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -12202,7 +12202,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -12225,13 +12225,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -13027,7 +13027,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -13050,7 +13050,7 @@ paths: type: string required: - message - - details + - detail headers: {} x-400:Invalid sort/expand/filter: description: '' @@ -13388,7 +13388,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -13409,13 +13409,13 @@ paths: - path required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: invalid_string validation: date message: Invalid date @@ -13657,7 +13657,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -13680,13 +13680,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -13967,7 +13967,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -13990,7 +13990,7 @@ paths: type: string required: - message - - details + - detail headers: {} '404': description: '' @@ -14079,7 +14079,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -14102,13 +14102,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string @@ -14333,7 +14333,7 @@ paths: properties: message: type: string - details: + detail: type: array items: type: object @@ -14356,13 +14356,13 @@ paths: type: string required: - message - - details + - detail examples: '1': summary: Exception value: message: invalid_params - details: + detail: - code: too_small minimum: 15 type: string diff --git a/web/static/imgs/logo_text_light.png b/web/static/imgs/logo_text_light.png new file mode 100644 index 0000000000000000000000000000000000000000..158cb37a815e5aed230c37945de97e13f4457699 GIT binary patch literal 3732 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