From a6718b2487cca8d17f5a6a4d53876338428e2ba6 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Sun, 14 Apr 2024 01:25:56 +0200 Subject: [PATCH 01/44] update docker build action --- .github/workflows/auth-service-cd.yml | 4 ++-- .github/workflows/backend-cd.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auth-service-cd.yml b/.github/workflows/auth-service-cd.yml index 5ad23ae..9b5a827 100644 --- a/.github/workflows/auth-service-cd.yml +++ b/.github/workflows/auth-service-cd.yml @@ -16,9 +16,9 @@ jobs: id: create_image_tag with: script: | - if (context.issue.number) { + if(github.ref == 'refs/heads/main') { return "pr" + context.issue.number; - } else if(github.ref == 'refs/heads/main') { + } else if(context.issue.number) { return 'latest'; } else { return "pr" + ( diff --git a/.github/workflows/backend-cd.yml b/.github/workflows/backend-cd.yml index 0245867..f3ebd9f 100644 --- a/.github/workflows/backend-cd.yml +++ b/.github/workflows/backend-cd.yml @@ -16,9 +16,9 @@ jobs: id: create_image_tag with: script: | - if (context.issue.number) { + if(github.ref == 'refs/heads/main') { return "pr" + context.issue.number; - } else if(github.ref == 'refs/heads/main') { + } else if(context.issue.number) { return 'latest'; } else { return "pr" + ( From 35f478e24c81d376436e33002ac0a80433fff9e1 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Sun, 14 Apr 2024 11:15:01 +0200 Subject: [PATCH 02/44] create memgraph db connection --- backend/go.mod | 1 + backend/go.sum | 2 ++ backend/main.go | 51 ++----------------------------- backend/memgraph/init_database.go | 22 +++++++++++++ 4 files changed, 28 insertions(+), 48 deletions(-) create mode 100644 backend/memgraph/init_database.go diff --git a/backend/go.mod b/backend/go.mod index b7df6ff..0bf577f 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -23,6 +23,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/neo4j/neo4j-go-driver/v5 v5.19.0 // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect diff --git a/backend/go.sum b/backend/go.sum index aaff72d..c713462 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -46,6 +46,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/neo4j/neo4j-go-driver/v5 v5.19.0 h1:v2cB19fZQYz1xmj6EZXofFHD/+Tj16hH/OOp39uNN1I= +github.com/neo4j/neo4j-go-driver/v5 v5.19.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k= github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/backend/main.go b/backend/main.go index cb60a60..dc1a2b8 100644 --- a/backend/main.go +++ b/backend/main.go @@ -2,12 +2,8 @@ package main import ( "context" - "errors" "flag" - "fmt" - "io" "log" - "net/http" "os" "os/signal" "syscall" @@ -34,56 +30,15 @@ func main() { if *release { gin.SetMode(gin.ReleaseMode) } - if *logToFileAndStd || *logToFile { - gin.DisableConsoleColor() // Disable Console Color, you don't need console color when writing the logs to file. - path := fmt.Sprintf("private/logs/%02dy_%02dm_%02dd_%02dh_%02dm_%02ds.log", time.Now().Year(), time.Now().Month(), time.Now().Day(), time.Now().Hour(), time.Now().Minute(), time.Now().Second()) - logerror1 := os.MkdirAll("private/logs/", 0755) - f, logerror2 := os.Create(path) - if logerror1 != nil || logerror2 != nil { - log.Println("Cant log to file") - } else if *logToFileAndStd { - gin.DefaultWriter = io.MultiWriter(f, os.Stdout) - } else { - gin.DefaultWriter = io.MultiWriter(f) - } - } - log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) - log.SetOutput(gin.DefaultErrorWriter) + + utilities.SetupLogger(*logToFileAndStd, *logToFile) hc := gin_liveness.New() router := gin.Default() router.GET("/health", hc.HealthCheckHandler()) - var server *http.Server - - if utilities.FileExists(*cert) && utilities.FileExists(*key) { - server = &http.Server{ - Addr: *httpsPort, - Handler: router, - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - go func() { - log.Println("Server starts at port ", *httpsPort) - if err := server.ListenAndServeTLS(*cert, *key); err != nil && errors.Is(err, http.ErrServerClosed) { - log.Fatal(err) - } - }() - } else { - server = &http.Server{ - Addr: *httpPort, - Handler: router, - ReadTimeout: requestTimeout * time.Second, - WriteTimeout: requestTimeout * time.Second, - } - go func() { - log.Println("Server starts at port ", *httpPort) - if err := server.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) { - log.Fatal(err) - } - }() - } + server := utilities.SetupHttpsServer(router, *cert, *key, *httpsPort, *httpPort, requestTimeout) // Wait for interrupt signal to gracefully shutdown the server with some time to finish requests. quit := make(chan os.Signal, 1) diff --git a/backend/memgraph/init_database.go b/backend/memgraph/init_database.go new file mode 100644 index 0000000..91df921 --- /dev/null +++ b/backend/memgraph/init_database.go @@ -0,0 +1,22 @@ +package memgraph + +import ( + "context" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" +) + +func InitDatabase(dbUri, dbUser, dbPassword string) error { + driver, err := neo4j.NewDriverWithContext(dbUri, neo4j.BasicAuth(dbUser, dbPassword, "")) + if err != nil { + return err + } + ctx := context.Background() + defer driver.Close(ctx) + + err = driver.VerifyConnectivity(ctx) + if err != nil { + panic(err) + } + +} From cdea69736dd120c07c5c5fb231ca55e3e175f7ee Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Sun, 14 Apr 2024 11:46:46 +0200 Subject: [PATCH 03/44] Add init database.go --- auth-service/go.mod | 4 ++++ backend/go.mod | 4 +++- backend/go.sum | 2 -- backend/memgraph/init_database.go | 9 ++++++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/auth-service/go.mod b/auth-service/go.mod index 45ebb40..eaace3c 100644 --- a/auth-service/go.mod +++ b/auth-service/go.mod @@ -1,3 +1,7 @@ module github.com/vcscsvcscs/GenerationsHeritage/auth-service go 1.22.2 + +replace github.com/vcscsvcscs/GenerationsHeritage/utilities => ../utilities + +require github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240413231225-317fc9067e28 // indirect diff --git a/backend/go.mod b/backend/go.mod index 0bf577f..c56535f 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,6 +4,7 @@ go 1.22.2 require ( github.com/gin-gonic/gin v1.9.1 + github.com/neo4j/neo4j-go-driver/v5 v5.19.0 github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240413225529-30321ba5d7e7 ) @@ -23,7 +24,6 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/neo4j/neo4j-go-driver/v5 v5.19.0 // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect @@ -35,3 +35,5 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/vcscsvcscs/GenerationsHeritage/utilities => ../utilities diff --git a/backend/go.sum b/backend/go.sum index c713462..c32e95a 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -68,8 +68,6 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240413225529-30321ba5d7e7 h1:6HOZdgsOt8KojDfNDOyHLwv+Chv90MECxMdP+cKKNv4= -github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240413225529-30321ba5d7e7/go.mod h1:8byGXK+Csy5RCmHrvdMIzS8oVuvkr9Ech2PqLrad7os= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= diff --git a/backend/memgraph/init_database.go b/backend/memgraph/init_database.go index 91df921..d03e645 100644 --- a/backend/memgraph/init_database.go +++ b/backend/memgraph/init_database.go @@ -2,21 +2,24 @@ package memgraph import ( "context" + "log" "github.com/neo4j/neo4j-go-driver/v5/neo4j" ) -func InitDatabase(dbUri, dbUser, dbPassword string) error { +func InitDatabase(dbUri, dbUser, dbPassword string) neo4j.DriverWithContext { driver, err := neo4j.NewDriverWithContext(dbUri, neo4j.BasicAuth(dbUser, dbPassword, "")) if err != nil { - return err + log.Panicln(err) } + ctx := context.Background() defer driver.Close(ctx) err = driver.VerifyConnectivity(ctx) if err != nil { - panic(err) + log.Panicln(err) } + return driver } From a4c1bc56f884f9c142079fb90ee3a2671e3c805a Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Sun, 14 Apr 2024 17:13:59 +0200 Subject: [PATCH 04/44] Fix release pipeline tagging --- .github/workflows/auth-service-cd.yml | 4 ++-- .github/workflows/backend-cd.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auth-service-cd.yml b/.github/workflows/auth-service-cd.yml index 9b5a827..a7374f7 100644 --- a/.github/workflows/auth-service-cd.yml +++ b/.github/workflows/auth-service-cd.yml @@ -17,9 +17,9 @@ jobs: with: script: | if(github.ref == 'refs/heads/main') { - return "pr" + context.issue.number; - } else if(context.issue.number) { return 'latest'; + } else if(context.issue.number) { + return "pr" + context.issue.number; } else { return "pr" + ( await github.rest.repos.listPullRequestsAssociatedWithCommit({ diff --git a/.github/workflows/backend-cd.yml b/.github/workflows/backend-cd.yml index f3ebd9f..16ba9c0 100644 --- a/.github/workflows/backend-cd.yml +++ b/.github/workflows/backend-cd.yml @@ -17,9 +17,9 @@ jobs: with: script: | if(github.ref == 'refs/heads/main') { - return "pr" + context.issue.number; - } else if(context.issue.number) { return 'latest'; + } else if(context.issue.number) { + return "pr" + context.issue.number; } else { return "pr" + ( await github.rest.repos.listPullRequestsAssociatedWithCommit({ From cb6628a83c676acb8f85cce972e2f1804773a7d9 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Sun, 14 Apr 2024 19:21:20 +0200 Subject: [PATCH 05/44] implement Get person endpoint --- backend/handlers/viewPerson.go | 48 ++++++++++++++++++++++ backend/main.go | 8 ++++ backend/memgraph/create_schema.go | 67 +++++++++++++++++++++++++++++++ backend/memgraph/init_database.go | 8 ++++ 4 files changed, 131 insertions(+) create mode 100644 backend/handlers/viewPerson.go create mode 100644 backend/memgraph/create_schema.go diff --git a/backend/handlers/viewPerson.go b/backend/handlers/viewPerson.go new file mode 100644 index 0000000..0966008 --- /dev/null +++ b/backend/handlers/viewPerson.go @@ -0,0 +1,48 @@ +package handlers + +import ( + "context" + "fmt" + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/neo4j/neo4j-go-driver/v5/neo4j" +) + +func ViewPerson(driver neo4j.DriverWithContext) gin.HandlerFunc { + return func(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) + defer session.Close(ctx) + + id := c.Query("id") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"}) + + return + } + query := fmt.Sprintf("MATCH (n:Person) WHERE n.ID = '%s' RETURN n", id) + + result, err := session.Run(ctx, query, nil) + if err != nil { + log.Println(err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) + + return + } + + rec, err := result.Single(ctx) + if err != nil { + log.Println(err) + c.JSON(http.StatusNotFound, gin.H{"error": "could not find person with information provided"}) + + return + } + + c.JSON(200, gin.H{"person": rec.AsMap()}) + } +} diff --git a/backend/main.go b/backend/main.go index dc1a2b8..e2bbdc4 100644 --- a/backend/main.go +++ b/backend/main.go @@ -10,6 +10,8 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/vcscsvcscs/GenerationsHeritage/backend/handlers" + "github.com/vcscsvcscs/GenerationsHeritage/backend/memgraph" "github.com/vcscsvcscs/GenerationsHeritage/utilities" "github.com/vcscsvcscs/GenerationsHeritage/utilities/gin_liveness" ) @@ -19,6 +21,9 @@ var ( key = flag.String("key", "./private/keys/key.pem", "Specify the path of TLS key") httpsPort = flag.String("https", ":443", "Specify port for http secure hosting(example for format :443)") httpPort = flag.String("http", ":80", "Specify port for http hosting(example for format :80)") + memgraphURI = flag.String("memgraph", "bolt+s://memgraph:7687", "Specify the Memgraph database URI") + memgraphUser = flag.String("memgraph-user", "", "Specify the Memgraph database user") + memgraphPass = flag.String("memgraph-pass", "", "Specify the Memgraph database password") release = flag.Bool("release", false, "Set true to release build") logToFile = flag.Bool("log-to-file", false, "Set true to log to file") logToFileAndStd = flag.Bool("log-to-file-and-std", false, "Set true to log to file and std") @@ -35,8 +40,11 @@ func main() { hc := gin_liveness.New() + memgraphDriver := memgraph.InitDatabase(*memgraphURI, *memgraphUser, *memgraphPass) + router := gin.Default() router.GET("/health", hc.HealthCheckHandler()) + router.GET("/person", handlers.ViewPerson(memgraphDriver)) server := utilities.SetupHttpsServer(router, *cert, *key, *httpsPort, *httpPort, requestTimeout) diff --git a/backend/memgraph/create_schema.go b/backend/memgraph/create_schema.go new file mode 100644 index 0000000..b2945f1 --- /dev/null +++ b/backend/memgraph/create_schema.go @@ -0,0 +1,67 @@ +package memgraph + +import ( + "log" + "time" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "golang.org/x/net/context" +) + +const dbCreateSchemaTimeout = 10 * time.Second + +func createIndexes(driver neo4j.DriverWithContext) error { + ctx, cancel := context.WithTimeout(context.Background(), dbCreateSchemaTimeout) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) + defer session.Close(ctx) + + indexes := []string{ + `CREATE INDEX ON :Person(ID);`, + `CREATE INDEX ON :Person(Surname);`, + `CREATE INDEX ON :Person(Firstname);`, + `CREATE INDEX ON :Person(Born);`, + `CREATE INDEX ON :Person(MothersFirstName);`, + `CREATE INDEX ON :Person(MothersSurname);`, + } + + // Run index queries via implicit auto-commit transaction + for _, index := range indexes { + _, err := session.Run(ctx, index, nil) + if err != nil { + log.Panicln(err) + } + } + + return nil +} + +func createConstraints(driver neo4j.DriverWithContext) error { + ctx, cancel := context.WithTimeout(context.Background(), dbCreateSchemaTimeout) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) + defer session.Close(ctx) + + constraints := []string{ + `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.ID);`, + `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Surname);`, + `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Firstname);`, + `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Born);`, + `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.MothersFirstName);`, + `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.MothersSurname);`, + `CREATE CONSTRAINT ON (n:Person) ASSERT n.ID IS UNIQUE;`, + `CREATE CONSTRAINT ON (n:Person) ASSERT n.Surname, n.Firstname, n.Born, n.MothersFirstName, n.MothersSurname IS UNIQUE;`, + } + + // Run index queries via implicit auto-commit transaction + for _, constraint := range constraints { + _, err := session.Run(ctx, constraint, nil) + if err != nil { + return err + } + } + + return nil +} diff --git a/backend/memgraph/init_database.go b/backend/memgraph/init_database.go index d03e645..cdd4a72 100644 --- a/backend/memgraph/init_database.go +++ b/backend/memgraph/init_database.go @@ -21,5 +21,13 @@ func InitDatabase(dbUri, dbUser, dbPassword string) neo4j.DriverWithContext { log.Panicln(err) } + if err := createIndexes(driver); err != nil { + log.Panicln(err) + } + + if err := createConstraints(driver); err != nil { + log.Panicln(err) + } + return driver } From 3b12f4798ca58cd89744701b9a30085147e2078a Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Sun, 14 Apr 2024 23:49:33 +0200 Subject: [PATCH 06/44] Add Person model --- backend/go.mod | 1 + backend/go.sum | 2 + backend/handlers/createPerson.go | 59 +++++++++++++++++++++++ backend/handlers/viewPerson.go | 2 +- backend/main.go | 1 + backend/memgraph/create_schema.go | 16 +++---- backend/memgraph/model.go | 71 ++++++++++++++++++++++++++++ backend/memgraph/model_test.go | 77 +++++++++++++++++++++++++++++++ 8 files changed, 220 insertions(+), 9 deletions(-) create mode 100644 backend/handlers/createPerson.go create mode 100644 backend/memgraph/model.go create mode 100644 backend/memgraph/model_test.go diff --git a/backend/go.mod b/backend/go.mod index c56535f..c7d3542 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -18,6 +18,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.19.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index c32e95a..981df85 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -31,6 +31,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= diff --git a/backend/handlers/createPerson.go b/backend/handlers/createPerson.go new file mode 100644 index 0000000..9a14dd2 --- /dev/null +++ b/backend/handlers/createPerson.go @@ -0,0 +1,59 @@ +package handlers + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/vcscsvcscs/GenerationsHeritage/backend/memgraph" +) + +func CreatePerson(driver neo4j.DriverWithContext) gin.HandlerFunc { + return func(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) + defer session.Close(ctx) + + if c.Request.Body == nil || c.ContentType() != "application/json" { + log.Printf("ip: %s error: request body is empty or content type is not application/json", c.ClientIP()) + c.JSON(http.StatusBadRequest, gin.H{"error": "content type must be application/json and request body must not be empty"}) + } + + var person memgraph.Person + err := json.NewDecoder(c.Request.Body).Decode(&person) + if err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) + } + + person.ID = uuid.New().String() + + query := fmt.Sprintf("CREATE (n:Person {%s}) RETURN n;", person.ToString()) + + result, err := session.Run(ctx, query, nil) + if err != nil { + log.Println(err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) + + return + } + + rec, err := result.Single(ctx) + if err != nil { + log.Println(err) + c.JSON(http.StatusNotFound, gin.H{"error": "could not find person with information provided"}) + + return + } + + c.JSON(200, gin.H{"person": rec.AsMap()}) + } +} diff --git a/backend/handlers/viewPerson.go b/backend/handlers/viewPerson.go index 0966008..3ca6d81 100644 --- a/backend/handlers/viewPerson.go +++ b/backend/handlers/viewPerson.go @@ -25,7 +25,7 @@ func ViewPerson(driver neo4j.DriverWithContext) gin.HandlerFunc { return } - query := fmt.Sprintf("MATCH (n:Person) WHERE n.ID = '%s' RETURN n", id) + query := fmt.Sprintf("MATCH (n:Person) WHERE n.ID = '%s' RETURN n;", id) result, err := session.Run(ctx, query, nil) if err != nil { diff --git a/backend/main.go b/backend/main.go index e2bbdc4..660d818 100644 --- a/backend/main.go +++ b/backend/main.go @@ -45,6 +45,7 @@ func main() { router := gin.Default() router.GET("/health", hc.HealthCheckHandler()) router.GET("/person", handlers.ViewPerson(memgraphDriver)) + router.POST("/createPerson", handlers.CreatePerson(memgraphDriver)) server := utilities.SetupHttpsServer(router, *cert, *key, *httpsPort, *httpPort, requestTimeout) diff --git a/backend/memgraph/create_schema.go b/backend/memgraph/create_schema.go index b2945f1..75abe49 100644 --- a/backend/memgraph/create_schema.go +++ b/backend/memgraph/create_schema.go @@ -19,11 +19,11 @@ func createIndexes(driver neo4j.DriverWithContext) error { indexes := []string{ `CREATE INDEX ON :Person(ID);`, - `CREATE INDEX ON :Person(Surname);`, + `CREATE INDEX ON :Person(Lastname);`, `CREATE INDEX ON :Person(Firstname);`, `CREATE INDEX ON :Person(Born);`, - `CREATE INDEX ON :Person(MothersFirstName);`, - `CREATE INDEX ON :Person(MothersSurname);`, + `CREATE INDEX ON :Person(MothersFirstname);`, + `CREATE INDEX ON :Person(MothersLastname);`, } // Run index queries via implicit auto-commit transaction @@ -46,13 +46,13 @@ func createConstraints(driver neo4j.DriverWithContext) error { constraints := []string{ `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.ID);`, - `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Surname);`, - `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Firstname);`, + `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Lastname);`, + `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Firstame);`, `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Born);`, - `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.MothersFirstName);`, - `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.MothersSurname);`, + `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.MothersFirstname);`, + `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.MothersLastname);`, `CREATE CONSTRAINT ON (n:Person) ASSERT n.ID IS UNIQUE;`, - `CREATE CONSTRAINT ON (n:Person) ASSERT n.Surname, n.Firstname, n.Born, n.MothersFirstName, n.MothersSurname IS UNIQUE;`, + `CREATE CONSTRAINT ON (n:Person) ASSERT n.Lastname, n.Firstname, n.Born, n.MothersFirstname, n.MothersLastname IS UNIQUE;`, } // Run index queries via implicit auto-commit transaction diff --git a/backend/memgraph/model.go b/backend/memgraph/model.go new file mode 100644 index 0000000..079fd5b --- /dev/null +++ b/backend/memgraph/model.go @@ -0,0 +1,71 @@ +package memgraph + +import ( + "fmt" + "time" +) + +type Person struct { + ID string `json:"id"` + Firstname string `json:"first_name"` + Lastname string `json:"last_name"` + MothersFirstname string `json:"mothers_first_name"` + MothersLastname string `json:"mothers_last_name"` + Born time.Time `json:"born"` + Birthplace string `json:"birthplace"` + Residence string `json:"residence"` + Death time.Time `json:"death"` + Deathplace string `json:"deathplace"` + LifeEvents []map[string]string `json:"life_events"` + Occupations []string `json:"occupation"` + OccupationToDisplay string `json:"occupation_to_display"` + OthersSaid map[string]string `json:"others_said"` + Photos map[string]string `json:"photos"` + ProfilePicture string `json:"profile_picture"` +} + +func (p *Person) ToString() string { + result := fmt.Sprintf("ID: '%s', Firstname: '%s', Lastname: '%s', MothersFirstname: '%s', MothersLastname: '%s'", p.ID, p.Firstname, p.Lastname, p.MothersFirstname, p.MothersLastname) + result = fmt.Sprintf("%s, Born: date({year:%d, month:%d, day:%d}), Death: date({year:%d, month:%d, day:%d})", result, p.Born.Year(), p.Born.Month(), p.Born.Day(), p.Death.Year(), p.Death.Month(), p.Death.Day()) + result = fmt.Sprintf("%s, Birthplace: '%s', Residence: '%s', Deathplace: '%s', OccupationToDisplay: '%s', ProfilePicture: '%s'", result, p.Birthplace, p.Residence, p.Deathplace, p.OccupationToDisplay, p.ProfilePicture) + + if p.LifeEvents != nil && len(p.LifeEvents) > 0 { + result = fmt.Sprintf("%s, LifeEvents: [", result) + for i := 0; i < len(p.LifeEvents); i++ { + date, dok := p.LifeEvents[i]["date"] + event, eok := p.LifeEvents[i]["event"] + if dok && eok { + result = fmt.Sprintf("%s{date: '%s', event: '%s'}, ", result, date, event) + } + } + result = fmt.Sprintf("%s]", result[:len(result)-2]) + } + + if p.Occupations != nil && len(p.Occupations) > 0 { + result = fmt.Sprintf("%s, Occupations: [", result) + + for _, occupation := range p.Occupations { + result = fmt.Sprintf("%s'%s', ", result, occupation) + } + + result = fmt.Sprintf("%s]", result[:len(result)-2]) + } + + if p.OthersSaid != nil { + result = fmt.Sprintf("%s, OthersSaid: {", result) + for key, value := range p.OthersSaid { + result = fmt.Sprintf("%s%s: '%s', ", result, key, value) + } + result = fmt.Sprintf("%s}", result[:len(result)-2]) + } + + if p.Photos != nil && len(p.Photos) > 0 { + result = fmt.Sprintf("%s, Photos: {", result) + for key, value := range p.Photos { + result = fmt.Sprintf("%s%s: '%s', ", result, key, value) + } + result = fmt.Sprintf("%s}", result[:len(result)-2]) + } + + return result +} diff --git a/backend/memgraph/model_test.go b/backend/memgraph/model_test.go new file mode 100644 index 0000000..0b47c0a --- /dev/null +++ b/backend/memgraph/model_test.go @@ -0,0 +1,77 @@ +package memgraph + +import ( + "testing" + "time" +) + +func TestPerson_ToString(t *testing.T) { + tests := []struct { + name string + p *Person + want string + }{ + { + name: "Test with nil values", + p: &Person{ + ID: "1", + Firstname: "John", + Lastname: "Doe", + MothersFirstname: "Jane", + MothersLastname: "Doe", + Born: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + Birthplace: "New York", + Residence: "New York", + Death: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + Deathplace: "New York", + }, + want: "ID: '1', Firstname: 'John', Lastname: 'Doe', MothersFirstname: 'Jane', MothersLastname: 'Doe', Born: date({year:2021, month:1, day:1}), Death: date({year:2021, month:1, day:1}), Birthplace: 'New York', Residence: 'New York', Deathplace: 'New York', OccupationToDisplay: '', ProfilePicture: ''", + }, { + name: "Test with All values", + p: &Person{ + ID: "1", + Firstname: "John", + Lastname: "Doe", + MothersFirstname: "Jane", + MothersLastname: "Doe", + Born: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + Birthplace: "New York", + Residence: "New York", + Death: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + Deathplace: "New York", + LifeEvents: []map[string]string{ + { + "date": "2021-01-01", + "event": "Event 1", + }, + { + "date": "2021-01-02", + "event": "Event 2", + }, + }, + Occupations: []string{ + "Welder", + "Plumber", + }, + OccupationToDisplay: "Welder", + OthersSaid: map[string]string{ + "Beni": "He is a good person", + "Jani": "He is a bad person", + }, + Photos: map[string]string{ + "Profile": "profile.jpg", + "Family": "family.jpg", + }, + ProfilePicture: "profile.jpg", + }, + want: "ID: '1', Firstname: 'John', Lastname: 'Doe', MothersFirstname: 'Jane', MothersLastname: 'Doe', Born: date({year:2021, month:1, day:1}), Death: date({year:2021, month:1, day:1}), Birthplace: 'New York', Residence: 'New York', Deathplace: 'New York', OccupationToDisplay: 'Welder', ProfilePicture: 'profile.jpg', LifeEvents: [{date: '2021-01-01', event: 'Event 1'}, {date: '2021-01-02', event: 'Event 2'}], Occupations: ['Welder', 'Plumber'], OthersSaid: {Beni: 'He is a good person', Jani: 'He is a bad person'}, Photos: {Profile: 'profile.jpg', Family: 'family.jpg'}", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.p.ToString(); got != tt.want { + t.Errorf("Person.ToString() = %v, want %v", got, tt.want) + } + }) + } +} From 33aa4945af1030dd07330087a1ac54c15a94a710 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 15 Apr 2024 20:50:46 +0200 Subject: [PATCH 07/44] go mod tidy --- backend/go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index c7d3542..03ca59b 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,8 +4,10 @@ go 1.22.2 require ( github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.6.0 github.com/neo4j/neo4j-go-driver/v5 v5.19.0 github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240413225529-30321ba5d7e7 + golang.org/x/net v0.22.0 ) require ( @@ -18,7 +20,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.19.0 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -30,7 +31,6 @@ require ( github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.7.0 // indirect golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.22.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.33.0 // indirect From 73627c7c59aa0ce42c1c36e058631fa1f6e6d0fd Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 15 Apr 2024 21:05:32 +0200 Subject: [PATCH 08/44] Add utilities to docker files --- auth-service/dockerfile | 1 + backend/dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/auth-service/dockerfile b/auth-service/dockerfile index a01bd27..ed1bc7e 100644 --- a/auth-service/dockerfile +++ b/auth-service/dockerfile @@ -3,6 +3,7 @@ FROM golang:1.22.2-alpine as build WORKDIR /app COPY . . +COPY ../utilities ../utilities RUN GOOS=linux GOARCH=arm64 go build -o auth-service diff --git a/backend/dockerfile b/backend/dockerfile index cf5f321..1209e3e 100644 --- a/backend/dockerfile +++ b/backend/dockerfile @@ -3,6 +3,7 @@ FROM golang:1.22.2-alpine as build WORKDIR /app COPY . . +COPY ../utilities ../utilities RUN GOOS=linux GOARCH=arm64 go build -o backend From a34a934bf4cecea5fe62ac8a19f45e2b4a409f75 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 15 Apr 2024 21:13:19 +0200 Subject: [PATCH 09/44] move utilities to remote instead local --- auth-service/dockerfile | 1 - auth-service/go.mod | 31 ++++++++++++++-- auth-service/go.sum | 80 +++++++++++++++++++++++++++++++++++++++++ backend/dockerfile | 1 - backend/go.mod | 4 +-- backend/go.sum | 4 +++ 6 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 auth-service/go.sum diff --git a/auth-service/dockerfile b/auth-service/dockerfile index ed1bc7e..a01bd27 100644 --- a/auth-service/dockerfile +++ b/auth-service/dockerfile @@ -3,7 +3,6 @@ FROM golang:1.22.2-alpine as build WORKDIR /app COPY . . -COPY ../utilities ../utilities RUN GOOS=linux GOARCH=arm64 go build -o auth-service diff --git a/auth-service/go.mod b/auth-service/go.mod index eaace3c..5e8b64d 100644 --- a/auth-service/go.mod +++ b/auth-service/go.mod @@ -2,6 +2,31 @@ module github.com/vcscsvcscs/GenerationsHeritage/auth-service go 1.22.2 -replace github.com/vcscsvcscs/GenerationsHeritage/utilities => ../utilities - -require github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240413231225-317fc9067e28 // indirect +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.9.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240414091827-ffde94d457cb // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/auth-service/go.sum b/auth-service/go.sum new file mode 100644 index 0000000..55534de --- /dev/null +++ b/auth-service/go.sum @@ -0,0 +1,80 @@ +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240414091827-ffde94d457cb h1:fU736we2gQQRMOWP/su7sCiUFmrXTKBN0s8LG5k7bOE= +github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240414091827-ffde94d457cb/go.mod h1:aQlmG6BiGFmOFxzAkWTJDzm1EzdCJ4OEETXTUkWJaLk= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/backend/dockerfile b/backend/dockerfile index 1209e3e..cf5f321 100644 --- a/backend/dockerfile +++ b/backend/dockerfile @@ -3,7 +3,6 @@ FROM golang:1.22.2-alpine as build WORKDIR /app COPY . . -COPY ../utilities ../utilities RUN GOOS=linux GOARCH=arm64 go build -o backend diff --git a/backend/go.mod b/backend/go.mod index 03ca59b..eb6ce58 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -6,7 +6,7 @@ require ( github.com/gin-gonic/gin v1.9.1 github.com/google/uuid v1.6.0 github.com/neo4j/neo4j-go-driver/v5 v5.19.0 - github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240413225529-30321ba5d7e7 + github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240414091827-ffde94d457cb golang.org/x/net v0.22.0 ) @@ -36,5 +36,3 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/vcscsvcscs/GenerationsHeritage/utilities => ../utilities diff --git a/backend/go.sum b/backend/go.sum index 981df85..10b506e 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -70,6 +70,10 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240413225529-30321ba5d7e7 h1:6HOZdgsOt8KojDfNDOyHLwv+Chv90MECxMdP+cKKNv4= +github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240413225529-30321ba5d7e7/go.mod h1:8byGXK+Csy5RCmHrvdMIzS8oVuvkr9Ech2PqLrad7os= +github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240414091827-ffde94d457cb h1:fU736we2gQQRMOWP/su7sCiUFmrXTKBN0s8LG5k7bOE= +github.com/vcscsvcscs/GenerationsHeritage/utilities v0.0.0-20240414091827-ffde94d457cb/go.mod h1:aQlmG6BiGFmOFxzAkWTJDzm1EzdCJ4OEETXTUkWJaLk= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= From 65345e0e76880c8a9c97dd238efea1bff2da4210 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 15 Apr 2024 23:45:01 +0200 Subject: [PATCH 10/44] cypher injection prevention --- backend/memgraph/cypher_verify_string.go | 124 +++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 backend/memgraph/cypher_verify_string.go diff --git a/backend/memgraph/cypher_verify_string.go b/backend/memgraph/cypher_verify_string.go new file mode 100644 index 0000000..2c67eab --- /dev/null +++ b/backend/memgraph/cypher_verify_string.go @@ -0,0 +1,124 @@ +package memgraph + +import ( + "fmt" + "strings" +) + +var cypherKeywords = []string{ + "CREATE", + "DELETE", + "DETACH", + "DETACH DELETE", + "FOREACH", + "LOAD CSV", + "MERGE", + "MATCH", + "ON", + "OPTIONAL MATCH", + "REMOVE", + "SET", + "START", + "UNION", + "UNWIND", + "WITH", + "RETURN", + "ORDER BY", + "SKIP", + "LIMIT", + "ASC", + "DESC", + "EXISTS", + "CALL", + "USING", + "CONSTRAINT", + "DROP", + "INDEX", + "WHERE", +} + +var cypherOperators = []string{ + "+", + "-", + "*", + "/", + "%", + "^", + "=", + "<", + ">", + "<=", + ">=", + "<>", + "AND", + "OR", + "XOR", + "NOT", + "IN", + "STARTS WITH", + "ENDS WITH", + "CONTAINS", + "IS NULL", + "IS NOT NULL", + "IS UNIQUE", + "IS NODE", + "IS RELATIONSHIP", + "IS PROPERTY KEY", + "IS MAP", + "IS LIST", + "IS BOOLEAN", + "IS STRING", + "IS NUMBER", + "IS INTEGER", + "IS FLOAT", + "IS NODE", + "IS RELATIONSHIP", + "IS PATH", + "IS POINT", + "IS DATE", + "IS DURATION", +} + +// cypherDelimiters contains the delimiters that need to be escaped in a string to prevent cypher injection keys are the delimiters that need to be escaped and values are the escaped delimiters +var cypherDelimiters = map[string]string{ + "'": "\\'", + "\"": "\\\"", + "\\u0027": "\\\\u0027", + "\\u0022": "\\\\u0022", + "`": "``", + "\\u0060": "\\u0060\\u0060", +} + +// VerifyString verifies if a string is valid and does not contain cypher injection +func VerifyString(s string) error { + s = strings.ToUpper(s) + for _, keyword := range cypherKeywords { + if strings.Contains(s, keyword) { + return fmt.Errorf("invalid string contains cypher keyword: %s", keyword) + } + } + + for _, operator := range cypherOperators { + if strings.Contains(s, operator) { + return fmt.Errorf("invalid string contains cypher operator: %s", operator) + } + } + + for key := range cypherDelimiters { + if strings.Contains(s, key) { + return fmt.Errorf("invalid string contains cypher delimiter: %s", key) + } + } + + return nil +} + +// EscapeString escapes delimiters in a string to prevent cypher injection +func EscapeString(s string) string { + result := s + for k, v := range cypherDelimiters { + result = strings.ReplaceAll(result, k, v) + } + + return result +} From c54b142b70c03359da58f8eeab5d1dcc61e99d31 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 15 Apr 2024 23:45:51 +0200 Subject: [PATCH 11/44] verify structs --- backend/memgraph/model.go | 163 +++++++++++++++++++++++++++++++++++++- 1 file changed, 162 insertions(+), 1 deletion(-) diff --git a/backend/memgraph/model.go b/backend/memgraph/model.go index 079fd5b..84e282b 100644 --- a/backend/memgraph/model.go +++ b/backend/memgraph/model.go @@ -5,10 +5,22 @@ import ( "time" ) +var RelationshipTypes = []string{ + "Parent", + "Child", + "Spouse", + "Sibling", +} + type Person struct { ID string `json:"id"` Firstname string `json:"first_name"` + Middlename string `json:"middle_name"` Lastname string `json:"last_name"` + Titles []string `json:"titles"` // e.g. Jr., Sr., III + Suffixes []string `json:"suffixes"` // e.g. Ph.D., M.D. + ExtraNames []string `json:"extra_names"` + Aliases []string `json:"aliases"` MothersFirstname string `json:"mothers_first_name"` MothersLastname string `json:"mothers_last_name"` Born time.Time `json:"born"` @@ -25,10 +37,42 @@ type Person struct { } func (p *Person) ToString() string { - result := fmt.Sprintf("ID: '%s', Firstname: '%s', Lastname: '%s', MothersFirstname: '%s', MothersLastname: '%s'", p.ID, p.Firstname, p.Lastname, p.MothersFirstname, p.MothersLastname) + result := fmt.Sprintf("ID: '%s', Firstname: '%s', Lastname: '%s', Middlename: '%s', MothersFirstname: '%s', MothersLastname: '%s'", p.ID, p.Firstname, p.Lastname, p.Middlename, p.MothersFirstname, p.MothersLastname) result = fmt.Sprintf("%s, Born: date({year:%d, month:%d, day:%d}), Death: date({year:%d, month:%d, day:%d})", result, p.Born.Year(), p.Born.Month(), p.Born.Day(), p.Death.Year(), p.Death.Month(), p.Death.Day()) result = fmt.Sprintf("%s, Birthplace: '%s', Residence: '%s', Deathplace: '%s', OccupationToDisplay: '%s', ProfilePicture: '%s'", result, p.Birthplace, p.Residence, p.Deathplace, p.OccupationToDisplay, p.ProfilePicture) + if p.Titles != nil && len(p.Titles) > 0 { + result = fmt.Sprintf("%s, Titles: [", result) + for _, title := range p.Titles { + result = fmt.Sprintf("%s'%s', ", result, title) + } + result = fmt.Sprintf("%s]", result[:len(result)-2]) + } + + if p.Suffixes != nil && len(p.Suffixes) > 0 { + result = fmt.Sprintf("%s, Suffixes: [", result) + for _, suffix := range p.Suffixes { + result = fmt.Sprintf("%s'%s', ", result, suffix) + } + result = fmt.Sprintf("%s]", result[:len(result)-2]) + } + + if p.ExtraNames != nil && len(p.ExtraNames) > 0 { + result = fmt.Sprintf("%s, ExtraNames: [", result) + for _, extraName := range p.ExtraNames { + result = fmt.Sprintf("%s'%s', ", result, extraName) + } + result = fmt.Sprintf("%s]", result[:len(result)-2]) + } + + if p.Aliases != nil && len(p.Aliases) > 0 { + result = fmt.Sprintf("%s, Aliases: [", result) + for _, alias := range p.Aliases { + result = fmt.Sprintf("%s'%s', ", result, alias) + } + result = fmt.Sprintf("%s]", result[:len(result)-2]) + } + if p.LifeEvents != nil && len(p.LifeEvents) > 0 { result = fmt.Sprintf("%s, LifeEvents: [", result) for i := 0; i < len(p.LifeEvents); i++ { @@ -69,3 +113,120 @@ func (p *Person) ToString() string { return result } + +// Verify checks if the person is valid and does not contain cypher injection it also escapes the delimiters contained in any of the strings +func (p *Person) Verify() error { + if err := VerifyString(p.ID); err != nil { + return fmt.Errorf("invalid ID type %s", err) + } + + p.Firstname = EscapeString(p.Firstname) + p.Middlename = EscapeString(p.Middlename) + p.Lastname = EscapeString(p.Lastname) + p.MothersFirstname = EscapeString(p.MothersFirstname) + p.MothersLastname = EscapeString(p.MothersLastname) + p.Birthplace = EscapeString(p.Birthplace) + p.Residence = EscapeString(p.Residence) + p.Deathplace = EscapeString(p.Deathplace) + p.OccupationToDisplay = EscapeString(p.OccupationToDisplay) + p.ProfilePicture = EscapeString(p.ProfilePicture) + + for i, title := range p.Titles { + p.Titles[i] = EscapeString(title) + } + + for i, suffix := range p.Suffixes { + p.Suffixes[i] = EscapeString(suffix) + } + + for i, extraName := range p.ExtraNames { + p.ExtraNames[i] = EscapeString(extraName) + } + + for i, alias := range p.Aliases { + p.Aliases[i] = EscapeString(alias) + } + + for i, lifeEvent := range p.LifeEvents { + for key, value := range lifeEvent { + if key != "date" && key != "event" { + return fmt.Errorf("invalid key in life event") + } + p.LifeEvents[i][key] = EscapeString(value) + } + } + + for i, occupation := range p.Occupations { + p.Occupations[i] = EscapeString(occupation) + } + + for key, value := range p.OthersSaid { + if err := VerifyString(key); err != nil { + return fmt.Errorf("invalid key in others said %s", err) + } + p.OthersSaid[key] = EscapeString(value) + } + + for key, value := range p.Photos { + if err := VerifyString(key); err != nil { + return fmt.Errorf("invalid key in photos %s", err) + } + p.Photos[key] = EscapeString(value) + } + + return nil +} + +type Relationship struct { + FirstPersonID string `json:"first_person_id"` + SecondPersonID string `json:"second_person_id"` + Relationship string `json:"relationship"` + Direction string `json:"direction"` +} + +// Verify checks if the relationship is valid and does not contain cypher injection +func (r *Relationship) Verify() error { + if r.Direction != "->" && r.Direction != "<-" && r.Direction != "-" { + return fmt.Errorf("invalid direction for relationship") + } + + // Check if the relationship is in the list of valid relationships + found := false + for _, relationship := range RelationshipTypes { + if r.Relationship == relationship { + found = true + + break + } + } + if !found { + return fmt.Errorf("invalid relationship type") + } + + if err := VerifyString(r.FirstPersonID); err != nil { + return fmt.Errorf("invalid FirstPersonID type %s", err) + } + + if err := VerifyString(r.SecondPersonID); err != nil { + return fmt.Errorf("invalid SecondPersonID type %s", err) + } + + return nil +} + +type RelationshipAndPerson struct { + Relationship Relationship `json:"relationship"` + Person Person `json:"person"` +} + +func (r *RelationshipAndPerson) Verify() error { + if err := r.Relationship.Verify(); err != nil { + return err + } + + if err := r.Person.Verify(); err != nil { + return err + } + + return nil +} From f5e95095c7b346cc2f12ddb20129d785be81755f Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 15 Apr 2024 23:46:11 +0200 Subject: [PATCH 12/44] create relationship --- backend/handlers/createRelationship.go | 32 +++++++++++++++++++++ backend/memgraph/createRelationship.go | 39 ++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 backend/handlers/createRelationship.go create mode 100644 backend/memgraph/createRelationship.go diff --git a/backend/handlers/createRelationship.go b/backend/handlers/createRelationship.go new file mode 100644 index 0000000..71b28eb --- /dev/null +++ b/backend/handlers/createRelationship.go @@ -0,0 +1,32 @@ +package handlers + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/vcscsvcscs/GenerationsHeritage/backend/memgraph" +) + +func CreateRelationship(driver neo4j.DriverWithContext) gin.HandlerFunc { + return func(c *gin.Context) { + var relationship memgraph.Relationship + if err := c.ShouldBindJSON(&relationship); err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + + return + } + + rec, err := relationship.CreateRelationship(driver) + if err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) + + return + } + + c.JSON(http.StatusCreated, gin.H{"relationship": rec.AsMap()}) + } +} diff --git a/backend/memgraph/createRelationship.go b/backend/memgraph/createRelationship.go new file mode 100644 index 0000000..8b7de6f --- /dev/null +++ b/backend/memgraph/createRelationship.go @@ -0,0 +1,39 @@ +package memgraph + +import ( + "fmt" + "time" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "golang.org/x/net/context" +) + +func (r *Relationship) CreateRelationship(driver neo4j.DriverWithContext) (*neo4j.Record, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) + defer session.Close(ctx) + + if err := r.Verify(); err != nil { + return nil, err + } + + query := fmt.Sprintf(`MATCH (a:Person), (b:Person) + WHERE a.ID = %s AND b.ID = %s`, r.FirstPersonID, r.SecondPersonID) + + if r.Direction == "->" { + query = fmt.Sprintf(`%s CREATE (a)-[r:%s {verified: false}]->(b) RETURN r;`, query, r.Relationship) + } else if r.Direction == "<-" { + query = fmt.Sprintf(`%s CREATE (a)<-[r:%s {verified: false}]-(b) RETURN r;`, query, r.Relationship) + } else { + query = fmt.Sprintf(`%s CREATE (a)-[r:%s {verified: false}]->(b) RETURN r;`, query, r.Relationship) + } + + result, err := session.Run(ctx, query, nil) + if err != nil { + return nil, err + } + + return result.Single(ctx) +} From d49601b87197b2eecd5990123e2d1a8b6aaa2845 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 15 Apr 2024 23:46:32 +0200 Subject: [PATCH 13/44] fix logging in createPerson --- backend/handlers/createPerson.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/handlers/createPerson.go b/backend/handlers/createPerson.go index 9a14dd2..c1db94a 100644 --- a/backend/handlers/createPerson.go +++ b/backend/handlers/createPerson.go @@ -25,6 +25,8 @@ func CreatePerson(driver neo4j.DriverWithContext) gin.HandlerFunc { if c.Request.Body == nil || c.ContentType() != "application/json" { log.Printf("ip: %s error: request body is empty or content type is not application/json", c.ClientIP()) c.JSON(http.StatusBadRequest, gin.H{"error": "content type must be application/json and request body must not be empty"}) + + return } var person memgraph.Person @@ -32,15 +34,23 @@ func CreatePerson(driver neo4j.DriverWithContext) gin.HandlerFunc { if err != nil { log.Printf("ip: %s error: %s", c.ClientIP(), err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) + + return } person.ID = uuid.New().String() + if person.Verify() != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid person information"}) + + return + } query := fmt.Sprintf("CREATE (n:Person {%s}) RETURN n;", person.ToString()) result, err := session.Run(ctx, query, nil) if err != nil { - log.Println(err) + log.Printf("ip: %s error: %s", c.ClientIP(), err) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return @@ -48,7 +58,7 @@ func CreatePerson(driver neo4j.DriverWithContext) gin.HandlerFunc { rec, err := result.Single(ctx) if err != nil { - log.Println(err) + log.Printf("ip: %s error: %s", c.ClientIP(), err) c.JSON(http.StatusNotFound, gin.H{"error": "could not find person with information provided"}) return From 162fe470516700f527a3ad36ca45657d86150dce Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 15 Apr 2024 23:47:25 +0200 Subject: [PATCH 14/44] create handler files --- backend/handlers/deletePerson.go | 1 + backend/handlers/deleteRelationship.go | 1 + backend/handlers/updatePerson.go | 1 + backend/handlers/verifyRelationship.go | 12 ++++++++++++ backend/handlers/viewFamilyTree.go | 1 + 5 files changed, 16 insertions(+) create mode 100644 backend/handlers/deletePerson.go create mode 100644 backend/handlers/deleteRelationship.go create mode 100644 backend/handlers/updatePerson.go create mode 100644 backend/handlers/verifyRelationship.go create mode 100644 backend/handlers/viewFamilyTree.go diff --git a/backend/handlers/deletePerson.go b/backend/handlers/deletePerson.go new file mode 100644 index 0000000..28ae6f5 --- /dev/null +++ b/backend/handlers/deletePerson.go @@ -0,0 +1 @@ +package handlers \ No newline at end of file diff --git a/backend/handlers/deleteRelationship.go b/backend/handlers/deleteRelationship.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/backend/handlers/deleteRelationship.go @@ -0,0 +1 @@ +package handlers diff --git a/backend/handlers/updatePerson.go b/backend/handlers/updatePerson.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/backend/handlers/updatePerson.go @@ -0,0 +1 @@ +package handlers diff --git a/backend/handlers/verifyRelationship.go b/backend/handlers/verifyRelationship.go new file mode 100644 index 0000000..65ed679 --- /dev/null +++ b/backend/handlers/verifyRelationship.go @@ -0,0 +1,12 @@ +package handlers + +import ( + "github.com/gin-gonic/gin" + "github.com/neo4j/neo4j-go-driver/v5/neo4j" +) + +func VerifyRelationship(driver neo4j.DriverWithContext) gin.HandlerFunc { + return func(c *gin.Context) { + + } +} diff --git a/backend/handlers/viewFamilyTree.go b/backend/handlers/viewFamilyTree.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/backend/handlers/viewFamilyTree.go @@ -0,0 +1 @@ +package handlers From ca67dead2bb0ebba82d5a9191e3a1bc4f63c8452 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 15 Apr 2024 23:56:52 +0200 Subject: [PATCH 15/44] add createRelationship as a handler --- backend/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/main.go b/backend/main.go index 660d818..ba057d0 100644 --- a/backend/main.go +++ b/backend/main.go @@ -46,6 +46,7 @@ func main() { router.GET("/health", hc.HealthCheckHandler()) router.GET("/person", handlers.ViewPerson(memgraphDriver)) router.POST("/createPerson", handlers.CreatePerson(memgraphDriver)) + router.POST("/createRelationship", handlers.CreateRelationship(memgraphDriver)) server := utilities.SetupHttpsServer(router, *cert, *key, *httpsPort, *httpPort, requestTimeout) From 2e4cd879b2f3f80a9d74e86086af07bb26e21d35 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Tue, 16 Apr 2024 22:26:55 +0200 Subject: [PATCH 16/44] delete relationship --- backend/handlers/deleteRelationship.go | 31 ++++++++++++++++++++++ backend/memgraph/deleteRelationship.go | 36 ++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 backend/memgraph/deleteRelationship.go diff --git a/backend/handlers/deleteRelationship.go b/backend/handlers/deleteRelationship.go index 5ac8282..97a12bf 100644 --- a/backend/handlers/deleteRelationship.go +++ b/backend/handlers/deleteRelationship.go @@ -1 +1,32 @@ package handlers + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/vcscsvcscs/GenerationsHeritage/backend/memgraph" +) + +func DelteRelationship(driver neo4j.DriverWithContext) gin.HandlerFunc { + return func(c *gin.Context) { + var relationship memgraph.Relationship + if err := c.ShouldBindJSON(&relationship); err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + + return + } + + err := relationship.DeleteRelationship(driver) + if err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) + + return + } + + c.JSON(http.StatusAccepted, gin.H{"status": "relationship deleted successfully"}) + } +} diff --git a/backend/memgraph/deleteRelationship.go b/backend/memgraph/deleteRelationship.go new file mode 100644 index 0000000..c59943c --- /dev/null +++ b/backend/memgraph/deleteRelationship.go @@ -0,0 +1,36 @@ +package memgraph + +import ( + "fmt" + "time" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "golang.org/x/net/context" +) + +func (r *Relationship) DeleteRelationship(driver neo4j.DriverWithContext) error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) + defer session.Close(ctx) + + if err := r.Verify(); err != nil { + return err + } + + query := "" + if r.Direction == "->" { + query = fmt.Sprintf(`MATCH (a)-[r:%s]->(b)`, r.Relationship) + } else if r.Direction == "<-" { + query = fmt.Sprintf(`MATCH (a)<-[r:%s]-(b)`, r.Relationship) + } else { + query = fmt.Sprintf(`MATCH (a)-[r:%s]-(b)`, r.Relationship) + } + + query = fmt.Sprintf(`%s WHERE a.ID = %s AND b.ID = %s DELETE r;`, query, r.FirstPersonID, r.SecondPersonID) + + _, err := session.Run(ctx, query, nil) + + return err +} From 47b52d8a339486e7a8c27f2e5a5871149a0888ec Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Tue, 16 Apr 2024 22:27:11 +0200 Subject: [PATCH 17/44] fix multi direction relationships --- backend/memgraph/createRelationship.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/memgraph/createRelationship.go b/backend/memgraph/createRelationship.go index 8b7de6f..2a00204 100644 --- a/backend/memgraph/createRelationship.go +++ b/backend/memgraph/createRelationship.go @@ -27,7 +27,7 @@ func (r *Relationship) CreateRelationship(driver neo4j.DriverWithContext) (*neo4 } else if r.Direction == "<-" { query = fmt.Sprintf(`%s CREATE (a)<-[r:%s {verified: false}]-(b) RETURN r;`, query, r.Relationship) } else { - query = fmt.Sprintf(`%s CREATE (a)-[r:%s {verified: false}]->(b) RETURN r;`, query, r.Relationship) + query = fmt.Sprintf(`%s CREATE (a)-[r:%s {verified: false}]-(b) RETURN r;`, query, r.Relationship) } result, err := session.Run(ctx, query, nil) From 72f81214becd576ceeffed2075e373f996fbd101 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Tue, 16 Apr 2024 22:45:07 +0200 Subject: [PATCH 18/44] use snake case file naming convention --- backend/handlers/createPerson.go | 23 ++------------ backend/memgraph/create_person.go | 30 +++++++++++++++++++ ...Relationship.go => create_relationship.go} | 2 +- ...Relationship.go => delete_relationship.go} | 2 +- 4 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 backend/memgraph/create_person.go rename backend/memgraph/{createRelationship.go => create_relationship.go} (97%) rename backend/memgraph/{deleteRelationship.go => delete_relationship.go} (97%) diff --git a/backend/handlers/createPerson.go b/backend/handlers/createPerson.go index c1db94a..4f668b2 100644 --- a/backend/handlers/createPerson.go +++ b/backend/handlers/createPerson.go @@ -1,12 +1,9 @@ package handlers import ( - "context" "encoding/json" - "fmt" "log" "net/http" - "time" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -16,12 +13,6 @@ import ( func CreatePerson(driver neo4j.DriverWithContext) gin.HandlerFunc { return func(c *gin.Context) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) - defer session.Close(ctx) - if c.Request.Body == nil || c.ContentType() != "application/json" { log.Printf("ip: %s error: request body is empty or content type is not application/json", c.ClientIP()) c.JSON(http.StatusBadRequest, gin.H{"error": "content type must be application/json and request body must not be empty"}) @@ -46,20 +37,10 @@ func CreatePerson(driver neo4j.DriverWithContext) gin.HandlerFunc { return } - query := fmt.Sprintf("CREATE (n:Person {%s}) RETURN n;", person.ToString()) - - result, err := session.Run(ctx, query, nil) + rec, err := person.CreatePerson(driver) if err != nil { log.Printf("ip: %s error: %s", c.ClientIP(), err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) - - return - } - - rec, err := result.Single(ctx) - if err != nil { - log.Printf("ip: %s error: %s", c.ClientIP(), err) - c.JSON(http.StatusNotFound, gin.H{"error": "could not find person with information provided"}) + c.JSON(http.StatusNotFound, gin.H{"error": "could not create person with information provided"}) return } diff --git a/backend/memgraph/create_person.go b/backend/memgraph/create_person.go new file mode 100644 index 0000000..e9124c7 --- /dev/null +++ b/backend/memgraph/create_person.go @@ -0,0 +1,30 @@ +package memgraph + +import ( + "fmt" + "time" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "golang.org/x/net/context" +) + +func (p *Person) CreatePerson(driver neo4j.DriverWithContext) (*neo4j.Record, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) + defer session.Close(ctx) + + if err := p.Verify(); err != nil { + return nil, err + } + + query := fmt.Sprintf("CREATE (n:Person {%s}) RETURN n;", p.ToString()) + + result, err := session.Run(ctx, query, nil) + if err != nil { + return nil, err + } + + return result.Single(ctx) +} diff --git a/backend/memgraph/createRelationship.go b/backend/memgraph/create_relationship.go similarity index 97% rename from backend/memgraph/createRelationship.go rename to backend/memgraph/create_relationship.go index 2a00204..0054a9f 100644 --- a/backend/memgraph/createRelationship.go +++ b/backend/memgraph/create_relationship.go @@ -12,7 +12,7 @@ func (r *Relationship) CreateRelationship(driver neo4j.DriverWithContext) (*neo4 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) defer session.Close(ctx) if err := r.Verify(); err != nil { diff --git a/backend/memgraph/deleteRelationship.go b/backend/memgraph/delete_relationship.go similarity index 97% rename from backend/memgraph/deleteRelationship.go rename to backend/memgraph/delete_relationship.go index c59943c..9555177 100644 --- a/backend/memgraph/deleteRelationship.go +++ b/backend/memgraph/delete_relationship.go @@ -12,7 +12,7 @@ func (r *Relationship) DeleteRelationship(driver neo4j.DriverWithContext) error ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) defer session.Close(ctx) if err := r.Verify(); err != nil { From f10d8a87dbf3510ba422c4e9239fea854e353ab8 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Tue, 16 Apr 2024 22:59:35 +0200 Subject: [PATCH 19/44] verify relationship --- backend/handlers/verifyRelationship.go | 20 +++++++++++++ backend/memgraph/verify_relationship.go | 39 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 backend/memgraph/verify_relationship.go diff --git a/backend/handlers/verifyRelationship.go b/backend/handlers/verifyRelationship.go index 65ed679..d347d80 100644 --- a/backend/handlers/verifyRelationship.go +++ b/backend/handlers/verifyRelationship.go @@ -1,12 +1,32 @@ package handlers import ( + "log" + "net/http" + "github.com/gin-gonic/gin" "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/vcscsvcscs/GenerationsHeritage/backend/memgraph" ) func VerifyRelationship(driver neo4j.DriverWithContext) gin.HandlerFunc { return func(c *gin.Context) { + var relationship memgraph.Relationship + if err := c.ShouldBindJSON(&relationship); err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + rec, err := relationship.VerifyRelationship(driver) + if err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) + + return + } + + c.JSON(http.StatusOK, gin.H{"relationship": rec.AsMap()}) } } diff --git a/backend/memgraph/verify_relationship.go b/backend/memgraph/verify_relationship.go new file mode 100644 index 0000000..5e302af --- /dev/null +++ b/backend/memgraph/verify_relationship.go @@ -0,0 +1,39 @@ +package memgraph + +import ( + "fmt" + "time" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "golang.org/x/net/context" +) + +func (r *Relationship) VerifyRelationship(driver neo4j.DriverWithContext) (*neo4j.Record, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) + defer session.Close(ctx) + + if err := r.Verify(); err != nil { + return nil, err + } + + query := "" + if r.Direction == "->" { + query = fmt.Sprintf(`MATCH (a)-[r:%s]->(b)`, r.Relationship) + } else if r.Direction == "<-" { + query = fmt.Sprintf(`MATCH (a)<-[r:%s]-(b)`, r.Relationship) + } else { + query = fmt.Sprintf(`MATCH (a)-[r:%s]-(b)`, r.Relationship) + } + + query = fmt.Sprintf(`%s WHERE a.ID = %s AND b.ID = %s set r.verified = true return r;`, query, r.FirstPersonID, r.SecondPersonID) + + result, err := session.Run(ctx, query, nil) + if err != nil { + return nil, err + } + + return result.Single(ctx) +} From 68bd7dec1176344d1aec7bdfb9c76a053a971f59 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Tue, 16 Apr 2024 23:00:06 +0200 Subject: [PATCH 20/44] update person to string --- backend/memgraph/model.go | 40 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/backend/memgraph/model.go b/backend/memgraph/model.go index 84e282b..68a12f7 100644 --- a/backend/memgraph/model.go +++ b/backend/memgraph/model.go @@ -37,9 +37,43 @@ type Person struct { } func (p *Person) ToString() string { - result := fmt.Sprintf("ID: '%s', Firstname: '%s', Lastname: '%s', Middlename: '%s', MothersFirstname: '%s', MothersLastname: '%s'", p.ID, p.Firstname, p.Lastname, p.Middlename, p.MothersFirstname, p.MothersLastname) - result = fmt.Sprintf("%s, Born: date({year:%d, month:%d, day:%d}), Death: date({year:%d, month:%d, day:%d})", result, p.Born.Year(), p.Born.Month(), p.Born.Day(), p.Death.Year(), p.Death.Month(), p.Death.Day()) - result = fmt.Sprintf("%s, Birthplace: '%s', Residence: '%s', Deathplace: '%s', OccupationToDisplay: '%s', ProfilePicture: '%s'", result, p.Birthplace, p.Residence, p.Deathplace, p.OccupationToDisplay, p.ProfilePicture) + result := fmt.Sprintf("ID: '%s'", p.ID) + if p.Firstname != "" { + result = fmt.Sprintf("%s, Firstname: '%s'", result, p.Firstname) + } + if p.Lastname != "" { + result = fmt.Sprintf("%s, Lastname: '%s'", result, p.Lastname) + } + if p.Middlename != "" { + result = fmt.Sprintf("%s, Middlename: '%s'", result, p.Middlename) + } + if p.MothersFirstname != "" { + result = fmt.Sprintf("%s, MothersFirstname: '%s'", result, p.MothersFirstname) + } + if p.MothersLastname != "" { + result = fmt.Sprintf("%s, MothersLastname: '%s'", result, p.MothersLastname) + } + if !p.Born.IsZero() { + result = fmt.Sprintf("%s, Born: date({year:%d, month:%d, day:%d})", result, p.Born.Year(), p.Born.Month(), p.Born.Day()) + } + if !p.Death.IsZero() { + result = fmt.Sprintf("%s, Death: date({year:%d, month:%d, day:%d})", result, p.Death.Year(), p.Death.Month(), p.Death.Day()) + } + if p.Birthplace != "" { + result = fmt.Sprintf("%s, Birthplace: '%s'", result, p.Birthplace) + } + if p.Residence != "" { + result = fmt.Sprintf("%s, Residence: '%s'", result, p.Residence) + } + if p.Deathplace != "" { + result = fmt.Sprintf("%s, Deathplace: '%s'", result, p.Deathplace) + } + if p.OccupationToDisplay != "" { + result = fmt.Sprintf("%s, OccupationToDisplay: '%s'", result, p.OccupationToDisplay) + } + if p.ProfilePicture != "" { + result = fmt.Sprintf("%s, ProfilePicture: '%s'", result, p.ProfilePicture) + } if p.Titles != nil && len(p.Titles) > 0 { result = fmt.Sprintf("%s, Titles: [", result) From 7358ef5db15cadd7a795f0731d61709a67f6087f Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Tue, 16 Apr 2024 23:12:54 +0200 Subject: [PATCH 21/44] delete person --- backend/handlers/deletePerson.go | 49 ++++++++++++++++++++++++++++++- backend/memgraph/delete_person.go | 27 +++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 backend/memgraph/delete_person.go diff --git a/backend/handlers/deletePerson.go b/backend/handlers/deletePerson.go index 28ae6f5..48becd0 100644 --- a/backend/handlers/deletePerson.go +++ b/backend/handlers/deletePerson.go @@ -1 +1,48 @@ -package handlers \ No newline at end of file +package handlers + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/vcscsvcscs/GenerationsHeritage/backend/memgraph" +) + +func DeletePerson(driver neo4j.DriverWithContext) gin.HandlerFunc { + return func(c *gin.Context) { + if c.Request.Body == nil || c.ContentType() != "application/json" { + log.Printf("ip: %s error: request body is empty or content type is not application/json", c.ClientIP()) + c.JSON(http.StatusBadRequest, gin.H{"error": "content type must be application/json and request body must not be empty"}) + + return + } + + var person memgraph.Person + err := json.NewDecoder(c.Request.Body).Decode(&person) + if err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) + + return + } + + if person.ID != "" { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusBadRequest, gin.H{"error": "no person ID provided"}) + + return + } + + err = person.DeletePerson(driver) + if err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusNotFound, gin.H{"error": "could not delete person with ID provided"}) + + return + } + + c.JSON(http.StatusOK, gin.H{"status": "person deleted successfully"}) + } +} diff --git a/backend/memgraph/delete_person.go b/backend/memgraph/delete_person.go new file mode 100644 index 0000000..2f0cf50 --- /dev/null +++ b/backend/memgraph/delete_person.go @@ -0,0 +1,27 @@ +package memgraph + +import ( + "fmt" + "time" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "golang.org/x/net/context" +) + +func (p *Person) DeletePerson(driver neo4j.DriverWithContext) error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) + defer session.Close(ctx) + + if err := p.Verify(); err != nil { + return err + } + + query := fmt.Sprintf("MATCH (n:Person {ID: '%s'}) DELETE n;", p.ID) + + _, err := session.Run(ctx, query, nil) + + return err +} From 5d19dad30f18805ad7c64fd43887e67e3a2b976a Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Tue, 16 Apr 2024 23:13:08 +0200 Subject: [PATCH 22/44] change status code --- backend/handlers/createPerson.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/handlers/createPerson.go b/backend/handlers/createPerson.go index 4f668b2..8b7177c 100644 --- a/backend/handlers/createPerson.go +++ b/backend/handlers/createPerson.go @@ -45,6 +45,6 @@ func CreatePerson(driver neo4j.DriverWithContext) gin.HandlerFunc { return } - c.JSON(200, gin.H{"person": rec.AsMap()}) + c.JSON(http.StatusCreated, gin.H{"person": rec.AsMap()}) } } From eadfcd7afc1e9a159e88c88aef81e827f94c40f4 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Tue, 16 Apr 2024 23:13:16 +0200 Subject: [PATCH 23/44] update person --- backend/handlers/updatePerson.go | 47 +++++++++++++++++++++++++++++++ backend/memgraph/update_person.go | 30 ++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 backend/memgraph/update_person.go diff --git a/backend/handlers/updatePerson.go b/backend/handlers/updatePerson.go index 5ac8282..f543df7 100644 --- a/backend/handlers/updatePerson.go +++ b/backend/handlers/updatePerson.go @@ -1 +1,48 @@ package handlers + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/vcscsvcscs/GenerationsHeritage/backend/memgraph" +) + +func UpdatePerson(driver neo4j.DriverWithContext) gin.HandlerFunc { + return func(c *gin.Context) { + if c.Request.Body == nil || c.ContentType() != "application/json" { + log.Printf("ip: %s error: request body is empty or content type is not application/json", c.ClientIP()) + c.JSON(http.StatusBadRequest, gin.H{"error": "content type must be application/json and request body must not be empty"}) + + return + } + + var person memgraph.Person + err := json.NewDecoder(c.Request.Body).Decode(&person) + if err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) + + return + } + + if person.ID != "" { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusBadRequest, gin.H{"error": "no person ID provided"}) + + return + } + + rec, err := person.UpdatePerson(driver) + if err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusNotFound, gin.H{"error": "could not update person with information provided"}) + + return + } + + c.JSON(http.StatusOK, gin.H{"person": rec.AsMap()}) + } +} diff --git a/backend/memgraph/update_person.go b/backend/memgraph/update_person.go new file mode 100644 index 0000000..27dcba8 --- /dev/null +++ b/backend/memgraph/update_person.go @@ -0,0 +1,30 @@ +package memgraph + +import ( + "fmt" + "time" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "golang.org/x/net/context" +) + +func (p *Person) UpdatePerson(driver neo4j.DriverWithContext) (*neo4j.Record, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) + defer session.Close(ctx) + + if err := p.Verify(); err != nil { + return nil, err + } + + query := fmt.Sprintf("MATCH (n:Person {ID: '%s'}) SET n += {%s} RETURN n;", p.ID, p.ToString()) + + result, err := session.Run(ctx, query, nil) + if err != nil { + return nil, err + } + + return result.Single(ctx) +} From 12bb08d6ce0b1592c45467ea8efb460a0d424402 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Tue, 16 Apr 2024 23:16:52 +0200 Subject: [PATCH 24/44] fix typo and add handlers to router --- backend/handlers/deleteRelationship.go | 2 +- backend/main.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/handlers/deleteRelationship.go b/backend/handlers/deleteRelationship.go index 97a12bf..160019a 100644 --- a/backend/handlers/deleteRelationship.go +++ b/backend/handlers/deleteRelationship.go @@ -9,7 +9,7 @@ import ( "github.com/vcscsvcscs/GenerationsHeritage/backend/memgraph" ) -func DelteRelationship(driver neo4j.DriverWithContext) gin.HandlerFunc { +func DeleteRelationship(driver neo4j.DriverWithContext) gin.HandlerFunc { return func(c *gin.Context) { var relationship memgraph.Relationship if err := c.ShouldBindJSON(&relationship); err != nil { diff --git a/backend/main.go b/backend/main.go index ba057d0..ffed0b5 100644 --- a/backend/main.go +++ b/backend/main.go @@ -46,7 +46,11 @@ func main() { router.GET("/health", hc.HealthCheckHandler()) router.GET("/person", handlers.ViewPerson(memgraphDriver)) router.POST("/createPerson", handlers.CreatePerson(memgraphDriver)) + router.DELETE("/deletePerson", handlers.DeletePerson(memgraphDriver)) + router.PUT("/updatePerson", handlers.UpdatePerson(memgraphDriver)) router.POST("/createRelationship", handlers.CreateRelationship(memgraphDriver)) + router.DELETE("/deleteRelationship", handlers.DeleteRelationship(memgraphDriver)) + router.PUT("/verifyRelationship", handlers.VerifyRelationship(memgraphDriver)) server := utilities.SetupHttpsServer(router, *cert, *key, *httpsPort, *httpPort, requestTimeout) From 40a70ecc93a211695973503e7d5438ffb5036313 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 21:01:01 +0200 Subject: [PATCH 25/44] Remove wrong driver close --- backend/memgraph/init_database.go | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/memgraph/init_database.go b/backend/memgraph/init_database.go index cdd4a72..ce9e55a 100644 --- a/backend/memgraph/init_database.go +++ b/backend/memgraph/init_database.go @@ -14,7 +14,6 @@ func InitDatabase(dbUri, dbUser, dbPassword string) neo4j.DriverWithContext { } ctx := context.Background() - defer driver.Close(ctx) err = driver.VerifyConnectivity(ctx) if err != nil { From 5e8cdecca7f1d5b82254ffdc9a5d6264361c43b4 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 21:01:33 +0200 Subject: [PATCH 26/44] change bolt+s protocol to bolt+ssc in default for memgraph --- backend/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/main.go b/backend/main.go index ffed0b5..f4c560a 100644 --- a/backend/main.go +++ b/backend/main.go @@ -21,7 +21,7 @@ var ( key = flag.String("key", "./private/keys/key.pem", "Specify the path of TLS key") httpsPort = flag.String("https", ":443", "Specify port for http secure hosting(example for format :443)") httpPort = flag.String("http", ":80", "Specify port for http hosting(example for format :80)") - memgraphURI = flag.String("memgraph", "bolt+s://memgraph:7687", "Specify the Memgraph database URI") + memgraphURI = flag.String("memgraph", "bolt+ssc://memgraph:7687", "Specify the Memgraph database URI") memgraphUser = flag.String("memgraph-user", "", "Specify the Memgraph database user") memgraphPass = flag.String("memgraph-pass", "", "Specify the Memgraph database password") release = flag.Bool("release", false, "Set true to release build") @@ -51,6 +51,7 @@ func main() { router.POST("/createRelationship", handlers.CreateRelationship(memgraphDriver)) router.DELETE("/deleteRelationship", handlers.DeleteRelationship(memgraphDriver)) router.PUT("/verifyRelationship", handlers.VerifyRelationship(memgraphDriver)) + router.POST("/createRelationshipAndPerson", handlers.CreateRelationshipAndPerson(memgraphDriver)) server := utilities.SetupHttpsServer(router, *cert, *key, *httpsPort, *httpPort, requestTimeout) From 5d03c510978e7cb93a71015d0e78583e6dbc9821 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 21:01:52 +0200 Subject: [PATCH 27/44] fix viewPerson query to be secure --- backend/handlers/viewPerson.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/handlers/viewPerson.go b/backend/handlers/viewPerson.go index 3ca6d81..3550dbd 100644 --- a/backend/handlers/viewPerson.go +++ b/backend/handlers/viewPerson.go @@ -2,7 +2,6 @@ package handlers import ( "context" - "fmt" "log" "net/http" "time" @@ -25,9 +24,8 @@ func ViewPerson(driver neo4j.DriverWithContext) gin.HandlerFunc { return } - query := fmt.Sprintf("MATCH (n:Person) WHERE n.ID = '%s' RETURN n;", id) - result, err := session.Run(ctx, query, nil) + result, err := session.Run(ctx, "MATCH (n:Person) WHERE n.ID = $person_id RETURN n;", map[string]any{"person_id": id}) if err != nil { log.Println(err) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) From a5822913f6d9b1530d615fe198914eaabcbe7181 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 21:02:04 +0200 Subject: [PATCH 28/44] create Relationship and person --- .../create_relationship_and_person.go | 32 +++++++++++++++ backend/memgraph/create_relationship.go | 3 +- .../create_relationship_and_person.go | 40 +++++++++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 backend/handlers/create_relationship_and_person.go create mode 100644 backend/memgraph/create_relationship_and_person.go diff --git a/backend/handlers/create_relationship_and_person.go b/backend/handlers/create_relationship_and_person.go new file mode 100644 index 0000000..7747ee3 --- /dev/null +++ b/backend/handlers/create_relationship_and_person.go @@ -0,0 +1,32 @@ +package handlers + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/vcscsvcscs/GenerationsHeritage/backend/memgraph" +) + +func CreateRelationshipAndPerson(driver neo4j.DriverWithContext) gin.HandlerFunc { + return func(c *gin.Context) { + var rp memgraph.RelationshipAndPerson + if err := c.ShouldBindJSON(&rp); err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + + return + } + + rec, err := rp.CreateRelationshipAndPerson(driver) + if err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) + + return + } + + c.JSON(http.StatusCreated, gin.H{"relationship": rec.AsMap()}) + } +} diff --git a/backend/memgraph/create_relationship.go b/backend/memgraph/create_relationship.go index 0054a9f..c466357 100644 --- a/backend/memgraph/create_relationship.go +++ b/backend/memgraph/create_relationship.go @@ -19,8 +19,7 @@ func (r *Relationship) CreateRelationship(driver neo4j.DriverWithContext) (*neo4 return nil, err } - query := fmt.Sprintf(`MATCH (a:Person), (b:Person) - WHERE a.ID = %s AND b.ID = %s`, r.FirstPersonID, r.SecondPersonID) + query := fmt.Sprintf(`MATCH (a:Person), (b:Person) WHERE a.ID = %s AND b.ID = %s`, r.FirstPersonID, r.SecondPersonID) if r.Direction == "->" { query = fmt.Sprintf(`%s CREATE (a)-[r:%s {verified: false}]->(b) RETURN r;`, query, r.Relationship) diff --git a/backend/memgraph/create_relationship_and_person.go b/backend/memgraph/create_relationship_and_person.go new file mode 100644 index 0000000..1b703d7 --- /dev/null +++ b/backend/memgraph/create_relationship_and_person.go @@ -0,0 +1,40 @@ +package memgraph + +import ( + "fmt" + "time" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "golang.org/x/net/context" +) + +func (rp *RelationshipAndPerson) CreateRelationshipAndPerson(driver neo4j.DriverWithContext) (*neo4j.Record, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) + defer session.Close(ctx) + + if err := rp.Verify(); err != nil { + return nil, err + } + + query := fmt.Sprintf(`MATCH (a:Person) WHERE a.ID = %s`, rp.Relationship.FirstPersonID) + + query = fmt.Sprintf("%s CREATE (b:Person {%s})", query, rp.Person.ToString()) + + if rp.Relationship.Direction == "->" { + query = fmt.Sprintf(`%s CREATE (a)-[r:%s {verified: True}]->(b) RETURN r;`, query, rp.Relationship.Relationship) + } else if rp.Relationship.Direction == "<-" { + query = fmt.Sprintf(`%s CREATE (a)<-[r:%s {verified: True}]-(b) RETURN r;`, query, rp.Relationship.Relationship) + } else { + query = fmt.Sprintf(`%s CREATE (a)-[r:%s {verified: True}]-(b) RETURN r;`, query, rp.Relationship.Relationship) + } + + result, err := session.Run(ctx, query, nil) + if err != nil { + return nil, err + } + + return result.Single(ctx) +} From 56607b31e519b149851649daf5257e0309528d84 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 22:35:02 +0200 Subject: [PATCH 29/44] correct typo --- backend/memgraph/create_schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/memgraph/create_schema.go b/backend/memgraph/create_schema.go index 75abe49..4c0af0a 100644 --- a/backend/memgraph/create_schema.go +++ b/backend/memgraph/create_schema.go @@ -47,7 +47,7 @@ func createConstraints(driver neo4j.DriverWithContext) error { constraints := []string{ `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.ID);`, `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Lastname);`, - `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Firstame);`, + `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Firstname);`, `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.Born);`, `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.MothersFirstname);`, `CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.MothersLastname);`, From 913042d441605a6f0751706c8d9b905c09014dda Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 22:35:32 +0200 Subject: [PATCH 30/44] add error logging and modify return --- backend/handlers/viewPerson.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/handlers/viewPerson.go b/backend/handlers/viewPerson.go index 3550dbd..3e7c3a1 100644 --- a/backend/handlers/viewPerson.go +++ b/backend/handlers/viewPerson.go @@ -27,7 +27,7 @@ func ViewPerson(driver neo4j.DriverWithContext) gin.HandlerFunc { result, err := session.Run(ctx, "MATCH (n:Person) WHERE n.ID = $person_id RETURN n;", map[string]any{"person_id": id}) if err != nil { - log.Println(err) + log.Printf("ip: %s error: %s", c.ClientIP(), err) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return @@ -35,12 +35,12 @@ func ViewPerson(driver neo4j.DriverWithContext) gin.HandlerFunc { rec, err := result.Single(ctx) if err != nil { - log.Println(err) + log.Printf("ip: %s error: %s", c.ClientIP(), err) c.JSON(http.StatusNotFound, gin.H{"error": "could not find person with information provided"}) return } - c.JSON(200, gin.H{"person": rec.AsMap()}) + c.JSON(200, rec.AsMap()["n"]) } } From d85d37eb2d5091925b44dca28f1b6d43267700f4 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 22:35:48 +0200 Subject: [PATCH 31/44] Change responses --- backend/handlers/createPerson.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/backend/handlers/createPerson.go b/backend/handlers/createPerson.go index 8b7177c..a6cb139 100644 --- a/backend/handlers/createPerson.go +++ b/backend/handlers/createPerson.go @@ -6,7 +6,6 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/google/uuid" "github.com/neo4j/neo4j-go-driver/v5/neo4j" "github.com/vcscsvcscs/GenerationsHeritage/backend/memgraph" ) @@ -23,24 +22,23 @@ func CreatePerson(driver neo4j.DriverWithContext) gin.HandlerFunc { var person memgraph.Person err := json.NewDecoder(c.Request.Body).Decode(&person) if err != nil { - log.Printf("ip: %s error: %s", c.ClientIP(), err) + log.Printf("ip: %s error: %s", c.ClientIP(), err.Error()) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } - person.ID = uuid.New().String() - if person.Verify() != nil { - log.Printf("ip: %s error: %s", c.ClientIP(), err) - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid person information"}) + if err := person.Verify(); err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err.Error()) + c.JSON(http.StatusBadRequest, gin.H{"error": "contains-forbidden-characters"}) return } rec, err := person.CreatePerson(driver) if err != nil { - log.Printf("ip: %s error: %s", c.ClientIP(), err) - c.JSON(http.StatusNotFound, gin.H{"error": "could not create person with information provided"}) + log.Printf("ip: %s error: %s", c.ClientIP(), err.Error()) + c.JSON(http.StatusBadRequest, gin.H{"error": "already-exists"}) return } From 564ef322e38b091dc1f27de39c404bc772d190f0 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 22:36:06 +0200 Subject: [PATCH 32/44] set uuid in a function after verify --- backend/memgraph/create_person.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/memgraph/create_person.go b/backend/memgraph/create_person.go index e9124c7..9c146ac 100644 --- a/backend/memgraph/create_person.go +++ b/backend/memgraph/create_person.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/google/uuid" "github.com/neo4j/neo4j-go-driver/v5/neo4j" "golang.org/x/net/context" ) @@ -19,6 +20,8 @@ func (p *Person) CreatePerson(driver neo4j.DriverWithContext) (*neo4j.Record, er return nil, err } + p.ID = uuid.New().String() + query := fmt.Sprintf("CREATE (n:Person {%s}) RETURN n;", p.ToString()) result, err := session.Run(ctx, query, nil) From 01c6e4b0c9812970fa300a1704c8cc9b9e7bc7ef Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 23:07:03 +0200 Subject: [PATCH 33/44] Fix update person --- backend/handlers/updatePerson.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/handlers/updatePerson.go b/backend/handlers/updatePerson.go index f543df7..ce094cb 100644 --- a/backend/handlers/updatePerson.go +++ b/backend/handlers/updatePerson.go @@ -28,9 +28,9 @@ func UpdatePerson(driver neo4j.DriverWithContext) gin.HandlerFunc { return } - if person.ID != "" { + if person.ID == "" { log.Printf("ip: %s error: %s", c.ClientIP(), err) - c.JSON(http.StatusBadRequest, gin.H{"error": "no person ID provided"}) + c.JSON(http.StatusBadRequest, gin.H{"error": "No ID provided"}) return } From e49aba7c58cdb089744c108b61d49e155aa44517 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 23:07:12 +0200 Subject: [PATCH 34/44] fix api --- backend/main.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/main.go b/backend/main.go index f4c560a..8ef18e4 100644 --- a/backend/main.go +++ b/backend/main.go @@ -45,12 +45,12 @@ func main() { router := gin.Default() router.GET("/health", hc.HealthCheckHandler()) router.GET("/person", handlers.ViewPerson(memgraphDriver)) - router.POST("/createPerson", handlers.CreatePerson(memgraphDriver)) - router.DELETE("/deletePerson", handlers.DeletePerson(memgraphDriver)) - router.PUT("/updatePerson", handlers.UpdatePerson(memgraphDriver)) - router.POST("/createRelationship", handlers.CreateRelationship(memgraphDriver)) - router.DELETE("/deleteRelationship", handlers.DeleteRelationship(memgraphDriver)) - router.PUT("/verifyRelationship", handlers.VerifyRelationship(memgraphDriver)) + router.POST("/person", handlers.CreatePerson(memgraphDriver)) + router.DELETE("/person", handlers.DeletePerson(memgraphDriver)) + router.PUT("/person", handlers.UpdatePerson(memgraphDriver)) + router.POST("/relationship", handlers.CreateRelationship(memgraphDriver)) + router.DELETE("/relationship", handlers.DeleteRelationship(memgraphDriver)) + router.PUT("/relationship", handlers.VerifyRelationship(memgraphDriver)) router.POST("/createRelationshipAndPerson", handlers.CreateRelationshipAndPerson(memgraphDriver)) server := utilities.SetupHttpsServer(router, *cert, *key, *httpsPort, *httpPort, requestTimeout) From bc7cf7f1a6f57c40f46682ff39ec4c0859215303 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 23:08:35 +0200 Subject: [PATCH 35/44] create memgraph compatible uuid --- backend/memgraph/create_person.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/memgraph/create_person.go b/backend/memgraph/create_person.go index 9c146ac..d800efb 100644 --- a/backend/memgraph/create_person.go +++ b/backend/memgraph/create_person.go @@ -2,6 +2,7 @@ package memgraph import ( "fmt" + "strings" "time" "github.com/google/uuid" @@ -20,7 +21,7 @@ func (p *Person) CreatePerson(driver neo4j.DriverWithContext) (*neo4j.Record, er return nil, err } - p.ID = uuid.New().String() + p.ID = strings.ReplaceAll(uuid.New().String(), "-", "") query := fmt.Sprintf("CREATE (n:Person {%s}) RETURN n;", p.ToString()) From cf4b79c593c16a4e5b601084f2b7e90696fb7107 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 23:08:52 +0200 Subject: [PATCH 36/44] uuid to memgraph compatible --- backend/memgraph/create_relationship_and_person.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/memgraph/create_relationship_and_person.go b/backend/memgraph/create_relationship_and_person.go index 1b703d7..d1649f1 100644 --- a/backend/memgraph/create_relationship_and_person.go +++ b/backend/memgraph/create_relationship_and_person.go @@ -2,8 +2,10 @@ package memgraph import ( "fmt" + "strings" "time" + "github.com/google/uuid" "github.com/neo4j/neo4j-go-driver/v5/neo4j" "golang.org/x/net/context" ) @@ -19,6 +21,8 @@ func (rp *RelationshipAndPerson) CreateRelationshipAndPerson(driver neo4j.Driver return nil, err } + rp.Person.ID = strings.ReplaceAll(uuid.New().String(), "-", "") + query := fmt.Sprintf(`MATCH (a:Person) WHERE a.ID = %s`, rp.Relationship.FirstPersonID) query = fmt.Sprintf("%s CREATE (b:Person {%s})", query, rp.Person.ToString()) From 304552c2a599114560ea2fb9eb1c9e05b63854df Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 23:09:23 +0200 Subject: [PATCH 37/44] Add ' to query --- backend/memgraph/create_relationship.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/memgraph/create_relationship.go b/backend/memgraph/create_relationship.go index c466357..b55cda6 100644 --- a/backend/memgraph/create_relationship.go +++ b/backend/memgraph/create_relationship.go @@ -19,7 +19,7 @@ func (r *Relationship) CreateRelationship(driver neo4j.DriverWithContext) (*neo4 return nil, err } - query := fmt.Sprintf(`MATCH (a:Person), (b:Person) WHERE a.ID = %s AND b.ID = %s`, r.FirstPersonID, r.SecondPersonID) + query := fmt.Sprintf(`MATCH (a:Person), (b:Person) WHERE a.ID = '%s' AND b.ID = '%s'`, r.FirstPersonID, r.SecondPersonID) if r.Direction == "->" { query = fmt.Sprintf(`%s CREATE (a)-[r:%s {verified: false}]->(b) RETURN r;`, query, r.Relationship) From 0b0b138c16dbf43bd9afd323b67fe74ced702844 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 23:09:42 +0200 Subject: [PATCH 38/44] change errors to contain original --- backend/memgraph/cypher_verify_string.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/memgraph/cypher_verify_string.go b/backend/memgraph/cypher_verify_string.go index 2c67eab..af0420c 100644 --- a/backend/memgraph/cypher_verify_string.go +++ b/backend/memgraph/cypher_verify_string.go @@ -81,10 +81,10 @@ var cypherOperators = []string{ // cypherDelimiters contains the delimiters that need to be escaped in a string to prevent cypher injection keys are the delimiters that need to be escaped and values are the escaped delimiters var cypherDelimiters = map[string]string{ - "'": "\\'", - "\"": "\\\"", - "\\u0027": "\\\\u0027", - "\\u0022": "\\\\u0022", + "'": `\'`, + `"`: `\"`, + `\u0027`: `\\u0027`, + `\u0022`: "\\\\u0022", "`": "``", "\\u0060": "\\u0060\\u0060", } @@ -94,19 +94,19 @@ func VerifyString(s string) error { s = strings.ToUpper(s) for _, keyword := range cypherKeywords { if strings.Contains(s, keyword) { - return fmt.Errorf("invalid string contains cypher keyword: %s", keyword) + return fmt.Errorf("invalid string: %s contains cypher keyword: %s", s, keyword) } } for _, operator := range cypherOperators { if strings.Contains(s, operator) { - return fmt.Errorf("invalid string contains cypher operator: %s", operator) + return fmt.Errorf("invalid string: %s contains cypher operator: %s", s, operator) } } for key := range cypherDelimiters { if strings.Contains(s, key) { - return fmt.Errorf("invalid string contains cypher delimiter: %s", key) + return fmt.Errorf("invalid string: %s contains cypher delimiter: %s", s, key) } } From 5b9b6c53a65dfb3c4788ade7a5b1521abdc03282 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 23:10:00 +0200 Subject: [PATCH 39/44] Add ' to query --- backend/memgraph/delete_relationship.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/memgraph/delete_relationship.go b/backend/memgraph/delete_relationship.go index 9555177..0f50bf8 100644 --- a/backend/memgraph/delete_relationship.go +++ b/backend/memgraph/delete_relationship.go @@ -28,7 +28,7 @@ func (r *Relationship) DeleteRelationship(driver neo4j.DriverWithContext) error query = fmt.Sprintf(`MATCH (a)-[r:%s]-(b)`, r.Relationship) } - query = fmt.Sprintf(`%s WHERE a.ID = %s AND b.ID = %s DELETE r;`, query, r.FirstPersonID, r.SecondPersonID) + query = fmt.Sprintf(`%s WHERE a.ID = '%s' AND b.ID = '%s' DELETE r;`, query, r.FirstPersonID, r.SecondPersonID) _, err := session.Run(ctx, query, nil) From 77042ffdc5a04a03780a920731f9da790bf5dec6 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Thu, 18 Apr 2024 23:10:16 +0200 Subject: [PATCH 40/44] Add verified to Person --- backend/memgraph/model.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/memgraph/model.go b/backend/memgraph/model.go index 68a12f7..226c3dd 100644 --- a/backend/memgraph/model.go +++ b/backend/memgraph/model.go @@ -34,6 +34,7 @@ type Person struct { OthersSaid map[string]string `json:"others_said"` Photos map[string]string `json:"photos"` ProfilePicture string `json:"profile_picture"` + verified bool } func (p *Person) ToString() string { @@ -150,6 +151,9 @@ func (p *Person) ToString() string { // Verify checks if the person is valid and does not contain cypher injection it also escapes the delimiters contained in any of the strings func (p *Person) Verify() error { + if p.verified { + return nil + } if err := VerifyString(p.ID); err != nil { return fmt.Errorf("invalid ID type %s", err) } @@ -208,6 +212,8 @@ func (p *Person) Verify() error { p.Photos[key] = EscapeString(value) } + p.verified = true + return nil } From 3ade387d7d3d0ef166e76af1acfea4d9bab61702 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 22 Apr 2024 08:27:44 +0200 Subject: [PATCH 41/44] view family tree --- backend/handlers/viewFamilyTree.go | 54 ++++++++++++++++++++++++++++++ backend/main.go | 1 + 2 files changed, 55 insertions(+) diff --git a/backend/handlers/viewFamilyTree.go b/backend/handlers/viewFamilyTree.go index 5ac8282..e082dc4 100644 --- a/backend/handlers/viewFamilyTree.go +++ b/backend/handlers/viewFamilyTree.go @@ -1 +1,55 @@ package handlers + +import ( + "context" + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/neo4j/neo4j-go-driver/v5/neo4j" +) + +func ViewFamiliyTree(driver neo4j.DriverWithContext) gin.HandlerFunc { + return func(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) + defer session.Close(ctx) + + id := c.Query("id") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"}) + + return + } + + query := ` + MATCH (n:Person {ID: $person_id})-[p:Parent*1..]->(family:Person) + OPTIONAL MATCH (family)-[c:Child]->(children:Person) + WITH family, p, children, c, n + OPTIONAL MATCH (children)<-[p2:Parent]-(OtherParents:Person) + WITH family, p, children, c, OtherParents, p2,n + OPTIONAL MATCH (family)-[s:Spouse]-(spouse:Person) + RETURN family, p, children, c, OtherParents, p2, spouse, s, n` + + result, err := session.Run(ctx, query, map[string]any{"person_id": id}) + if err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) + + return + } + + rec, err := result.Single(ctx) + if err != nil { + log.Printf("ip: %s error: %s", c.ClientIP(), err) + c.JSON(http.StatusNotFound, gin.H{"error": "could not find family tree for person with id: " + id}) + + return + } + + c.JSON(200, rec.AsMap()["n"]) + } +} diff --git a/backend/main.go b/backend/main.go index 8ef18e4..ce1ca68 100644 --- a/backend/main.go +++ b/backend/main.go @@ -52,6 +52,7 @@ func main() { router.DELETE("/relationship", handlers.DeleteRelationship(memgraphDriver)) router.PUT("/relationship", handlers.VerifyRelationship(memgraphDriver)) router.POST("/createRelationshipAndPerson", handlers.CreateRelationshipAndPerson(memgraphDriver)) + router.GET("/familyTree", handlers.ViewFamiliyTree(memgraphDriver)) server := utilities.SetupHttpsServer(router, *cert, *key, *httpsPort, *httpPort, requestTimeout) From 22ca38ad8612b8eb58919694d15e1a2a99543db1 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 22 Apr 2024 08:29:04 +0200 Subject: [PATCH 42/44] fix bidirectional relationships --- backend/memgraph/create_relationship.go | 3 ++- backend/memgraph/create_relationship_and_person.go | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/memgraph/create_relationship.go b/backend/memgraph/create_relationship.go index b55cda6..34b1d20 100644 --- a/backend/memgraph/create_relationship.go +++ b/backend/memgraph/create_relationship.go @@ -26,7 +26,8 @@ func (r *Relationship) CreateRelationship(driver neo4j.DriverWithContext) (*neo4 } else if r.Direction == "<-" { query = fmt.Sprintf(`%s CREATE (a)<-[r:%s {verified: false}]-(b) RETURN r;`, query, r.Relationship) } else { - query = fmt.Sprintf(`%s CREATE (a)-[r:%s {verified: false}]-(b) RETURN r;`, query, r.Relationship) + query = fmt.Sprintf(`%s CREATE (a)<-[r1:%s {verified: True}]-(b) CREATE (a)-[r2:%s {verified: True}]->(b) RETURN r1, r2;`, + query, r.Relationship, r.Relationship) } result, err := session.Run(ctx, query, nil) diff --git a/backend/memgraph/create_relationship_and_person.go b/backend/memgraph/create_relationship_and_person.go index d1649f1..ae3828f 100644 --- a/backend/memgraph/create_relationship_and_person.go +++ b/backend/memgraph/create_relationship_and_person.go @@ -23,7 +23,7 @@ func (rp *RelationshipAndPerson) CreateRelationshipAndPerson(driver neo4j.Driver rp.Person.ID = strings.ReplaceAll(uuid.New().String(), "-", "") - query := fmt.Sprintf(`MATCH (a:Person) WHERE a.ID = %s`, rp.Relationship.FirstPersonID) + query := fmt.Sprintf(`MATCH (a:Person) WHERE a.ID = '%s'`, rp.Relationship.FirstPersonID) query = fmt.Sprintf("%s CREATE (b:Person {%s})", query, rp.Person.ToString()) @@ -32,7 +32,8 @@ func (rp *RelationshipAndPerson) CreateRelationshipAndPerson(driver neo4j.Driver } else if rp.Relationship.Direction == "<-" { query = fmt.Sprintf(`%s CREATE (a)<-[r:%s {verified: True}]-(b) RETURN r;`, query, rp.Relationship.Relationship) } else { - query = fmt.Sprintf(`%s CREATE (a)-[r:%s {verified: True}]-(b) RETURN r;`, query, rp.Relationship.Relationship) + query = fmt.Sprintf(`%s CREATE (a)<-[r1:%s {verified: True}]-(b) CREATE (a)-[r2:%s {verified: True}]->(b) RETURN r1, r2, b;`, + query, rp.Relationship.Relationship, rp.Relationship.Relationship) } result, err := session.Run(ctx, query, nil) From 5e871cb272c5d756922dd16d90d759bcbc645382 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 22 Apr 2024 08:37:21 +0200 Subject: [PATCH 43/44] fix some miss conventions --- backend/memgraph/create_schema.go | 3 +-- backend/memgraph/init_database.go | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/memgraph/create_schema.go b/backend/memgraph/create_schema.go index 4c0af0a..87afa46 100644 --- a/backend/memgraph/create_schema.go +++ b/backend/memgraph/create_schema.go @@ -1,7 +1,6 @@ package memgraph import ( - "log" "time" "github.com/neo4j/neo4j-go-driver/v5/neo4j" @@ -30,7 +29,7 @@ func createIndexes(driver neo4j.DriverWithContext) error { for _, index := range indexes { _, err := session.Run(ctx, index, nil) if err != nil { - log.Panicln(err) + return err } } diff --git a/backend/memgraph/init_database.go b/backend/memgraph/init_database.go index ce9e55a..9a2f545 100644 --- a/backend/memgraph/init_database.go +++ b/backend/memgraph/init_database.go @@ -7,8 +7,8 @@ import ( "github.com/neo4j/neo4j-go-driver/v5/neo4j" ) -func InitDatabase(dbUri, dbUser, dbPassword string) neo4j.DriverWithContext { - driver, err := neo4j.NewDriverWithContext(dbUri, neo4j.BasicAuth(dbUser, dbPassword, "")) +func InitDatabase(dbURI, dbUser, dbPassword string) neo4j.DriverWithContext { + driver, err := neo4j.NewDriverWithContext(dbURI, neo4j.BasicAuth(dbUser, dbPassword, "")) if err != nil { log.Panicln(err) } From 5a7e62a183b24d58a71c2841d955a1302ddbb6d0 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Mon, 22 Apr 2024 08:38:33 +0200 Subject: [PATCH 44/44] update ci to only trigger once --- .github/workflows/auth-service-ci.yml | 4 ---- .github/workflows/backend-ci.yml | 3 --- .github/workflows/frontend-ci.yml | 4 ---- 3 files changed, 11 deletions(-) diff --git a/.github/workflows/auth-service-ci.yml b/.github/workflows/auth-service-ci.yml index 36749d8..a7bc90e 100644 --- a/.github/workflows/auth-service-ci.yml +++ b/.github/workflows/auth-service-ci.yml @@ -3,10 +3,6 @@ on: push: paths: - "auth-service/**" - pull_request: - paths: - - "auth-service/**" - jobs: lint: uses: ./.github/workflows/go_lint.yml diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 5ef7000..ebee273 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -3,9 +3,6 @@ on: push: paths: - "backend/**" - pull_request: - paths: - - "backend/**" jobs: lint: uses: ./.github/workflows/go_lint.yml diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index 6ca1c52..209ae60 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -3,10 +3,6 @@ on: push: paths: - "frontend/**" - pull_request: - paths: - - "frontend/**" - jobs: lint: uses: ./.github/workflows/svelte_lint.yml