From ea3faba0562677d9b81532da78fbe8d4d2d9644e Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 24 Apr 2025 22:15:30 +0200 Subject: [PATCH] add comments --- api/openapi.json | 440 +++++++++++++++++- apps/app/src/lib/api/api.gen.ts | 306 +++++++++++- apps/db-adapter/integration-tests/comment.go | 94 ++++ .../integration-tests/payloads/comment.json | 5 + .../payloads/edit_comment.json | 5 + .../internal/api/auth/read_operations.go | 2 +- apps/db-adapter/internal/api/comment.go | 120 +++++ apps/db-adapter/internal/api/comment_test.go | 164 +++++++ apps/db-adapter/internal/api/person.go | 2 +- apps/db-adapter/internal/memgraph/comments.go | 66 +++ .../internal/memgraph/comments_test.go | 197 ++++++++ apps/db-adapter/internal/memgraph/queries.go | 19 + .../internal/memgraph/queries/comment.cypher | 13 + .../queries/comments_on_profile.cypher | 12 + .../memgraph/queries/delete_comment.cypher | 3 + apps/db-adapter/main_test.go | 4 + apps/db-adapter/pkg/api/api.gen.go | 284 ++++++++++- 17 files changed, 1688 insertions(+), 48 deletions(-) create mode 100644 apps/db-adapter/integration-tests/comment.go create mode 100644 apps/db-adapter/integration-tests/payloads/comment.json create mode 100644 apps/db-adapter/integration-tests/payloads/edit_comment.json create mode 100644 apps/db-adapter/internal/api/comment.go create mode 100644 apps/db-adapter/internal/api/comment_test.go create mode 100644 apps/db-adapter/internal/memgraph/comments.go create mode 100644 apps/db-adapter/internal/memgraph/comments_test.go create mode 100644 apps/db-adapter/internal/memgraph/queries/comment.cypher create mode 100644 apps/db-adapter/internal/memgraph/queries/comments_on_profile.cypher create mode 100644 apps/db-adapter/internal/memgraph/queries/delete_comment.cypher diff --git a/api/openapi.json b/api/openapi.json index e6f3f5f..4b23686 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -218,6 +218,359 @@ } } }, + "/comment/{id}": { + "post": { + "summary": "Comment on person's profile by ID", + "operationId": "commentOnPerson", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "X-User-ID", + "in": "header", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "responses": { + "200": { + "description": "Message updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Messages" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + } + } + }, + "get": { + "summary": "Get comments on person's profile by ID", + "operationId": "GetCommentsOnPerson", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "X-User-ID", + "in": "header", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "responses": { + "200": { + "description": "Comments", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Messages" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + } + } + }, + "patch": { + "summary": "Edit comment on person's profile by ID", + "operationId": "editComment", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "X-User-ID", + "in": "header", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "responses": { + "200": { + "description": "Comment updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Messages" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + } + } + }, + "delete": { + "summary": "Comment on person's profile by ID", + "operationId": "deleteCommentOnPerson", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "X-User-ID", + "in": "header", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Deleted comment", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + } + } + } + } + } + }, "/person/{id}": { "patch": { "summary": "Update a person by ID", @@ -2235,31 +2588,6 @@ "type": "string", "nullable": true }, - "others_said": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "relationship": { - "type": "string" - }, - "description": { - "type": "string" - }, - "url": { - "type": "string", - "nullable": true - } - } - }, - "nullable": true - }, "limit": { "type": "integer", "nullable": true @@ -2887,6 +3215,68 @@ } } } + }, + "Comment":{ + "type": "object", + "properties": { + "id": { + "type": "integer", + "nullable": true + }, + "start": { + "type": "integer", + "nullable": true + }, + "end": { + "type": "integer", + "nullable": true + }, + "type": { + "type": "string", + "nullable": true + }, + "label": { + "type": "string", + "nullable": true + }, + "props": { + "$ref": "#/components/schemas/Message" + } + } + }, + "Message": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "edited": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "sent_at": { + "type": "string", + "format": "date-time" + } + } + }, + "Messages":{ + "type": "object", + "properties": { + "people":{ + "type": "array", + "items": { + "$ref": "#/components/schemas/OptimizedPersonNode" + } + }, + "comments":{ + "type": "array", + "items": { + "$ref": "#/components/schemas/Comment" + } + } + } } } } diff --git a/apps/app/src/lib/api/api.gen.ts b/apps/app/src/lib/api/api.gen.ts index dcbedb6..f3236c6 100644 --- a/apps/app/src/lib/api/api.gen.ts +++ b/apps/app/src/lib/api/api.gen.ts @@ -55,6 +55,26 @@ export interface paths { patch?: never; trace?: never; }; + "/comment/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get comments on person's profile by ID */ + get: operations["GetCommentsOnPerson"]; + put?: never; + /** Comment on person's profile by ID */ + post: operations["commentOnPerson"]; + /** Comment on person's profile by ID */ + delete: operations["deleteCommentOnPerson"]; + options?: never; + head?: never; + /** Edit comment on person's profile by ID */ + patch: operations["editComment"]; + trace?: never; + }; "/person/{id}": { parameters: { query?: never; @@ -185,7 +205,7 @@ export interface paths { path?: never; cookie?: never; }; - /** Get relationship between two persons */ + /** Get relationships between two persons */ get: operations["getRelationship"]; put?: never; post?: never; @@ -334,13 +354,6 @@ export interface components { }[] | null; occupations?: string[] | null; occupation_to_display?: string | null; - others_said?: { - id?: number; - name?: string; - relationship?: string; - description?: string; - url?: string | null; - }[] | null; limit?: number | null; photos?: { url?: string; @@ -443,6 +456,16 @@ export interface components { end?: number; properties?: components["schemas"]["FamilyRelationship"]; }; + dbtypeRelationship: { + Id?: number; + ElementId?: string; + Type?: string | null; + StartId?: number; + StartElementId?: string; + EndId?: number; + EndElementId?: string; + Props?: components["schemas"]["FamilyRelationship"]; + }; OptimizedPersonNode: { id?: number; labels?: string[]; @@ -512,6 +535,25 @@ export interface components { added?: number; }; }; + Comment: { + id?: number | null; + start?: number | null; + end?: number | null; + type?: string | null; + label?: string | null; + props?: components["schemas"]["Message"]; + }; + Message: { + message?: string; + /** Format: date-time */ + edited?: string | null; + /** Format: date-time */ + sent_at?: string; + }; + Messages: { + people?: components["schemas"]["OptimizedPersonNode"][]; + comments?: components["schemas"]["Comment"][]; + }; }; responses: never; parameters: never; @@ -649,6 +691,248 @@ export interface operations { }; }; }; + GetCommentsOnPerson: { + parameters: { + query?: never; + header: { + "X-User-ID": number; + }; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Message"]; + }; + }; + responses: { + /** @description Comments */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Messages"]; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + }; + }; + commentOnPerson: { + parameters: { + query?: never; + header: { + "X-User-ID": number; + }; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Message"]; + }; + }; + responses: { + /** @description Message updated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Messages"]; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + }; + }; + deleteCommentOnPerson: { + parameters: { + query?: never; + header: { + "X-User-ID": number; + }; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Deleted comment */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + }; + }; + editComment: { + parameters: { + query?: never; + header: { + "X-User-ID": number; + }; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Message"]; + }; + }; + responses: { + /** @description Comment updated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Messages"]; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + msg?: string; + }; + }; + }; + }; + }; getPersonById: { parameters: { query?: never; @@ -1156,13 +1440,13 @@ export interface operations { }; }; responses: { - /** @description Relationship created */ + /** @description Relationships created */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Relationship"]; + "application/json": components["schemas"]["dbtypeRelationship"][]; }; }; /** @description Unauthorized */ @@ -1322,8 +1606,6 @@ export interface operations { requestBody: { content: { "application/json": { - id1?: number; - id2?: number; relationship?: components["schemas"]["FamilyRelationship"]; }; }; diff --git a/apps/db-adapter/integration-tests/comment.go b/apps/db-adapter/integration-tests/comment.go new file mode 100644 index 0000000..2faf5fe --- /dev/null +++ b/apps/db-adapter/integration-tests/comment.go @@ -0,0 +1,94 @@ +package integration_tests + +import ( + "bytes" + _ "embed" + "encoding/json" + "io" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api" +) + +//go:embed payloads/comment.json +var comment []byte + +func CommentOnPersonTest(dbAdapterUri string, client *http.Client) func(t *testing.T) { + return func(t *testing.T) { + url := dbAdapterUri + "/comment/8" + + req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, url, bytes.NewBuffer(comment)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-User-ID", "6") + + resp, err := client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + } +} + +func GetCommentsOnPersonTest(dbAdapterUri string, client *http.Client) func(t *testing.T) { + return func(t *testing.T) { + url := dbAdapterUri + "/comment/8" + + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, url, http.NoBody) + require.NoError(t, err) + req.Header.Set("X-User-ID", "6") + + resp, err := client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Contains(t, string(body), "StartElementId") + } +} + +//go:embed payloads/edit_comment.json +var edit_comment []byte + +func PatchCommentOnPersonTest(dbAdapterUri string, client *http.Client) func(t *testing.T) { + return func(t *testing.T) { + patchURL := dbAdapterUri + "/comment/8" + + patchReq, err := http.NewRequestWithContext(t.Context(), http.MethodPatch, patchURL, bytes.NewBuffer(edit_comment)) + require.NoError(t, err) + patchReq.Header.Set("Content-Type", "application/json") + patchReq.Header.Set("X-User-ID", "6") + + patchResp, err := client.Do(patchReq) + require.NoError(t, err) + defer patchResp.Body.Close() + + require.Equal(t, http.StatusOK, patchResp.StatusCode) + + var responseBody api.Messages + err = json.NewDecoder(patchResp.Body).Decode(&responseBody) + require.NoError(t, err) + require.NotEmpty(t, responseBody.Comments) + require.Len(t, *responseBody.Comments, 1) + } +} + +func DeleteCommentOnPersonTest(dbAdapterUri string, client *http.Client) func(t *testing.T) { + return func(t *testing.T) { + deleteURL := dbAdapterUri + "/comment/8" + deleteReq, err := http.NewRequestWithContext(t.Context(), http.MethodDelete, deleteURL, http.NoBody) + require.NoError(t, err) + deleteReq.Header.Set("X-User-ID", "6") + + deleteResp, err := client.Do(deleteReq) + require.NoError(t, err) + defer deleteResp.Body.Close() + + require.Equal(t, http.StatusOK, deleteResp.StatusCode) + } +} diff --git a/apps/db-adapter/integration-tests/payloads/comment.json b/apps/db-adapter/integration-tests/payloads/comment.json new file mode 100644 index 0000000..4017074 --- /dev/null +++ b/apps/db-adapter/integration-tests/payloads/comment.json @@ -0,0 +1,5 @@ +{ + "message": "Test comment", + "created_at": "2023-10-01T12:00:00Z", + "updated_at": "2023-10-01T12:00:00Z" +} \ No newline at end of file diff --git a/apps/db-adapter/integration-tests/payloads/edit_comment.json b/apps/db-adapter/integration-tests/payloads/edit_comment.json new file mode 100644 index 0000000..ee72a78 --- /dev/null +++ b/apps/db-adapter/integration-tests/payloads/edit_comment.json @@ -0,0 +1,5 @@ +{ + "message": "Test comment **edited**", + "created_at": "2023-10-01T12:00:00Z", + "updated_at": "2023-11-01T12:00:00Z" +} \ No newline at end of file diff --git a/apps/db-adapter/internal/api/auth/read_operations.go b/apps/db-adapter/internal/api/auth/read_operations.go index 145432f..b4d3b44 100644 --- a/apps/db-adapter/internal/api/auth/read_operations.go +++ b/apps/db-adapter/internal/api/auth/read_operations.go @@ -14,7 +14,7 @@ func CouldSeePersonsProfile(ctx context.Context, session neo4j.SessionWithContex return nil } - res, err := session.ExecuteRead(ctx, memgraph.GetFamilyTreeById(ctx, xUserID)) + res, err := session.ExecuteRead(ctx, memgraph.GetFamilyTreeWithSpousesById(ctx, xUserID)) if err != nil { return err } diff --git a/apps/db-adapter/internal/api/comment.go b/apps/db-adapter/internal/api/comment.go new file mode 100644 index 0000000..5861529 --- /dev/null +++ b/apps/db-adapter/internal/api/comment.go @@ -0,0 +1,120 @@ +package api + +import ( + "context" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/api/auth" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/memgraph" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api" +) + +func (srv *server) CommentOnPerson(c *gin.Context, id int, params api.CommentOnPersonParams) { //nolint:dupl,lll // This just does not worth abstracting anymore + var comment api.Message + if err := c.ShouldBindJSON(&comment); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) + + return + } + + session := srv.createSessionWithTimeout(c.Request.Context()) + defer closeSession(c.Request.Context(), srv.logger, session, srv.dbOpTimeout) + + actx, acancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout) + defer acancel() + if err := auth.CouldSeePersonsProfile(actx, session, id, params.XUserID); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"msg": fmt.Sprint("User does not have access to this person", err.Error())}) + + return + } + + qctx, qCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout) + defer qCancel() + res, err := session.ExecuteWrite(qctx, memgraph.UpsertCommentOnProfile( + qctx, params.XUserID, id, &comment, + )) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()}) + + return + } + + c.JSON(http.StatusOK, res) +} + +func (srv *server) EditComment(c *gin.Context, id int, params api.EditCommentParams) { //nolint:dupl,lll // This just does not worth abstracting anymore + var comment api.Message + if err := c.ShouldBindJSON(&comment); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) + + return + } + + session := srv.createSessionWithTimeout(c.Request.Context()) + defer closeSession(c.Request.Context(), srv.logger, session, srv.dbOpTimeout) + + actx, acancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout) + defer acancel() + if err := auth.CouldSeePersonsProfile(actx, session, id, params.XUserID); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"msg": fmt.Sprint("User does not have access to this person", err.Error())}) + + return + } + + qctx, qCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout) + defer qCancel() + res, err := session.ExecuteWrite(qctx, memgraph.UpsertCommentOnProfile( + qctx, params.XUserID, id, &comment, + )) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()}) + + return + } + + c.JSON(http.StatusOK, res) +} + +func (srv *server) DeleteCommentOnPerson(c *gin.Context, id int, params api.DeleteCommentOnPersonParams) { + session := srv.createSessionWithTimeout(c.Request.Context()) + defer closeSession(c.Request.Context(), srv.logger, session, srv.dbOpTimeout) + + qctx, qCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout) + defer qCancel() + _, err := session.ExecuteWrite(qctx, memgraph.DeleteComment( + qctx, params.XUserID, id, + )) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()}) + + return + } + + c.JSON(http.StatusOK, gin.H{"msg": "Comment deleted successfully"}) +} + +func (srv *server) GetCommentsOnPerson(c *gin.Context, id int, params api.GetCommentsOnPersonParams) { //nolint:dupl,lll // This just does not worth abstracting anymore + session := srv.createSessionWithTimeout(c.Request.Context()) + defer closeSession(c.Request.Context(), srv.logger, session, srv.dbOpTimeout) + + actx, acancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout) + defer acancel() + if err := auth.CouldSeePersonsProfile(actx, session, id, params.XUserID); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"msg": fmt.Sprint("User does not have access to this person", err.Error())}) + + return + } + + qctx, qCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout) + defer qCancel() + res, err := session.ExecuteRead(qctx, memgraph.GetCommentsOnProfile(qctx, id)) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()}) + + return + } + + c.JSON(http.StatusOK, res) +} diff --git a/apps/db-adapter/internal/api/comment_test.go b/apps/db-adapter/internal/api/comment_test.go new file mode 100644 index 0000000..f89c537 --- /dev/null +++ b/apps/db-adapter/internal/api/comment_test.go @@ -0,0 +1,164 @@ +package api + +import ( + "bytes" + "context" + "errors" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + memgraphMock "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/memgraph/mock" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api" +) + +func mockServer(writeErr, authErr error, writeResult any) *server { + mockSession := new(memgraphMock.SessionWithContext) + mockDriver := new(memgraphMock.DriverWithContext) + mockDriver.On("NewSession", mock.Anything, mock.Anything).Return(mockSession) + + if authErr != nil { + mockSession.On("ExecuteRead", mock.Anything, mock.Anything, mock.Anything).Return(nil, authErr) + } else { + mockSession.On("ExecuteRead", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) + } + + if writeErr != nil { + mockSession.On("ExecuteWrite", mock.Anything, mock.Anything, mock.Anything).Return(nil, writeErr) + } else { + mockSession.On("ExecuteWrite", mock.Anything, mock.Anything, mock.Anything).Return(writeResult, nil) + } + + mockSession.On("Close", mock.Anything).Return(nil) + + return &server{ + db: mockDriver, + dbOpTimeout: 2 * time.Second, + } +} + +func requestWithBody(method, url, body string) (*gin.Context, *httptest.ResponseRecorder) { //nolint:unparam // could be fixed in the future + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequestWithContext(context.Background(), method, url, bytes.NewBufferString(body)) + c.Request.Header.Set("Content-Type", "application/json") + return c, w +} + +func TestCommentOnPerson(t *testing.T) { + gin.SetMode(gin.TestMode) + + t.Run("Success", func(t *testing.T) { + srv := mockServer(nil, nil, map[string]any{"ok": true}) + c, w := requestWithBody(http.MethodPost, "/comment", `{"text": "Hello"}`) + params := api.CommentOnPersonParams{XUserID: 1} + + srv.CommentOnPerson(c, 123, params) + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "ok") + }) + + t.Run("Unauthorized", func(t *testing.T) { + srv := mockServer(nil, errors.New("unauthorized"), nil) + c, w := requestWithBody(http.MethodPost, "/comment", `{"text": "Hi"}`) + params := api.CommentOnPersonParams{XUserID: 1} + + srv.CommentOnPerson(c, 456, params) + assert.Equal(t, http.StatusUnauthorized, w.Code) + assert.Contains(t, w.Body.String(), "unauthorized") + }) + + t.Run("Database error", func(t *testing.T) { + srv := mockServer(errors.New("db error"), nil, nil) + c, w := requestWithBody(http.MethodPost, "/comment", `{"text": "Oops"}`) + params := api.CommentOnPersonParams{XUserID: 2} + + srv.CommentOnPerson(c, 789, params) + assert.Equal(t, http.StatusInternalServerError, w.Code) + assert.Contains(t, w.Body.String(), "db error") + }) +} + +func TestEditComment(t *testing.T) { + gin.SetMode(gin.TestMode) + + t.Run("Success", func(t *testing.T) { + srv := mockServer(nil, nil, map[string]any{"updated": true}) + c, w := requestWithBody(http.MethodPut, "/comment", `{"text": "Updated text"}`) + params := api.EditCommentParams{XUserID: 4} + + srv.EditComment(c, 101, params) + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "updated") + }) +} + +func TestDeleteCommentOnPerson(t *testing.T) { + t.Run("Success", func(t *testing.T) { + srv := mockServer(nil, nil, nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodDelete, "/comment", http.NoBody) + + params := api.DeleteCommentOnPersonParams{XUserID: 5} + srv.DeleteCommentOnPerson(c, 202, params) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "Comment deleted") + }) + + t.Run("DB error", func(t *testing.T) { + srv := mockServer(errors.New("delete error"), nil, nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodDelete, "/comment", http.NoBody) + + params := api.DeleteCommentOnPersonParams{XUserID: 6} + srv.DeleteCommentOnPerson(c, 303, params) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + assert.Contains(t, w.Body.String(), "delete error") + }) +} + +func TestGetCommentsOnPerson(t *testing.T) { + t.Run("Success", func(t *testing.T) { + mockSession := new(memgraphMock.SessionWithContext) + mockDriver := new(memgraphMock.DriverWithContext) + mockDriver.On("NewSession", mock.Anything, mock.Anything).Return(mockSession) + mockSession.On("ExecuteRead", mock.Anything, mock.Anything, mock.Anything).Return([]string{"Comment 1"}, nil) + mockSession.On("Close", mock.Anything).Return(nil) + + srv := &server{ + db: mockDriver, + dbOpTimeout: 2 * time.Second, + } + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/comment", http.NoBody) + + params := api.GetCommentsOnPersonParams{XUserID: 7} + srv.GetCommentsOnPerson(c, 404, params) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "Comment 1") + }) + + t.Run("Unauthorized", func(t *testing.T) { + srv := mockServer(nil, errors.New("access denied"), nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/comment", http.NoBody) + + params := api.GetCommentsOnPersonParams{XUserID: 8} + srv.GetCommentsOnPerson(c, 505, params) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + assert.Contains(t, w.Body.String(), "access denied") + }) +} diff --git a/apps/db-adapter/internal/api/person.go b/apps/db-adapter/internal/api/person.go index f54b1d0..b1230b9 100644 --- a/apps/db-adapter/internal/api/person.go +++ b/apps/db-adapter/internal/api/person.go @@ -87,7 +87,7 @@ func (srv *server) CreatePerson(c *gin.Context, params api.CreatePersonParams) { c.JSON(http.StatusOK, createdPerson) } -func (srv *server) GetPersonById(c *gin.Context, id int, params api.GetPersonByIdParams) { +func (srv *server) GetPersonById(c *gin.Context, id int, params api.GetPersonByIdParams) { //nolint:dupl,lll // This just does not worth abstracting anymore session := srv.createSessionWithTimeout(c.Request.Context()) defer closeSession(c.Request.Context(), srv.logger, session, srv.dbOpTimeout) diff --git a/apps/db-adapter/internal/memgraph/comments.go b/apps/db-adapter/internal/memgraph/comments.go new file mode 100644 index 0000000..4fae663 --- /dev/null +++ b/apps/db-adapter/internal/memgraph/comments.go @@ -0,0 +1,66 @@ +package memgraph + +import ( + "context" + "fmt" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api" +) + +func UpsertCommentOnProfile(ctx context.Context, commenter, profile int, comment *api.Message) neo4j.ManagedTransactionWork { + convertedComment := StructToMap(comment) + return func(tx neo4j.ManagedTransaction) (any, error) { + result, err := tx.Run(ctx, CommentCypherQuery, map[string]any{ + "id1": commenter, + "id2": profile, + "comment": convertedComment, + }) + if err != nil { + return nil, err + } + + record, err := result.Single(ctx) + if err != nil { + return nil, err + } + + return record.AsMap(), nil + } +} + +func GetCommentsOnProfile(ctx context.Context, profile int) neo4j.ManagedTransactionWork { + return func(tx neo4j.ManagedTransaction) (any, error) { + result, err := tx.Run(ctx, CommentsOnProfileCypherQuery, map[string]any{ + "id": profile, + }) + if err != nil { + return nil, err + } + + record, err := result.Single(ctx) + if err != nil { + return nil, err + } + + return record.AsMap(), nil + } +} + +func DeleteComment(ctx context.Context, commenter, profile int) neo4j.ManagedTransactionWork { + return func(tx neo4j.ManagedTransaction) (any, error) { + result, err := tx.Run(ctx, DeleteCommentCypherQuery, map[string]any{ + "id1": commenter, + "id2": profile, + }) + if err != nil { + return nil, err + } + + if result.Peek(ctx) { + return nil, fmt.Errorf("there was a returned value, when deleting admin but there should be none") + } + + return nil, nil + } +} diff --git a/apps/db-adapter/internal/memgraph/comments_test.go b/apps/db-adapter/internal/memgraph/comments_test.go new file mode 100644 index 0000000..2d1efa1 --- /dev/null +++ b/apps/db-adapter/internal/memgraph/comments_test.go @@ -0,0 +1,197 @@ +package memgraph + +import ( + "context" + "errors" + "testing" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + mmock "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/memgraph/mock" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api" +) + +func TestUpsertCommentOnProfile(t *testing.T) { + ctx := context.Background() + comment := &api.Message{Message: api.StringPtr("Hello!")} + commentMap := StructToMap(comment) + + testCases := []struct { + expectedResult map[string]any + mockTxSetup func() *mock.Transaction + expectedError error + name string + }{ + { + name: "Successful case", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockRecord := &neo4j.Record{Values: []any{"val"}, Keys: []string{"out"}} + mockResult.On("Single", ctx).Return(mockRecord, nil) + mockTx.On("Run", ctx, CommentCypherQuery, map[string]any{"id1": 1, "id2": 2, "comment": commentMap}).Return(mockResult, nil) + return mockTx + }, + expectedResult: map[string]any{"out": "val"}, + expectedError: nil, + }, + { + name: "Run error", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockTx.On("Run", ctx, CommentCypherQuery, mmock.Anything).Return(nil, errors.New("run error")) + return mockTx + }, + expectedResult: nil, + expectedError: errors.New("run error"), + }, + { + name: "Single error", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockResult.On("Single", ctx).Return(nil, errors.New("single error")) + mockTx.On("Run", ctx, CommentCypherQuery, mmock.Anything).Return(mockResult, nil) + return mockTx + }, + expectedResult: nil, + expectedError: errors.New("single error"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + work := UpsertCommentOnProfile(ctx, 1, 2, comment) + result, err := work(tc.mockTxSetup()) + + if tc.expectedError != nil { + require.Error(t, err) + require.Nil(t, result) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedResult, result) + } + }) + } +} + +func TestGetCommentsOnProfile(t *testing.T) { + ctx := context.Background() + + testCases := []struct { + expectedResult map[string]any + mockTxSetup func() *mock.Transaction + expectedError error + name string + }{ + { + name: "Successful case", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockRecord := &neo4j.Record{Values: []any{"some"}, Keys: []string{"comments"}} + mockResult.On("Single", ctx).Return(mockRecord, nil) + mockTx.On("Run", ctx, CommentsOnProfileCypherQuery, map[string]any{"id": 2}).Return(mockResult, nil) + return mockTx + }, + expectedResult: map[string]any{"comments": "some"}, + expectedError: nil, + }, + { + name: "Run error", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockTx.On("Run", ctx, CommentsOnProfileCypherQuery, mmock.Anything).Return(nil, errors.New("run error")) + return mockTx + }, + expectedResult: nil, + expectedError: errors.New("run error"), + }, + { + name: "Single error", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockResult.On("Single", ctx).Return(nil, errors.New("single error")) + mockTx.On("Run", ctx, CommentsOnProfileCypherQuery, mmock.Anything).Return(mockResult, nil) + return mockTx + }, + expectedResult: nil, + expectedError: errors.New("single error"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + work := GetCommentsOnProfile(ctx, 2) + result, err := work(tc.mockTxSetup()) + + if tc.expectedError != nil { + require.Error(t, err) + require.Nil(t, result) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedResult, result) + } + }) + } +} + +func TestDeleteComment(t *testing.T) { + ctx := context.Background() + + testCases := []struct { + mockTxSetup func() *mock.Transaction + expectedError error + name string + }{ + { + name: "Successful deletion", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockResult.On("Peek", ctx).Return(false) + mockTx.On("Run", ctx, DeleteCommentCypherQuery, map[string]any{"id1": 1, "id2": 2}).Return(mockResult, nil) + return mockTx + }, + expectedError: nil, + }, + { + name: "Run error", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockTx.On("Run", ctx, DeleteCommentCypherQuery, mmock.Anything).Return(nil, errors.New("run error")) + return mockTx + }, + expectedError: errors.New("run error"), + }, + { + name: "Peek unexpected return", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockResult.On("Peek", ctx).Return(true) + mockTx.On("Run", ctx, DeleteCommentCypherQuery, mmock.Anything).Return(mockResult, nil) + return mockTx + }, + expectedError: errors.New("there was a returned value, when deleting admin but there should be none"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + work := DeleteComment(ctx, 1, 2) + result, err := work(tc.mockTxSetup()) + + if tc.expectedError != nil { + require.Error(t, err) + require.Nil(t, result) + require.EqualError(t, err, tc.expectedError.Error()) + } else { + require.NoError(t, err) + require.Nil(t, result) + } + }) + } +} diff --git a/apps/db-adapter/internal/memgraph/queries.go b/apps/db-adapter/internal/memgraph/queries.go index 8a7573b..afe137d 100644 --- a/apps/db-adapter/internal/memgraph/queries.go +++ b/apps/db-adapter/internal/memgraph/queries.go @@ -148,3 +148,22 @@ var GetBloodRelativesCypherQuery string // //go:embed queries/get_family_tree_with_spouses.cypher var GetFamilyTreeWithSpousesCypherQuery string + +// Requires comment, id1 as commenter and id2 as profile that is commented on parameter. +// +// returns people, comments +// +//go:embed queries/comment.cypher +var CommentCypherQuery string + +// Requires id1 as commenter and id2 as profile that is commented on parameter. +// +//go:embed queries/delete_comment.cypher +var DeleteCommentCypherQuery string + +// Requires id1 as profile that is commented on parameter. +// +// returns comments, people +// +//go:embed queries/comments_on_profile.cypher +var CommentsOnProfileCypherQuery string diff --git a/apps/db-adapter/internal/memgraph/queries/comment.cypher b/apps/db-adapter/internal/memgraph/queries/comment.cypher new file mode 100644 index 0000000..cf80054 --- /dev/null +++ b/apps/db-adapter/internal/memgraph/queries/comment.cypher @@ -0,0 +1,13 @@ +MATCH (a:Person), (b:Person) +WHERE id(a) = $id1 AND id(b) = $id2 +MERGE (a)-[r:Comment]->(b) +SET r += $comment +RETURN collect(r) as comments, collect({ + id: id(a), + first_name: a.first_name, + middle_name: a.middle_name, + last_name: a.last_name, + born: a.born, + died: a.died, + profile_picture: a.profile_picture +}) as people; \ No newline at end of file diff --git a/apps/db-adapter/internal/memgraph/queries/comments_on_profile.cypher b/apps/db-adapter/internal/memgraph/queries/comments_on_profile.cypher new file mode 100644 index 0000000..91a6bd8 --- /dev/null +++ b/apps/db-adapter/internal/memgraph/queries/comments_on_profile.cypher @@ -0,0 +1,12 @@ +MATCH (b:Person) +WHERE id(b) = $id +MERGE (a)-[r:Comment]->(b) +RETURN collect(r) as comments, collect({ + id: id(a), + first_name: a.first_name, + middle_name: a.middle_name, + last_name: a.last_name, + born: a.born, + died: a.died, + profile_picture: a.profile_picture +}) as people; \ No newline at end of file diff --git a/apps/db-adapter/internal/memgraph/queries/delete_comment.cypher b/apps/db-adapter/internal/memgraph/queries/delete_comment.cypher new file mode 100644 index 0000000..780c565 --- /dev/null +++ b/apps/db-adapter/internal/memgraph/queries/delete_comment.cypher @@ -0,0 +1,3 @@ +MATCH (a)-[r:Comment]->(b) +WHERE id(a) = $id1 AND id(b) = $id2 +DELETE r; \ No newline at end of file diff --git a/apps/db-adapter/main_test.go b/apps/db-adapter/main_test.go index 6b57173..5cd376a 100644 --- a/apps/db-adapter/main_test.go +++ b/apps/db-adapter/main_test.go @@ -120,5 +120,9 @@ func IntegrationTestFlow(dbAdapterURI string) func(t *testing.T) { t.Run("GetFamilyTreeWithSpousesByIdTest", integration_tests.GetFamilyTreeWithSpousesByIdTest(dbAdapterURI, client)) t.Run("VerifyRelationships", integration_tests.UpdateRelationshipTest(dbAdapterURI, client)) t.Run("DeleteRelationship", integration_tests.DeleteRelationshipTest(dbAdapterURI, client)) + t.Run("CreateComment", integration_tests.CommentOnPersonTest(dbAdapterURI, client)) + t.Run("GetCommentsOnPerson", integration_tests.GetCommentsOnPersonTest(dbAdapterURI, client)) + t.Run("PatchCommentOnPerson", integration_tests.PatchCommentOnPersonTest(dbAdapterURI, client)) + t.Run("DeleteCommentOnPerson", integration_tests.DeleteCommentOnPersonTest(dbAdapterURI, client)) } } diff --git a/apps/db-adapter/pkg/api/api.gen.go b/apps/db-adapter/pkg/api/api.gen.go index 234dd12..c02be38 100644 --- a/apps/db-adapter/pkg/api/api.gen.go +++ b/apps/db-adapter/pkg/api/api.gen.go @@ -6,6 +6,7 @@ package api import ( "fmt" "net/http" + "time" "github.com/gin-gonic/gin" "github.com/oapi-codegen/runtime" @@ -41,6 +42,16 @@ type Admin struct { Type *string `json:"Type,omitempty"` } +// Comment defines model for Comment. +type Comment struct { + End *int `json:"end"` + Id *int `json:"id"` + Label *string `json:"label"` + Props *Message `json:"props,omitempty"` + Start *int `json:"start"` + Type *string `json:"type"` +} + // FamilyRelationship defines model for FamilyRelationship. type FamilyRelationship struct { From *openapi_types.Date `json:"from"` @@ -73,6 +84,19 @@ type LikesProperties struct { LikeIt *bool `json:"like_it"` } +// Message defines model for Message. +type Message struct { + Edited *time.Time `json:"edited"` + Message *string `json:"message,omitempty"` + SentAt *time.Time `json:"sent_at,omitempty"` +} + +// Messages defines model for Messages. +type Messages struct { + Comments *[]Comment `json:"comments,omitempty"` + People *[]OptimizedPersonNode `json:"people,omitempty"` +} + // OptimizedPersonNode defines model for OptimizedPersonNode. type OptimizedPersonNode struct { Born *openapi_types.Date `json:"born,omitempty"` @@ -150,15 +174,8 @@ type PersonProperties struct { } `json:"notes"` OccupationToDisplay *string `json:"occupation_to_display"` Occupations *[]string `json:"occupations"` - OthersSaid *[]struct { - Description *string `json:"description,omitempty"` - Id *int `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Relationship *string `json:"relationship,omitempty"` - Url *string `json:"url"` - } `json:"others_said"` - Phone *string `json:"phone"` - Photos *[]struct { + Phone *string `json:"phone"` + Photos *[]struct { Date *openapi_types.Date `json:"date,omitempty"` Description *string `json:"description,omitempty"` Name *string `json:"name,omitempty"` @@ -272,6 +289,26 @@ type CreateAdminRelationshipParams struct { XUserID int `json:"X-User-ID"` } +// DeleteCommentOnPersonParams defines parameters for DeleteCommentOnPerson. +type DeleteCommentOnPersonParams struct { + XUserID int `json:"X-User-ID"` +} + +// GetCommentsOnPersonParams defines parameters for GetCommentsOnPerson. +type GetCommentsOnPersonParams struct { + XUserID int `json:"X-User-ID"` +} + +// EditCommentParams defines parameters for EditComment. +type EditCommentParams struct { + XUserID int `json:"X-User-ID"` +} + +// CommentOnPersonParams defines parameters for CommentOnPerson. +type CommentOnPersonParams struct { + XUserID int `json:"X-User-ID"` +} + // GetFamilyTreeByIdParams defines parameters for GetFamilyTreeById. type GetFamilyTreeByIdParams struct { XUserID int `json:"X-User-ID"` @@ -408,6 +445,15 @@ type UpdateRelationshipParams struct { XUserID int `json:"X-User-ID"` } +// GetCommentsOnPersonJSONRequestBody defines body for GetCommentsOnPerson for application/json ContentType. +type GetCommentsOnPersonJSONRequestBody = Message + +// EditCommentJSONRequestBody defines body for EditComment for application/json ContentType. +type EditCommentJSONRequestBody = Message + +// CommentOnPersonJSONRequestBody defines body for CommentOnPerson for application/json ContentType. +type CommentOnPersonJSONRequestBody = Message + // CreatePersonJSONRequestBody defines body for CreatePerson for application/json ContentType. type CreatePersonJSONRequestBody = PersonRegistration @@ -449,6 +495,18 @@ type ServerInterface interface { // Create admin relationship between two persons // (POST /admin/{id1}/{id2}) CreateAdminRelationship(c *gin.Context, id1 int, id2 int, params CreateAdminRelationshipParams) + // Comment on person's profile by ID + // (DELETE /comment/{id}) + DeleteCommentOnPerson(c *gin.Context, id int, params DeleteCommentOnPersonParams) + // Get comments on person's profile by ID + // (GET /comment/{id}) + GetCommentsOnPerson(c *gin.Context, id int, params GetCommentsOnPersonParams) + // Edit comment on person's profile by ID + // (PATCH /comment/{id}) + EditComment(c *gin.Context, id int, params EditCommentParams) + // Comment on person's profile by ID + // (POST /comment/{id}) + CommentOnPerson(c *gin.Context, id int, params CommentOnPersonParams) // Get family tree by person ID // (GET /family-tree) GetFamilyTreeById(c *gin.Context, params GetFamilyTreeByIdParams) @@ -760,6 +818,210 @@ func (siw *ServerInterfaceWrapper) CreateAdminRelationship(c *gin.Context) { siw.Handler.CreateAdminRelationship(c, id1, id2, params) } +// DeleteCommentOnPerson operation middleware +func (siw *ServerInterfaceWrapper) DeleteCommentOnPerson(c *gin.Context) { + + var err error + + // ------------- Path parameter "id" ------------- + var id int + + err = runtime.BindStyledParameterWithOptions("simple", "id", c.Param("id"), &id, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %w", err), http.StatusBadRequest) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params DeleteCommentOnPersonParams + + headers := c.Request.Header + + // ------------- Required header parameter "X-User-ID" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("X-User-ID")]; found { + var XUserID int + n := len(valueList) + if n != 1 { + siw.ErrorHandler(c, fmt.Errorf("Expected one value for X-User-ID, got %d", n), http.StatusBadRequest) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "X-User-ID", valueList[0], &XUserID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter X-User-ID: %w", err), http.StatusBadRequest) + return + } + + params.XUserID = XUserID + + } else { + siw.ErrorHandler(c, fmt.Errorf("Header parameter X-User-ID is required, but not found"), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.DeleteCommentOnPerson(c, id, params) +} + +// GetCommentsOnPerson operation middleware +func (siw *ServerInterfaceWrapper) GetCommentsOnPerson(c *gin.Context) { + + var err error + + // ------------- Path parameter "id" ------------- + var id int + + err = runtime.BindStyledParameterWithOptions("simple", "id", c.Param("id"), &id, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %w", err), http.StatusBadRequest) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params GetCommentsOnPersonParams + + headers := c.Request.Header + + // ------------- Required header parameter "X-User-ID" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("X-User-ID")]; found { + var XUserID int + n := len(valueList) + if n != 1 { + siw.ErrorHandler(c, fmt.Errorf("Expected one value for X-User-ID, got %d", n), http.StatusBadRequest) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "X-User-ID", valueList[0], &XUserID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter X-User-ID: %w", err), http.StatusBadRequest) + return + } + + params.XUserID = XUserID + + } else { + siw.ErrorHandler(c, fmt.Errorf("Header parameter X-User-ID is required, but not found"), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.GetCommentsOnPerson(c, id, params) +} + +// EditComment operation middleware +func (siw *ServerInterfaceWrapper) EditComment(c *gin.Context) { + + var err error + + // ------------- Path parameter "id" ------------- + var id int + + err = runtime.BindStyledParameterWithOptions("simple", "id", c.Param("id"), &id, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %w", err), http.StatusBadRequest) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params EditCommentParams + + headers := c.Request.Header + + // ------------- Required header parameter "X-User-ID" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("X-User-ID")]; found { + var XUserID int + n := len(valueList) + if n != 1 { + siw.ErrorHandler(c, fmt.Errorf("Expected one value for X-User-ID, got %d", n), http.StatusBadRequest) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "X-User-ID", valueList[0], &XUserID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter X-User-ID: %w", err), http.StatusBadRequest) + return + } + + params.XUserID = XUserID + + } else { + siw.ErrorHandler(c, fmt.Errorf("Header parameter X-User-ID is required, but not found"), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.EditComment(c, id, params) +} + +// CommentOnPerson operation middleware +func (siw *ServerInterfaceWrapper) CommentOnPerson(c *gin.Context) { + + var err error + + // ------------- Path parameter "id" ------------- + var id int + + err = runtime.BindStyledParameterWithOptions("simple", "id", c.Param("id"), &id, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %w", err), http.StatusBadRequest) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params CommentOnPersonParams + + headers := c.Request.Header + + // ------------- Required header parameter "X-User-ID" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("X-User-ID")]; found { + var XUserID int + n := len(valueList) + if n != 1 { + siw.ErrorHandler(c, fmt.Errorf("Expected one value for X-User-ID, got %d", n), http.StatusBadRequest) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "X-User-ID", valueList[0], &XUserID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter X-User-ID: %w", err), http.StatusBadRequest) + return + } + + params.XUserID = XUserID + + } else { + siw.ErrorHandler(c, fmt.Errorf("Header parameter X-User-ID is required, but not found"), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.CommentOnPerson(c, id, params) +} + // GetFamilyTreeById operation middleware func (siw *ServerInterfaceWrapper) GetFamilyTreeById(c *gin.Context) { @@ -1842,6 +2104,10 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options router.DELETE(options.BaseURL+"/admin/:id1/:id2", wrapper.DeleteAdminRelationship) router.GET(options.BaseURL+"/admin/:id1/:id2", wrapper.GetAdminRelationship) router.POST(options.BaseURL+"/admin/:id1/:id2", wrapper.CreateAdminRelationship) + router.DELETE(options.BaseURL+"/comment/:id", wrapper.DeleteCommentOnPerson) + router.GET(options.BaseURL+"/comment/:id", wrapper.GetCommentsOnPerson) + router.PATCH(options.BaseURL+"/comment/:id", wrapper.EditComment) + router.POST(options.BaseURL+"/comment/:id", wrapper.CommentOnPerson) router.GET(options.BaseURL+"/family-tree", wrapper.GetFamilyTreeById) router.GET(options.BaseURL+"/family-tree-with-spouses", wrapper.GetFamilyTreeWithSpousesById) router.GET(options.BaseURL+"/health", wrapper.HealthCheck)