diff --git a/api/openapi.json b/api/openapi.json index 53c6e81..9ca1a6a 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -116,8 +116,11 @@ "person": { "$ref": "#/components/schemas/Person" }, - "relationship": { - "$ref": "#/components/schemas/Relationship" + "relationships": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Relationship" + } } } } @@ -169,14 +172,6 @@ "schema": { "type": "integer" } - }, - { - "name": "X-User-Name", - "in": "header", - "required": true, - "schema": { - "type": "string" - } } ], "requestBody": { @@ -2560,6 +2555,9 @@ "Id": { "type": "integer" }, + "ElementId": { + "type": "string" + }, "Labels": { "type": "array", "items": { @@ -2597,23 +2595,24 @@ "Relationship": { "type": "object", "properties": { - "Id": { + "id": { "type": "integer" }, - "Label": { - "type": "string" - }, - "Type": { + "type": { "type": "string", "nullable": true }, - "Start": { + "label":{ + "type": "string", + "nullable": true + }, + "start": { "type": "integer" }, - "End": { + "end": { "type": "integer" }, - "Props": { + "properties": { "$ref": "#/components/schemas/FamilyRelationship" } } @@ -2766,6 +2765,9 @@ "Id": { "type": "integer" }, + "ElementId":{ + "type": "string" + }, "Labels": { "type": "array", "items": { @@ -2783,13 +2785,19 @@ "Id": { "type": "integer" }, - "Label": { + "Type": { "type": "string" }, - "Start": { + "StartId": { + "type": "integer" + }, + "StartElementId": { "type": "string" }, - "End": { + "EndId": { + "type": "integer" + }, + "EndElementId": { "type": "string" }, "Props": { @@ -2820,13 +2828,19 @@ "Id": { "type": "integer" }, - "Label": { + "Type": { "type": "string" }, - "Start": { + "StartId": { + "type": "integer" + }, + "StartElementId": { "type": "string" }, - "End": { + "EndId": { + "type": "integer" + }, + "EndElementId": { "type": "string" }, "Props": { diff --git a/apps/db-adapter/integration-tests/payloads/create_relationship_child.json b/apps/db-adapter/integration-tests/payloads/create_relationship_child.json new file mode 100644 index 0000000..d317ed7 --- /dev/null +++ b/apps/db-adapter/integration-tests/payloads/create_relationship_child.json @@ -0,0 +1,10 @@ +{ + "id1": 2, + "id2": 3, + "type": "child", + "relationship": { + "verified": true, + "notes": "Test notes", + "from": "2022-01-01" + } +} \ No newline at end of file diff --git a/apps/db-adapter/integration-tests/person.go b/apps/db-adapter/integration-tests/person.go index 373dd23..c525dd5 100644 --- a/apps/db-adapter/integration-tests/person.go +++ b/apps/db-adapter/integration-tests/person.go @@ -46,12 +46,12 @@ func CreatePersonTest(dbAdapterUri string, client *http.Client) func(t *testing. func GetPersonById(dbAdapterUri string, client *http.Client) func(t *testing.T) { return func(t *testing.T) { - url := dbAdapterUri + "/person/0" + url := dbAdapterUri + "/person/2" req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, url, http.NoBody) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-User-ID", "0") + req.Header.Set("X-User-ID", "3") // Send the request resp, err := client.Do(req) @@ -68,8 +68,8 @@ func GetPersonById(dbAdapterUri string, client *http.Client) func(t *testing.T) _, ok := responseBody["Id"] require.True(t, ok) - require.Equal(t, "Alice", responseBody["Props"].(map[string]any)["first_name"]) - require.Equal(t, "Wonderland", responseBody["Props"].(map[string]any)["last_name"]) + require.Equal(t, "Johannes", responseBody["Props"].(map[string]any)["first_name"]) + require.Equal(t, "Doe", responseBody["Props"].(map[string]any)["last_name"]) } } diff --git a/apps/db-adapter/integration-tests/person_and_relationship.go b/apps/db-adapter/integration-tests/person_and_relationship.go index 7d221d8..bdf5eb2 100644 --- a/apps/db-adapter/integration-tests/person_and_relationship.go +++ b/apps/db-adapter/integration-tests/person_and_relationship.go @@ -7,6 +7,7 @@ import ( "net/http" "testing" + "github.com/neo4j/neo4j-go-driver/v5/neo4j/dbtype" "github.com/stretchr/testify/require" ) @@ -35,29 +36,28 @@ func CreatePersonAndRelationshipTest(dbAdapterUri string, payload *[]byte, clien return func(t *testing.T) { url := dbAdapterUri + "/person_and_relationship/1" - req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, url, bytes.NewBuffer(create_other_person)) + req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, url, bytes.NewBuffer(*payload)) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-User-ID", "0") - req.Header.Set("X-User-Name", "application/json") // Send the request resp, err := client.Do(req) require.NoError(t, err) defer resp.Body.Close() - var responseBody map[string]any + var responseBody struct { + Person dbtype.Node `json:"person"` + Relationships []dbtype.Relationship `json:"relationships"` + } + err = json.NewDecoder(resp.Body).Decode(&responseBody) require.NoError(t, err) // Validate the response require.Equal(t, http.StatusOK, resp.StatusCode) - _, ok := responseBody["Id"] - require.True(t, ok) - _, ok = responseBody["person"] - require.True(t, ok) - _, ok = responseBody["relationship"] - require.True(t, ok) + require.NotEmpty(t, responseBody.Person) + require.NotEmpty(t, responseBody.Relationships) } } diff --git a/apps/db-adapter/integration-tests/person_family_tree.go b/apps/db-adapter/integration-tests/person_family_tree.go index e1e8b26..0e93ae3 100644 --- a/apps/db-adapter/integration-tests/person_family_tree.go +++ b/apps/db-adapter/integration-tests/person_family_tree.go @@ -1,7 +1,69 @@ package integration_tests -func GetFamilyTreeById() { +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/stretchr/testify/require" +) + +func GetFamilyTreeByIdTest(dbAdapterUri string, client *http.Client) func(t *testing.T) { + return func(t *testing.T) { + url := dbAdapterUri + "/family-tree" + + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, url, http.NoBody) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-User-ID", "1") + + // Send the request + resp, err := client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + // Validate the response + require.Equal(t, http.StatusOK, resp.StatusCode) + + var responseBody struct { + People []any `json:"people"` + Relationships []any `json:"relationships"` + } + + err = json.NewDecoder(resp.Body).Decode(&responseBody) + require.NoError(t, err) + + require.Equal(t, 4, len(responseBody.People)) + require.Equal(t, 4, len(responseBody.Relationships)) + } } -func GetFamilyTreeWithSpousesById() { +func GetFamilyTreeWithSpousesByIdTest(dbAdapterUri string, client *http.Client) func(t *testing.T) { + return func(t *testing.T) { + url := dbAdapterUri + "/family-tree-with-spouses" + + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, url, http.NoBody) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-User-ID", "3") + + // Send the request + resp, err := client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + // Validate the response + require.Equal(t, http.StatusOK, resp.StatusCode) + + var responseBody struct { + People []any `json:"people"` + Relationships []any `json:"relationships"` + } + + err = json.NewDecoder(resp.Body).Decode(&responseBody) + require.NoError(t, err) + + require.Equal(t, 5, len(responseBody.People)) + require.Equal(t, 6, len(responseBody.Relationships)) + } } diff --git a/apps/db-adapter/integration-tests/person_google.go b/apps/db-adapter/integration-tests/person_google.go index a0acaac..98dd10c 100644 --- a/apps/db-adapter/integration-tests/person_google.go +++ b/apps/db-adapter/integration-tests/person_google.go @@ -71,7 +71,7 @@ func CreatePersonByGoogleIdAndInviteCodeTest(dbAdapterUri string, client *http.C require.NoError(t, err) // Validate the response - t.Log("Response Status Code: ", responseBody) + // t.Log("Response Status Code: ", responseBody) require.Equal(t, http.StatusOK, resp.StatusCode) _, ok := responseBody["Id"] diff --git a/apps/db-adapter/integration-tests/relationship.go b/apps/db-adapter/integration-tests/relationship.go index d669743..d6325ba 100644 --- a/apps/db-adapter/integration-tests/relationship.go +++ b/apps/db-adapter/integration-tests/relationship.go @@ -1,6 +1,6 @@ package integration_tests -func CreateRelationship() { +func CreateRelationshipTest() { } func UpdateRelationship() { diff --git a/apps/db-adapter/internal/api/auth/read_operations.go b/apps/db-adapter/internal/api/auth/read_operations.go index 4732c29..145432f 100644 --- a/apps/db-adapter/internal/api/auth/read_operations.go +++ b/apps/db-adapter/internal/api/auth/read_operations.go @@ -24,13 +24,19 @@ func CouldSeePersonsProfile(ctx context.Context, session neo4j.SessionWithContex return fmt.Errorf("could not convert result to map[string]any") } - people, ok := resMap["people"].([]api.OptimizedPersonNode) - if !ok { - return fmt.Errorf("could not convert people to []api.PersonProperties") + var uniqueIds []int64 + var flattenedPeople []any + if err := api.Flatten(resMap["people"], &uniqueIds, &flattenedPeople); err != nil { + return fmt.Errorf("could not convert people to []map[string]any: %w", err) } - for _, person := range people { - if *person.Id == userId { + for _, person := range flattenedPeople { + person, ok := person.(map[string]any) + if !ok { + return fmt.Errorf("could not convert person to map[string]any") + } + + if person["id"].(int64) == int64(userId) { return nil } } diff --git a/apps/db-adapter/internal/api/flatten_family_tree.go b/apps/db-adapter/internal/api/flatten_family_tree.go new file mode 100644 index 0000000..af46aee --- /dev/null +++ b/apps/db-adapter/internal/api/flatten_family_tree.go @@ -0,0 +1,29 @@ +package api + +import ( + "fmt" + + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api" +) + +type FamilyTree struct { + People []any `json:"people"` + Relationships []any `json:"relationships"` +} + +func FlattenFamilyTree(input any, result *FamilyTree) error { + root, ok := input.(map[string]any) + if !ok { + return fmt.Errorf("could not convert result to map[string]any") + + } + + var uniqueIds []int64 + err := api.Flatten(root["people"], &uniqueIds, &result.People) + if err != nil { + return err + } + + uniqueIds = []int64{} + return api.Flatten(root["relationships"], &uniqueIds, &result.Relationships) +} diff --git a/apps/db-adapter/internal/api/person_and_relationship.go b/apps/db-adapter/internal/api/person_and_relationship.go index 5ee1dfd..cb9cefc 100644 --- a/apps/db-adapter/internal/api/person_and_relationship.go +++ b/apps/db-adapter/internal/api/person_and_relationship.go @@ -126,13 +126,6 @@ func (srv *server) CreatePersonAndRelationship(c *gin.Context, id int, params ap return } - if err := trs.Commit(c.Request.Context()); err != nil { - srv.logger.Error("failed to commit transaction", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()}) - - return - } - relationshipsSingle, err := relationShipResultRaw.Single(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"msg": "no relationship was created" + err.Error()}) @@ -147,8 +140,15 @@ func (srv *server) CreatePersonAndRelationship(c *gin.Context, id int, params ap return } + if err := trs.Commit(c.Request.Context()); err != nil { + srv.logger.Error("failed to commit transaction", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()}) + + return + } + c.JSON(http.StatusOK, struct { Person any `json:"person"` - Rel any `json:"relationship"` - }{Person: singleRes, Rel: relationships}) + Rel any `json:"relationships"` + }{Person: createdPerson, Rel: relationships}) } diff --git a/apps/db-adapter/internal/api/person_family_tree.go b/apps/db-adapter/internal/api/person_family_tree.go index 7cd7fb5..91c54c8 100644 --- a/apps/db-adapter/internal/api/person_family_tree.go +++ b/apps/db-adapter/internal/api/person_family_tree.go @@ -20,7 +20,17 @@ func (srv *server) GetFamilyTreeById(c *gin.Context, params api.GetFamilyTreeByI return } - c.JSON(http.StatusOK, res) + results := FamilyTree{ + People: []any{}, + Relationships: []any{}, + } + err = FlattenFamilyTree(res, &results) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()}) + return + } + + c.JSON(http.StatusOK, results) } func (srv *server) GetFamilyTreeWithSpousesById( @@ -36,5 +46,15 @@ func (srv *server) GetFamilyTreeWithSpousesById( return } - c.JSON(http.StatusOK, res) + results := FamilyTree{ + People: []any{}, + Relationships: []any{}, + } + err = FlattenFamilyTree(res, &results) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()}) + return + } + + c.JSON(http.StatusOK, results) } diff --git a/apps/db-adapter/internal/api/person_test.go b/apps/db-adapter/internal/api/person_test.go index 0d8f815..d07b2fd 100644 --- a/apps/db-adapter/internal/api/person_test.go +++ b/apps/db-adapter/internal/api/person_test.go @@ -52,7 +52,7 @@ func TestCreatePerson(t *testing.T) { c.Request = httptest.NewRequestWithContext( t.Context(), http.MethodPost, "/person", io.NopCloser(strings.NewReader(body)), ) - params := api.CreatePersonParams{XUserID: 1, XUserName: "test"} + params := api.CreatePersonParams{XUserID: 1} srv.CreatePerson(c, params) diff --git a/apps/db-adapter/internal/memgraph/queries.go b/apps/db-adapter/internal/memgraph/queries.go index 80aa97d..8a7573b 100644 --- a/apps/db-adapter/internal/memgraph/queries.go +++ b/apps/db-adapter/internal/memgraph/queries.go @@ -69,6 +69,13 @@ var GetRelationshipCypherQuery string //go:embed queries/create_child_parent_relationships.cypher var CreateChildParentRelationshipCypherQuery string +// Requires childId, parentId parameters. +// +// returns relationships +// +//go:embed queries/create_sibling_relationships_based_on_parent.cypher +var CreateSiblingRelationshipsBasedOnParentCypherQuery string + // Requires id1, id2, Relationship1, Relationship1 parameters. // // return relationships diff --git a/apps/db-adapter/internal/memgraph/queries/get_blood_relations_by_id.cypher b/apps/db-adapter/internal/memgraph/queries/get_blood_relations_by_id.cypher index 30cde5b..bb14423 100644 --- a/apps/db-adapter/internal/memgraph/queries/get_blood_relations_by_id.cypher +++ b/apps/db-adapter/internal/memgraph/queries/get_blood_relations_by_id.cypher @@ -4,14 +4,14 @@ OPTIONAL MATCH (n)-[p:Parent*..]->(family:Person) OPTIONAL MATCH (family)-[c:Child*1..4]->(children:Person) OPTIONAL MATCH (family)-[s:Sibling]->(siblings:Person) OPTIONAL MATCH (n)-[ds:Sibling]->(direct_siblings:Person) -WITH collections.to_set(collect(n)+collect(family)+collect(children)+collect(direct_siblings)) as people, +WITH collections.to_set(collect(n)+collect(family)+collect(children)+collect(direct_siblings)+collect(siblings)) as people, collections.to_set(collect(c) + collect(p) + collect(s) + collect(ds)) as relationships UNWIND people as ppl RETURN collect({ id: id(ppl), first_name: ppl.first_name, middle_name: ppl.middle_name, - last_name: ppl.last_name, + last_name: ppl.last_name, born: ppl.born, died: ppl.died, profile_picture: ppl.profile_picture diff --git a/apps/db-adapter/internal/memgraph/queries/get_family_tree_with_spouses.cypher b/apps/db-adapter/internal/memgraph/queries/get_family_tree_with_spouses.cypher index f63c46e..20ee375 100644 --- a/apps/db-adapter/internal/memgraph/queries/get_family_tree_with_spouses.cypher +++ b/apps/db-adapter/internal/memgraph/queries/get_family_tree_with_spouses.cypher @@ -7,7 +7,7 @@ OPTIONAL MATCH (n)-[ds:Sibling]->(direct_siblings:Person) OPTIONAL MATCH (family)-[fsp:Spouse]->(fspouse:Person) OPTIONAL MATCH (children)-[csp:Spouse]->(cspouse:Person) OPTIONAL MATCH (n)-[sp:Spouse]->(spouse:Person) -WITH collections.to_set(collect(n) + collect(family) + collect(children) + collect(direct_siblings) + collect(fspouse) + collect(cspouse) + collect(spouse)) as people, +WITH collections.to_set(collect(n) + collect(family) + collect(children) + collect(direct_siblings) + collect(fspouse) + collect(cspouse) + collect(spouse) + collect(siblings)) as people, collections.to_set(collect(c) + collect(p) + collect(s) + collect(ds) + collect(fsp) + collect(csp) + collect(sp)) as relationships UNWIND people as ppl RETURN collect({ diff --git a/apps/db-adapter/internal/memgraph/relationship.go b/apps/db-adapter/internal/memgraph/relationship.go index dba0b23..3bc8ac0 100644 --- a/apps/db-adapter/internal/memgraph/relationship.go +++ b/apps/db-adapter/internal/memgraph/relationship.go @@ -4,6 +4,7 @@ import ( "context" "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/neo4j/neo4j-go-driver/v5/neo4j/dbtype" "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api" ) @@ -78,7 +79,40 @@ func CreateChildParentRelationship( return nil, err } - return result.Single(ctx) + siblingResult, err := tx.Run(ctx, CreateSiblingRelationshipsBasedOnParentCypherQuery, map[string]any{ + "childId": childId, + "parentId": parentId, + }) + if err != nil { + return nil, err + } + + relationship, err := result.Single(ctx) + if err != nil { + return nil, err + } + + relationshipsRaw, ok := relationship.Get("relationships") + if !ok { + return nil, err + } + + relationships, rok := relationshipsRaw.([]dbtype.Relationship) + if !rok { + return nil, err + } + + siblingRecord, err := siblingResult.Single(ctx) + if err != nil { + + siblingRelationship, ok := siblingRecord.Get("relationships") + siblings, ok := siblingRelationship.([]dbtype.Relationship) + if ok { + relationships = append(relationships, siblings...) + } + } + + return relationships, nil } } diff --git a/apps/db-adapter/internal/memgraph/struct_to_map.go b/apps/db-adapter/internal/memgraph/struct_to_map.go index 11d1d8f..dbe1835 100644 --- a/apps/db-adapter/internal/memgraph/struct_to_map.go +++ b/apps/db-adapter/internal/memgraph/struct_to_map.go @@ -5,6 +5,7 @@ import ( "time" "github.com/neo4j/neo4j-go-driver/v5/neo4j/dbtype" + openapi_types "github.com/oapi-codegen/runtime/types" ) // StructToMap recursively converts a struct to a map using JSON tags. @@ -70,7 +71,12 @@ func StructToMap(input any) map[string]any { // Recurse into nested structs switch val.Kind() { case reflect.Struct: - result[jsonKey] = StructToMap(val.Interface()) + switch val.Interface().(type) { + case openapi_types.Date: + result[jsonKey] = val.Interface().(openapi_types.Date).String() + default: + result[jsonKey] = StructToMap(val.Interface()) + } case reflect.Slice, reflect.Array: result[jsonKey] = processSlice(val) default: @@ -114,7 +120,12 @@ func processSlice(val reflect.Value) []any { if isPreservedType(item) { slice[i] = item } else if reflect.ValueOf(item).Kind() == reflect.Struct { - slice[i] = StructToMap(item) + switch item.(type) { + case openapi_types.Date: + slice[i] = item.(openapi_types.Date).String() + default: + slice[i] = StructToMap(item) + } } else { slice[i] = item } diff --git a/apps/db-adapter/main_test.go b/apps/db-adapter/main_test.go index 9d2d116..bb46cbd 100644 --- a/apps/db-adapter/main_test.go +++ b/apps/db-adapter/main_test.go @@ -110,9 +110,11 @@ func IntegrationTestFlow(dbAdapterURI string) func(t *testing.T) { t.Run("CreatePerson", integration_tests.CreatePersonTest(dbAdapterURI, client)) t.Run("UpdatePerson", integration_tests.UpdatePersonTest(dbAdapterURI, client)) t.Run("AddInviteCodeToPerson", integration_tests.UpdatePersonWithInviteCodeTest(dbAdapterURI, client)) - t.Run("GetPersonById", integration_tests.GetPersonById(dbAdapterURI, client)) t.Run("CreateFamilyTest", integration_tests.CreateAFamilyTest(dbAdapterURI, client)) t.Run("SoftDeletePerson", integration_tests.SoftDeletePersonTest(dbAdapterURI, client)) t.Run("HardDeletePerson", integration_tests.HardDeletePersonTest(dbAdapterURI, client)) + t.Run("GetPersonById", integration_tests.GetPersonById(dbAdapterURI, client)) + t.Run("GetFamilyTreeByIdTest", integration_tests.GetFamilyTreeByIdTest(dbAdapterURI, client)) + t.Run("GetFamilyTreeWithSpousesByIdTest", integration_tests.GetFamilyTreeWithSpousesByIdTest(dbAdapterURI, client)) } } diff --git a/apps/db-adapter/manual-testing/create_person_and_relationship_child.bru b/apps/db-adapter/manual-testing/create_person_and_relationship_child.bru new file mode 100644 index 0000000..7aa61ea --- /dev/null +++ b/apps/db-adapter/manual-testing/create_person_and_relationship_child.bru @@ -0,0 +1,40 @@ +meta { + name: create_person_and_relationship_child + type: http + seq: 10 +} + +post { + url: http://localhost:8080/person_and_relationship/{{id}} + body: json + auth: inherit +} + +headers { + Content-Type: application/json + X-User-Id: 2 +} + +body:json { + { + "person": { + "first_name": "Johannes", + "last_name": "Doe", + "born": "2000-07-01", + "limit": 1000, + "mothers_first_name": "Frances", + "mothers_last_name": "Soft", + "email": "dj@example.com" + }, + "type": "child", + "relationship": { + "verified": true, + "notes": "Test notes", + "from": "2023-01-01" + } + } +} + +vars:pre-request { + id: 2 +} diff --git a/apps/db-adapter/manual-testing/create_person_and_relationship_parent.bru b/apps/db-adapter/manual-testing/create_person_and_relationship_parent.bru new file mode 100644 index 0000000..c9bad8e --- /dev/null +++ b/apps/db-adapter/manual-testing/create_person_and_relationship_parent.bru @@ -0,0 +1,38 @@ +meta { + name: create_person_and_relationship_parent + type: http + seq: 11 +} + +post { + url: http://localhost:8080/person_and_relationship/{{id}} + body: json + auth: inherit +} + +headers { + Content-Type: application/json + X-User-Id: 2 +} + +body:json { + { + "person": { + "first_name": "Ferdinand", + "last_name": "Fritz", + "born": "1940-06-01", + "limit": 1000, + "mothers_first_name": "Feras", + "mothers_last_name": "Frea", + "email": "FFd@example.com" + }, + "type": "parent", + "relationship": { + "verified": true + } + } +} + +vars:pre-request { + id: 2 +} diff --git a/apps/db-adapter/manual-testing/create_person_and_relationship_sibling.bru b/apps/db-adapter/manual-testing/create_person_and_relationship_sibling.bru new file mode 100644 index 0000000..2bed8c8 --- /dev/null +++ b/apps/db-adapter/manual-testing/create_person_and_relationship_sibling.bru @@ -0,0 +1,39 @@ +meta { + name: create_person_and_relationship_sibling + type: http + seq: 12 +} + +post { + url: http://localhost:8080/person_and_relationship/{{id}} + body: json + auth: inherit +} + +headers { + Content-Type: application/json + X-User-Id: 2 +} + +body:json { + { + "person": { + "first_name": "Sandra", + "last_name": "Doe", + "born": "1987-07-01", + "limit": 1000, + "mothers_first_name": "Mary", + "mothers_last_name": "Smith", + "email": "SD@example.com" + }, + "type": "sibling", + "relationship": { + "verified": true, + "notes": "Good siblings" + } + } +} + +vars:pre-request { + id: 2 +} diff --git a/apps/db-adapter/manual-testing/get_person.bru b/apps/db-adapter/manual-testing/get_person.bru new file mode 100644 index 0000000..95f3ff1 --- /dev/null +++ b/apps/db-adapter/manual-testing/get_person.bru @@ -0,0 +1,19 @@ +meta { + name: get_person + type: http + seq: 14 +} + +get { + url: http://localhost:8080/person/{{id}} + body: none + auth: inherit +} + +headers { + X-User-Id: 9 +} + +vars:pre-request { + id: 2 +} diff --git a/apps/db-adapter/manual-testing/person_family_tree.bru b/apps/db-adapter/manual-testing/person_family_tree.bru new file mode 100644 index 0000000..a7fdd3b --- /dev/null +++ b/apps/db-adapter/manual-testing/person_family_tree.bru @@ -0,0 +1,29 @@ +meta { + name: person_family_tree + type: http + seq: 13 +} + +post { + url: http://localhost:8080/person + body: json + auth: inherit +} + +headers { + X-User-Id: 2 + X-User-Name: Alice Wonderland + Content-Type: application/json +} + +body:json { + { + "first_name": "Jhon", + "last_name": "Doe", + "born": "1985-07-01", + "limit": 1000, + "mothers_first_name": "Mary", + "mothers_last_name": "Smith", + "email": "jd@example.com" + } +} diff --git a/apps/db-adapter/manual-testing/person_family_tree_with_spouses.bru b/apps/db-adapter/manual-testing/person_family_tree_with_spouses.bru new file mode 100644 index 0000000..03938d2 --- /dev/null +++ b/apps/db-adapter/manual-testing/person_family_tree_with_spouses.bru @@ -0,0 +1,15 @@ +meta { + name: person_family_tree_with_spouses + type: http + seq: 15 +} + +get { + url: http://localhost:8080/family-tree + body: none + auth: inherit +} + +headers { + X-User-Id: 2 +} diff --git a/apps/db-adapter/pkg/api/api.gen.go b/apps/db-adapter/pkg/api/api.gen.go index 7cb9210..f616601 100644 --- a/apps/db-adapter/pkg/api/api.gen.go +++ b/apps/db-adapter/pkg/api/api.gen.go @@ -30,13 +30,15 @@ const ( // Admin defines model for Admin. type Admin struct { - End *string `json:"End,omitempty"` - Id *int `json:"Id,omitempty"` - Label *string `json:"Label,omitempty"` - Props *struct { + EndElementId *string `json:"EndElementId,omitempty"` + EndId *int `json:"EndId,omitempty"` + Id *int `json:"Id,omitempty"` + Props *struct { Added *int `json:"added,omitempty"` } `json:"Props,omitempty"` - Start *string `json:"Start,omitempty"` + StartElementId *string `json:"StartElementId,omitempty"` + StartId *int `json:"StartId,omitempty"` + Type *string `json:"Type,omitempty"` } // FamilyRelationship defines model for FamilyRelationship. @@ -55,11 +57,13 @@ type FamilyTree struct { // Likes defines model for Likes. type Likes struct { - End *string `json:"End,omitempty"` - Id *int `json:"Id,omitempty"` - Label *string `json:"Label,omitempty"` - Props *LikesProperties `json:"Props,omitempty"` - Start *string `json:"Start,omitempty"` + EndElementId *string `json:"EndElementId,omitempty"` + EndId *int `json:"EndId,omitempty"` + Id *int `json:"Id,omitempty"` + Props *LikesProperties `json:"Props,omitempty"` + StartElementId *string `json:"StartElementId,omitempty"` + StartId *int `json:"StartId,omitempty"` + Type *string `json:"Type,omitempty"` } // LikesProperties defines model for LikesProperties. @@ -84,9 +88,10 @@ type OptimizedPersonNode struct { // Person defines model for Person. type Person struct { - Id *int `json:"Id,omitempty"` - Labels *[]string `json:"Labels,omitempty"` - Props *PersonProperties `json:"Props,omitempty"` + ElementId *string `json:"ElementId,omitempty"` + Id *int `json:"Id,omitempty"` + Labels *[]string `json:"Labels,omitempty"` + Props *PersonProperties `json:"Props,omitempty"` } // PersonProperties defines model for PersonProperties. @@ -197,9 +202,10 @@ type PersonRegistration struct { // Recipe defines model for Recipe. type Recipe struct { - Id *int `json:"Id,omitempty"` - Labels *[]string `json:"Labels,omitempty"` - Props *RecipeProperties `json:"Props,omitempty"` + ElementId *string `json:"ElementId,omitempty"` + Id *int `json:"Id,omitempty"` + Labels *[]string `json:"Labels,omitempty"` + Props *RecipeProperties `json:"Props,omitempty"` } // RecipeProperties defines model for RecipeProperties. @@ -226,12 +232,12 @@ type RecipeProperties struct { // Relationship defines model for Relationship. type Relationship struct { - End *int `json:"End,omitempty"` - Id *int `json:"Id,omitempty"` - Label *string `json:"Label,omitempty"` - Props *FamilyRelationship `json:"Props,omitempty"` - Start *int `json:"Start,omitempty"` - Type *string `json:"Type"` + End *int `json:"end,omitempty"` + Id *int `json:"id,omitempty"` + Label *string `json:"label"` + Properties *FamilyRelationship `json:"properties,omitempty"` + Start *int `json:"start,omitempty"` + Type *string `json:"type"` } // GetProfileAdminsParams defines parameters for GetProfileAdmins. @@ -271,8 +277,7 @@ type GetManagedProfilesParams struct { // CreatePersonParams defines parameters for CreatePerson. type CreatePersonParams struct { - XUserID int `json:"X-User-ID"` - XUserName string `json:"X-User-Name"` + XUserID int `json:"X-User-ID"` } // CreatePersonByGoogleIdAndInviteCodeJSONBody defines parameters for CreatePersonByGoogleIdAndInviteCode. @@ -916,28 +921,6 @@ func (siw *ServerInterfaceWrapper) CreatePerson(c *gin.Context) { return } - // ------------- Required header parameter "X-User-Name" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("X-User-Name")]; found { - var XUserName string - n := len(valueList) - if n != 1 { - siw.ErrorHandler(c, fmt.Errorf("Expected one value for X-User-Name, got %d", n), http.StatusBadRequest) - return - } - - err = runtime.BindStyledParameterWithOptions("simple", "X-User-Name", valueList[0], &XUserName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter X-User-Name: %w", err), http.StatusBadRequest) - return - } - - params.XUserName = XUserName - - } else { - siw.ErrorHandler(c, fmt.Errorf("Header parameter X-User-Name is required, but not found"), http.StatusBadRequest) - return - } - for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { diff --git a/apps/db-adapter/pkg/api/flatten_list.go b/apps/db-adapter/pkg/api/flatten_list.go new file mode 100644 index 0000000..0a3f868 --- /dev/null +++ b/apps/db-adapter/pkg/api/flatten_list.go @@ -0,0 +1,54 @@ +package api + +import ( + "fmt" + "reflect" + "slices" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j/dbtype" +) + +func Flatten(input any, uniqueIds *[]int64, result *[]any) error { + val := reflect.ValueOf(input) + if uniqueIds == nil { + uniqueIds = &[]int64{} + } + + switch val.Kind() { + case reflect.Slice, reflect.Array: + for i := 0; i < val.Len(); i++ { + if err := Flatten(val.Index(i).Interface(), uniqueIds, result); err != nil { + return err + } + } + case reflect.Map: + id, ok := input.(map[string]any)["id"].(int64) + if !ok { + return fmt.Errorf("could not convert id to int: %v", input.(map[string]any)["id"]) + } + + if !slices.Contains(*uniqueIds, id) { + *result = append(*result, input.(map[string]any)) + *uniqueIds = append(*uniqueIds, id) + } + case reflect.Struct: + switch input.(type) { + case dbtype.Node: + node := val.Interface().(dbtype.Node) + if !slices.Contains(*uniqueIds, node.Id) { + *result = append(*result, node) + *uniqueIds = append(*uniqueIds, node.Id) + } + case dbtype.Relationship: + relationship := val.Interface().(dbtype.Relationship) + if !slices.Contains(*uniqueIds, relationship.Id) { + *result = append(*result, relationship) + *uniqueIds = append(*uniqueIds, relationship.Id) + } + } + default: + return fmt.Errorf("unexpected type: %T", input) + } + + return nil +}