fix folder name for db adapter

This commit is contained in:
2025-03-27 22:09:34 +01:00
parent a56454110a
commit 46077870c3
50 changed files with 2 additions and 1 deletions

View 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)
}

View 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")
}
}

View File

@@ -0,0 +1,52 @@
package healthcheck
import (
"net/http"
"sync"
"github.com/gin-gonic/gin"
)
type HealthCheck interface {
SetStatus(status string)
GetStatus() string
HealthCheckHandler(c *gin.Context)
}
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(c *gin.Context) {
switch hc.GetStatus() {
case "nok":
c.JSON(http.StatusServiceUnavailable, gin.H{
"msg": hc.GetStatus(),
})
default:
c.JSON(http.StatusOK, gin.H{
"msg": hc.GetStatus(),
})
}
}

View File

@@ -0,0 +1,134 @@
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) {
t.Parallel()
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: `{"msg":"ok"}`,
statusCode: http.StatusOK,
},
{
name: "Test respond with status nok",
args: args{
status: "nok",
},
want: `{"msg":"nok"}`,
statusCode: http.StatusServiceUnavailable,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
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)
}
})
}
}

View 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)
}

View 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)
}

View File

@@ -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)
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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)
}
})
}
}

View File

@@ -0,0 +1,4 @@
MATCH (a:Person), (b:Person)
WHERE id(a) = $id1 AND id(b) = $id2
CREATE (a)-[r:Relationship $Relationship]->(b)
RETURN r AS relationship

View File

@@ -0,0 +1 @@
CREATE (p:Person $Person) RETURN p as person

View File

@@ -0,0 +1,4 @@
MATCH (a:Person), (b:Person)
WHERE id(a) = $id1 AND id(b) = $id2
CREATE (a)-[r:Relationship $Relationship]-(b)
RETURN r as relationship

View File

@@ -0,0 +1,5 @@
MATCH (a:Person), (b:Person)
WHERE id(a) = $id1 AND id(b) = $id2
CREATE (a)-[r1:Relationship $Relationship1]->(b)
CREATE (b)-[r2:Relationship $Relationship2]->(a)
RETURN r1 as relationship1, r2 as relationship2

View File

@@ -0,0 +1,8 @@
MATCH (n:Person)-[p:Parent*1..]->(family:Person)
WHERE id(n) = $id
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;

View File

@@ -0,0 +1 @@
MATCH (n:Person) WHERE n.id = $id RETURN n AS people

View File

@@ -0,0 +1 @@
MATCH (n:Person) WHERE n.google_id = $google_id RETURN n as person

View File

@@ -0,0 +1 @@
MATCH (n:Person) WHERE id(n) = $id RETURN n as person

View File

@@ -0,0 +1,3 @@
MATCH (n)-[r]-(o)
WHERE id(n) = $id1 AND id(o) = $id2
RETURN r as relationship

View File

@@ -0,0 +1,3 @@
MATCH (n:DeletedPerson)
WHERE id(n) = $id
DETACH DELETE n;

View File

@@ -0,0 +1,14 @@
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.google_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;
CREATE INDEX ON :Person(google_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);

View File

@@ -0,0 +1,4 @@
MATCH (n:Person {id: $id})
SET n:DeletedPerson
REMOVE n:Person
RETURN labels(n) AS labels, n AS person

View File

@@ -0,0 +1,4 @@
MATCH (n:Person)
WHERE id(n) = $id
SET n += $props
RETURN n AS person

View 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)
}

View 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)
}

View 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
}