mirror of
https://github.com/vcscsvcscs/GenerationsHeritage.git
synced 2025-08-14 23:09:07 +02:00
restructure repo
This commit is contained in:
118
apps/db-handler/.golangci.yml
Normal file
118
apps/db-handler/.golangci.yml
Normal file
@@ -0,0 +1,118 @@
|
||||
linters-settings:
|
||||
depguard:
|
||||
rules:
|
||||
logger:
|
||||
deny:
|
||||
# logging is allowed only by logutils.Log,
|
||||
# logrus is allowed to use only in logutils package.
|
||||
- pkg: "github.com/sirupsen/logrus"
|
||||
desc: logging is allowed only by logutils.Log.
|
||||
- pkg: "github.com/pkg/errors"
|
||||
desc: Should be replaced by standard lib errors package.
|
||||
- pkg: "github.com/instana/testify"
|
||||
desc: It's a fork of github.com/stretchr/testify.
|
||||
dupl:
|
||||
threshold: 100
|
||||
funlen:
|
||||
lines: -1 # the number of lines (code + empty lines) is not a right metric and leads to code without empty line or one-liner.
|
||||
statements: 50
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 3
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- experimental
|
||||
- opinionated
|
||||
- performance
|
||||
- style
|
||||
disabled-checks:
|
||||
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
||||
- ifElseChain
|
||||
- octalLiteral
|
||||
- whyNoLint
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
gofmt:
|
||||
rewrite-rules:
|
||||
- pattern: 'interface{}'
|
||||
replacement: 'any'
|
||||
govet:
|
||||
settings:
|
||||
printf:
|
||||
funcs:
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
|
||||
enable:
|
||||
- nilness
|
||||
- shadow
|
||||
errorlint:
|
||||
asserts: false
|
||||
lll:
|
||||
line-length: 140
|
||||
misspell:
|
||||
locale: US
|
||||
nolintlint:
|
||||
allow-unused: false # report any unused nolint directives
|
||||
require-explanation: false # don't require an explanation for nolint directives
|
||||
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
|
||||
revive:
|
||||
rules:
|
||||
- name: unexported-return
|
||||
disabled: true
|
||||
- name: unused-parameter
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exportloopref
|
||||
- funlen
|
||||
- gocheckcompilerdirectives
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
- gomnd
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- noctx
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
|
||||
# don't enable:
|
||||
# - asciicheck
|
||||
# - gochecknoglobals
|
||||
# - gocognit
|
||||
# - godot
|
||||
# - godox
|
||||
# - goerr113
|
||||
# - nestif
|
||||
# - prealloc
|
||||
# - testpackage
|
||||
# - wsl
|
||||
|
||||
run:
|
||||
timeout: 5m
|
18
apps/db-handler/dockerfile
Normal file
18
apps/db-handler/dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN go get ./...
|
||||
|
||||
RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o backend
|
||||
|
||||
RUN apk update && apk add ca-certificates && update-ca-certificates
|
||||
|
||||
FROM --platform=$TARGETPLATFORM busybox:1.36.1
|
||||
|
||||
COPY --from=build /etc/ssl/certs /etc/ssl/certs
|
||||
COPY --from=build /app/backend /app/
|
||||
|
||||
CMD [ "/app/backend" ]
|
54
apps/db-handler/go.mod
Normal file
54
apps/db-handler/go.mod
Normal file
@@ -0,0 +1,54 @@
|
||||
module github.com/vcscsvcscs/GenerationsHeritage/apps/db-handler
|
||||
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/cors v1.7.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/neo4j/neo4j-go-driver/v5 v5.27.0
|
||||
golang.org/x/net v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.12.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // 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.23.0 // indirect
|
||||
github.com/goccy/go-json v0.10.4 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
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/muhlemmer/gu v0.3.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/zitadel/logging v0.6.1 // indirect
|
||||
github.com/zitadel/oidc/v3 v3.33.1 // indirect
|
||||
github.com/zitadel/schema v1.3.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.33.0 // indirect
|
||||
golang.org/x/arch v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
|
||||
golang.org/x/oauth2 v0.24.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.36.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
49
apps/db-handler/internal/api/createPerson.go
Normal file
49
apps/db-handler/internal/api/createPerson.go
Normal file
@@ -0,0 +1,49 @@
|
||||
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/pkg/memgraph"
|
||||
)
|
||||
|
||||
func CreatePerson(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.Error())
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
person.ID = c.GetString("id")
|
||||
rec, err := person.CreatePerson(driver)
|
||||
if err != nil {
|
||||
log.Printf("ip: %s error: %s", c.ClientIP(), err.Error())
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "already-exists"})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"person": rec.AsMap()})
|
||||
}
|
||||
}
|
32
apps/db-handler/internal/api/createRelationship.go
Normal file
32
apps/db-handler/internal/api/createRelationship.go
Normal file
@@ -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/pkg/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()})
|
||||
}
|
||||
}
|
@@ -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/pkg/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()})
|
||||
}
|
||||
}
|
48
apps/db-handler/internal/api/deletePerson.go
Normal file
48
apps/db-handler/internal/api/deletePerson.go
Normal file
@@ -0,0 +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/pkg/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"})
|
||||
}
|
||||
}
|
32
apps/db-handler/internal/api/deleteRelationship.go
Normal file
32
apps/db-handler/internal/api/deleteRelationship.go
Normal file
@@ -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/pkg/memgraph"
|
||||
)
|
||||
|
||||
func DeleteRelationship(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"})
|
||||
}
|
||||
}
|
3
apps/db-handler/internal/api/generate.go
Normal file
3
apps/db-handler/internal/api/generate.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package api
|
||||
|
||||
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../../../../openapi.json
|
6
apps/db-handler/internal/api/oapi-codegen-cfg.yaml
Normal file
6
apps/db-handler/internal/api/oapi-codegen-cfg.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json
|
||||
package: api
|
||||
output: api.gen.go
|
||||
generate:
|
||||
models: true
|
||||
gin-server: true
|
48
apps/db-handler/internal/api/updatePerson.go
Normal file
48
apps/db-handler/internal/api/updatePerson.go
Normal file
@@ -0,0 +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/pkg/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 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()})
|
||||
}
|
||||
}
|
32
apps/db-handler/internal/api/verifyRelationship.go
Normal file
32
apps/db-handler/internal/api/verifyRelationship.go
Normal file
@@ -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/pkg/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()})
|
||||
}
|
||||
}
|
55
apps/db-handler/internal/api/viewFamilyTree.go
Normal file
55
apps/db-handler/internal/api/viewFamilyTree.go
Normal file
@@ -0,0 +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.GetString("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.Collect(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)
|
||||
}
|
||||
}
|
50
apps/db-handler/internal/api/viewPerson.go
Normal file
50
apps/db-handler/internal/api/viewPerson.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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 == "" {
|
||||
id = c.GetString("id")
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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.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"})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, rec.AsMap()["n"])
|
||||
}
|
||||
}
|
106
apps/db-handler/main.go
Normal file
106
apps/db-handler/main.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/vcscsvcscs/GenerationsHeritage/cmd/backend/handlers"
|
||||
utilities "github.com/vcscsvcscs/GenerationsHeritage/pkg"
|
||||
"github.com/vcscsvcscs/GenerationsHeritage/pkg/gin/healthcheck"
|
||||
"github.com/vcscsvcscs/GenerationsHeritage/pkg/memgraph"
|
||||
//"github.com/zitadel/zitadel-go/v3/pkg/authorization"
|
||||
//"github.com/zitadel/zitadel-go/v3/pkg/authorization/oauth"
|
||||
//"github.com/zitadel/zitadel-go/v3/pkg/zitadel"
|
||||
)
|
||||
|
||||
var (
|
||||
cert = flag.String("cert", "/etc/gh-backend/ssl/tls.crt", "Specify the path of TLS cert")
|
||||
key = flag.String("key", "/etc/gh-backend/ssl/tls.key", "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)")
|
||||
// zitadelAccessKey = flag.String("zitadel-access-key", "/etc/gh-backend/zitadel/api-key.json", "Specify the path of Zitadel access key")
|
||||
// zitadelURI = flag.String("zitadel-uri", "zitadel.varghacsongor.hu", "Specify the Zitadel 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")
|
||||
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")
|
||||
requestTimeout = time.Duration(*flag.Int("request-timeout", 20, "Set request timeout in seconds"))
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *release {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
utilities.SetupLogger(*logToFileAndStd, *logToFile)
|
||||
|
||||
hc := healthcheck.New()
|
||||
|
||||
memgraphDriver := memgraph.InitDatabase(*memgraphURI, *memgraphUser, *memgraphPass)
|
||||
|
||||
router := gin.Default()
|
||||
router.Use(cors.New(cors.Config{
|
||||
AllowOrigins: []string{"http://localhost:5173", "http://localhost", "https://heritagebackend.varghacsongor.hu", "https://feature-add-frontend.generationsheritage.pages.dev/", "https://csalad.varghacsongor.hu/"},
|
||||
AllowCredentials: true,
|
||||
AllowHeaders: []string{"Authorization", "id", "Content-Type"},
|
||||
MaxAge: 12 * time.Hour,
|
||||
}))
|
||||
router.Use(gin.Recovery())
|
||||
|
||||
//ctx := context.Background()
|
||||
|
||||
// Initiate the authorization by providing a zitadel configuration and a verifier.
|
||||
// This example will use OAuth2 Introspection for this, therefore you will also need to provide the downloaded api key.json
|
||||
//authZ, err := authorization.New(ctx, zitadel.New(*zitadelURI), oauth.DefaultAuthorization(*zitadelAccessKey))
|
||||
//if err != nil {
|
||||
// log.Println("zitadel sdk could not initialize", "error", err)
|
||||
// os.Exit(1)
|
||||
//}
|
||||
|
||||
// Initialize the HTTP middleware by providing the authorization
|
||||
//mw := middleware.New(authZ)
|
||||
|
||||
//router.Use(auth(mw))
|
||||
router.GET("/health", hc.HealthCheckHandler())
|
||||
router.GET("/person", handlers.ViewPerson(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))
|
||||
router.GET("/familyTree", handlers.ViewFamiliyTree(memgraphDriver))
|
||||
|
||||
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)
|
||||
// kill (no param) default send syscall.SIGTERM
|
||||
// kill -2 is syscall.SIGINT
|
||||
// kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
||||
<-quit
|
||||
log.Println("Shutting down server...")
|
||||
|
||||
// The context is used to inform the server it has some seconds to finish
|
||||
// the request it is currently handling
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
log.Fatal("Server forced to shutdown:", err)
|
||||
}
|
||||
|
||||
log.Println("Server exiting")
|
||||
}
|
11
apps/db-handler/pkg/fileExists.go
Normal file
11
apps/db-handler/pkg/fileExists.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Checks if file on path exists or not
|
||||
func FileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
27
apps/db-handler/pkg/fileExists_test.go
Normal file
27
apps/db-handler/pkg/fileExists_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFileExists(t *testing.T) {
|
||||
// Create a temporary file for testing
|
||||
file, err := os.CreateTemp("", "testfile")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temporary file: %v", err)
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
// Test with an existing file
|
||||
exists := FileExists(file.Name())
|
||||
if !exists {
|
||||
t.Errorf("FileExists(%q) = false, want true", file.Name())
|
||||
}
|
||||
|
||||
// Test with a non-existing file
|
||||
exists = FileExists("non_existing_file.txt")
|
||||
if exists {
|
||||
t.Errorf("FileExists(%q) = true, want false", "non_existing_file.txt")
|
||||
}
|
||||
}
|
54
apps/db-handler/pkg/gin/healthcheck/health_check.go
Normal file
54
apps/db-handler/pkg/gin/healthcheck/health_check.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type HealthCheck interface {
|
||||
SetStatus(status string)
|
||||
GetStatus() string
|
||||
HealthCheckHandler() gin.HandlerFunc
|
||||
}
|
||||
|
||||
type healthCheck struct {
|
||||
status string
|
||||
sync sync.Mutex
|
||||
}
|
||||
|
||||
func New() HealthCheck {
|
||||
return &healthCheck{
|
||||
status: "ok",
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *healthCheck) SetStatus(status string) {
|
||||
hc.sync.Lock()
|
||||
defer hc.sync.Unlock()
|
||||
|
||||
hc.status = status
|
||||
}
|
||||
|
||||
func (hc *healthCheck) GetStatus() string {
|
||||
hc.sync.Lock()
|
||||
defer hc.sync.Unlock()
|
||||
|
||||
return hc.status
|
||||
}
|
||||
|
||||
func (hc *healthCheck) HealthCheckHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
switch hc.GetStatus() {
|
||||
case "nok":
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"status": hc.GetStatus(),
|
||||
})
|
||||
default:
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": hc.GetStatus(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
131
apps/db-handler/pkg/gin/healthcheck/health_check_test.go
Normal file
131
apps/db-handler/pkg/gin/healthcheck/health_check_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestHealthCheck_GetStatus(t *testing.T) {
|
||||
var hc HealthCheck
|
||||
|
||||
type fields struct {
|
||||
status string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Test GetStatus with status ok",
|
||||
fields: fields{
|
||||
status: "ok",
|
||||
},
|
||||
want: "ok",
|
||||
},
|
||||
{
|
||||
name: "Test GetStatus with status nok",
|
||||
fields: fields{
|
||||
status: "nok",
|
||||
},
|
||||
want: "nok",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
hc = &healthCheck{
|
||||
status: tt.fields.status,
|
||||
}
|
||||
if got := hc.GetStatus(); got != tt.want {
|
||||
t.Errorf("HealthCheck.GetStatus() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthCheck_SetStatus(t *testing.T) {
|
||||
hc := New()
|
||||
|
||||
type args struct {
|
||||
status string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Test SetStatus with status ok",
|
||||
args: args{
|
||||
status: "ok",
|
||||
},
|
||||
want: "ok",
|
||||
},
|
||||
{
|
||||
name: "Test SetStatus with status nok",
|
||||
args: args{
|
||||
status: "nok",
|
||||
},
|
||||
want: "nok",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
hc.SetStatus(tt.args.status)
|
||||
if got := hc.GetStatus(); got != tt.want {
|
||||
t.Errorf("HealthCheck.GetStatus() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthCheck_HealthCheckHandler(t *testing.T) {
|
||||
r := gin.Default()
|
||||
hc := New()
|
||||
r.GET("/health", hc.HealthCheckHandler())
|
||||
type args struct {
|
||||
status string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
statusCode int
|
||||
}{
|
||||
{
|
||||
name: "Test respond with status ok",
|
||||
args: args{
|
||||
status: "ok",
|
||||
},
|
||||
want: `{"status":"ok"}`,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "Test respond with status nok",
|
||||
args: args{
|
||||
status: "nok",
|
||||
},
|
||||
want: `{"status":"nok"}`,
|
||||
statusCode: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
hc.SetStatus(tt.args.status)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/health", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if got := w.Code; got != tt.statusCode {
|
||||
t.Errorf("HealthCheck response status code = %v, want %v", got, tt.statusCode)
|
||||
}
|
||||
if got := w.Body.String(); got != tt.want {
|
||||
t.Errorf("HealthCheck response = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
30
apps/db-handler/pkg/memgraph/create_person.go
Normal file
30
apps/db-handler/pkg/memgraph/create_person.go
Normal file
@@ -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(), 30*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)
|
||||
}
|
39
apps/db-handler/pkg/memgraph/create_relationship.go
Normal file
39
apps/db-handler/pkg/memgraph/create_relationship.go
Normal file
@@ -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.AccessModeWrite})
|
||||
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)<-[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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Single(ctx)
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
package memgraph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"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
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
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)<-[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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Single(ctx)
|
||||
}
|
66
apps/db-handler/pkg/memgraph/create_schema.go
Normal file
66
apps/db-handler/pkg/memgraph/create_schema.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package memgraph
|
||||
|
||||
import (
|
||||
"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(last_name);`,
|
||||
`CREATE INDEX ON :Person(first_name);`,
|
||||
`CREATE INDEX ON :Person(born);`,
|
||||
`CREATE INDEX ON :Person(mothers_first_name);`,
|
||||
`CREATE INDEX ON :Person(mothers_last_name);`,
|
||||
}
|
||||
|
||||
// Run index queries via implicit auto-commit transaction
|
||||
for _, index := range indexes {
|
||||
_, err := session.Run(ctx, index, nil)
|
||||
if err != nil {
|
||||
return 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.last_name);`,
|
||||
`CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.first_name);`,
|
||||
`CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.born);`,
|
||||
`CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.mothers_first_name);`,
|
||||
`CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.mothers_last_name);`,
|
||||
`CREATE CONSTRAINT ON (n:Person) ASSERT n.id IS UNIQUE;`,
|
||||
`CREATE CONSTRAINT ON (n:Person) ASSERT n.last_name, n.first_name, n.born, n.mothers_first_name, n.mothers_last_name 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
|
||||
}
|
124
apps/db-handler/pkg/memgraph/cypher_verify_string.go
Normal file
124
apps/db-handler/pkg/memgraph/cypher_verify_string.go
Normal file
@@ -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: %s contains cypher keyword: %s", s, keyword)
|
||||
}
|
||||
}
|
||||
|
||||
for _, operator := range cypherOperators {
|
||||
if strings.Contains(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: %s contains cypher delimiter: %s", 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
|
||||
}
|
27
apps/db-handler/pkg/memgraph/delete_person.go
Normal file
27
apps/db-handler/pkg/memgraph/delete_person.go
Normal file
@@ -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
|
||||
}
|
36
apps/db-handler/pkg/memgraph/delete_relationship.go
Normal file
36
apps/db-handler/pkg/memgraph/delete_relationship.go
Normal file
@@ -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.AccessModeWrite})
|
||||
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
|
||||
}
|
32
apps/db-handler/pkg/memgraph/init_database.go
Normal file
32
apps/db-handler/pkg/memgraph/init_database.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package memgraph
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"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, ""))
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err = driver.VerifyConnectivity(ctx)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := createIndexes(driver); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := createConstraints(driver); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
return driver
|
||||
}
|
272
apps/db-handler/pkg/memgraph/model.go
Normal file
272
apps/db-handler/pkg/memgraph/model.go
Normal file
@@ -0,0 +1,272 @@
|
||||
package memgraph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"`
|
||||
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"`
|
||||
verified bool
|
||||
}
|
||||
|
||||
func (p *Person) ToString() string {
|
||||
result := fmt.Sprintf("id: '%s'", p.ID)
|
||||
if p.Firstname != "" {
|
||||
result = fmt.Sprintf("%s, first_name: '%s'", result, p.Firstname)
|
||||
}
|
||||
if p.Lastname != "" {
|
||||
result = fmt.Sprintf("%s, last_name: '%s'", result, p.Lastname)
|
||||
}
|
||||
if p.Middlename != "" {
|
||||
result = fmt.Sprintf("%s, middle_name: '%s'", result, p.Middlename)
|
||||
}
|
||||
if p.MothersFirstname != "" {
|
||||
result = fmt.Sprintf("%s, mothers_first_name: '%s'", result, p.MothersFirstname)
|
||||
}
|
||||
if p.MothersLastname != "" {
|
||||
result = fmt.Sprintf("%s, mothers_last_name: '%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, occupation_to_display: '%s'", result, p.OccupationToDisplay)
|
||||
}
|
||||
if p.ProfilePicture != "" {
|
||||
result = fmt.Sprintf("%s, profile_picture: '%s'", result, 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, extra_names: [", 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, life_events: [", 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, others_said: {", 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
p.verified = true
|
||||
|
||||
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
|
||||
}
|
77
apps/db-handler/pkg/memgraph/model_test.go
Normal file
77
apps/db-handler/pkg/memgraph/model_test.go
Normal file
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
30
apps/db-handler/pkg/memgraph/update_person.go
Normal file
30
apps/db-handler/pkg/memgraph/update_person.go
Normal file
@@ -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)
|
||||
}
|
39
apps/db-handler/pkg/memgraph/verify_relationship.go
Normal file
39
apps/db-handler/pkg/memgraph/verify_relationship.go
Normal file
@@ -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)
|
||||
}
|
40
apps/db-handler/pkg/setup_https_server.go
Normal file
40
apps/db-handler/pkg/setup_https_server.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetupHttpsServer(router http.Handler, cert, key, httpsPort, httpPort string, requestTimeout time.Duration) (server *http.Server) {
|
||||
if FileExists(cert) && 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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
29
apps/db-handler/pkg/setup_logger.go
Normal file
29
apps/db-handler/pkg/setup_logger.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupLogger(logToFileAndStd bool, logToFile bool) {
|
||||
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)
|
||||
}
|
Reference in New Issue
Block a user