implement admin syste,

This commit is contained in:
2025-04-07 21:54:19 +02:00
parent 600f51ed1f
commit be05f2d895
16 changed files with 1701 additions and 99 deletions

View File

@@ -0,0 +1,130 @@
package api
import (
"context"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
"github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/api/auth"
"github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/memgraph"
"github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api"
)
func (srv *server) CreateAdminRelationship(c *gin.Context, id1 int, id2 int, params api.CreateAdminRelationshipParams) {
session := srv.db.NewSession(c.Request.Context(), neo4j.SessionConfig{})
defer closeSession(c.Request.Context(), session, srv.dbOpTimeout)
actx, aCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout)
defer aCancel()
if err := auth.CouldManagePerson(actx, session, id1, id2, params.XUserID); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"msg": fmt.Sprint("User does not have permissions to manage this person with error:", err.Error())})
return
}
qctx, qCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout)
defer qCancel()
res, err := session.ExecuteWrite(qctx, memgraph.CreateAdminRelationship(qctx, id1, id2))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
return
}
c.JSON(http.StatusOK, res)
}
func (srv *server) DeleteAdminRelationship(c *gin.Context, id1 int, id2 int, params api.DeleteAdminRelationshipParams) {
session := srv.db.NewSession(c.Request.Context(), neo4j.SessionConfig{})
defer closeSession(c.Request.Context(), session, srv.dbOpTimeout)
actx, aCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout)
defer aCancel()
if err := auth.CouldManagePerson(actx, session, id1, id2, params.XUserID); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"msg": fmt.Sprint("User does not have permissions to manage this person with error:", err.Error())})
return
}
qctx, qCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout)
defer qCancel()
_, err := session.ExecuteWrite(qctx, memgraph.DeleteAdminRelationship(qctx, id1, id2))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"msg": "admin relationship was deleted"})
}
func (srv *server) GetAdminRelationship(c *gin.Context, id1 int, id2 int, params api.GetAdminRelationshipParams) {
session := srv.db.NewSession(c.Request.Context(), neo4j.SessionConfig{})
defer closeSession(c.Request.Context(), session, srv.dbOpTimeout)
actx, aCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout)
defer aCancel()
if err := auth.CouldSeePersonsProfile(actx, session, id1, params.XUserID); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"msg": fmt.Sprint("User does not have permissions to see this person", err.Error())})
return
}
qctx, qCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout)
defer qCancel()
res, err := session.ExecuteRead(qctx, memgraph.GetAdminRelationship(qctx, id1, id2))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
return
}
c.JSON(http.StatusOK, res)
}
func (srv *server) GetProfileAdmins(c *gin.Context, id int, params api.GetProfileAdminsParams) {
session := srv.db.NewSession(c.Request.Context(), neo4j.SessionConfig{})
defer closeSession(c.Request.Context(), session, srv.dbOpTimeout)
actx, aCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout)
defer aCancel()
if err := auth.CouldSeePersonsProfile(actx, session, id, params.XUserID); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"msg": fmt.Sprint("User does not have permissions to see this person", err.Error())})
return
}
qctx, qCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout)
defer qCancel()
res, err := session.ExecuteRead(qctx, memgraph.GetProfileAdmins(qctx, id))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
return
}
c.JSON(http.StatusOK, res)
}
func (srv *server) GetManagedProfiles(c *gin.Context, params api.GetManagedProfilesParams) {
session := srv.db.NewSession(c.Request.Context(), neo4j.SessionConfig{})
defer closeSession(c.Request.Context(), session, srv.dbOpTimeout)
qctx, qCancel := context.WithTimeout(c.Request.Context(), srv.dbOpTimeout)
defer qCancel()
res, err := session.ExecuteRead(qctx, memgraph.GetManagedProfiles(qctx, id))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
return
}
c.JSON(http.StatusOK, res)
}

View File

@@ -0,0 +1,27 @@
package auth
import (
"context"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
"github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/memgraph"
)
// This function checks if the user has permission to manage another user's profile, it returns an error if the user does not have permission.
func CouldManagePerson(ctx context.Context, session neo4j.SessionWithContext, userId, adminId, XUserID int) error {
if adminId == XUserID {
return nil
}
return CouldManagePersonUnknownAdmin(ctx, session, userId, XUserID)
}
// This function checks if the user has permission to manage another user's profile, it returns an error if the user does not have permission.
func CouldManagePersonUnknownAdmin(ctx context.Context, session neo4j.SessionWithContext, userId, XUserID int) error {
if userId == XUserID {
return nil
}
_, err := session.ExecuteRead(ctx, memgraph.GetAdminRelationship(ctx, userId, XUserID), nil)
return err
}

View File

@@ -0,0 +1,14 @@
package auth
import (
"context"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
func CouldSeePersonsProfile(ctx context.Context, session neo4j.SessionWithContext, userId, XUserID int) error {
if CouldManagePersonUnknownAdmin(ctx, session, userId, XUserID) == nil {
return nil
}
}

View File

@@ -1,45 +0,0 @@
package api
import (
"context"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
"github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/memgraph"
)
type accessMode int
const (
accessModeNone accessMode = iota
accessModeRead
accessModeWrite
)
func userWithIdHasAccessToGivenPerson(ctx context.Context, session neo4j.SessionWithContext, userId, personId int) accessMode {
resPerson, err := session.ExecuteRead(ctx, memgraph.GetPersonById(ctx, userId))
if err != nil {
return accessModeNone
}
resPersonMap, ok := resPerson.(map[string]any)
if !ok {
return accessModeNone
}
if resPersonMap["id"] == userId {
return accessModeWrite
}
AllowAdminAccess, ok := resPersonMap["allow_admin_access"].([]map[string]any)
if !ok {
return accessModeNone
}
for _, admin := range AllowAdminAccess {
if admin["id"].(int) == userId {
return accessModeWrite
}
}
return accessModeNone
}

View File

@@ -2,10 +2,12 @@ package api
import (
"context"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
"github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/api/auth"
"github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/memgraph"
"github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api"
)
@@ -18,29 +20,57 @@ func (srv *server) CreatePerson(c *gin.Context, params api.CreatePersonParams) {
return
}
adminList := []struct {
Id *int "json:\"id,omitempty\""
Name *string "json:\"name,omitempty\""
}{
{Id: &params.XUserID, Name: &params.XUserName},
}
person.AllowAdminAccess = &adminList
ctx, cancel := context.WithTimeout(context.Background(), srv.dbOpTimeout)
defer cancel()
session := srv.db.NewSession(ctx, neo4j.SessionConfig{})
session := srv.db.NewSession(c.Request.Context(), neo4j.SessionConfig{})
defer closeSession(c.Request.Context(), session, srv.dbOpTimeout)
trs, err := session.BeginTransaction(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
return
}
defer func() {
trs.Commit(c.Request.Context())
trs.Close(c.Request.Context())
}()
qctx, qCancel := context.WithTimeout(context.Background(), srv.dbOpTimeout)
defer qCancel()
res, err := session.ExecuteRead(qctx, memgraph.CreatePerson(qctx, person))
res, err := trs.Run(qctx, memgraph.CreatePersonCypherQuery, map[string]any{
"Person": *person,
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
return
}
c.JSON(http.StatusOK, res)
singleRes, err := res.Single(qctx)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
return
}
personId, ok := singleRes.Get("id")
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"msg": "Person ID not found in response"})
return
}
actx, acancel := context.WithTimeout(context.Background(), srv.dbOpTimeout)
defer acancel()
_, aErr := trs.Run(actx, memgraph.CreateAdminRelationshipCypherQuery, map[string]any{
"id2": personId.(int),
"id1": params.XUserID,
})
if aErr != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": aErr.Error()})
return
}
c.JSON(http.StatusOK, singleRes.AsMap())
}
func (srv *server) GetPersonById(c *gin.Context, id int, params api.GetPersonByIdParams) {
@@ -51,8 +81,8 @@ func (srv *server) GetPersonById(c *gin.Context, id int, params api.GetPersonByI
actx, acancel := context.WithTimeout(context.Background(), srv.dbOpTimeout)
defer acancel()
if userWithIdHasAccessToGivenPerson(actx, session, params.XUserID, id) == accessModeNone {
c.JSON(http.StatusUnauthorized, gin.H{"msg": "User does not have access to this person"})
if err := auth.CouldSeePersonsProfile(actx, session, id, params.XUserID); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"msg": fmt.Sprint("User does not have access to this person", err.Error())})
return
}
@@ -77,8 +107,8 @@ func (srv *server) SoftDeletePerson(c *gin.Context, id int, params api.SoftDelet
actx, acancel := context.WithTimeout(context.Background(), srv.dbOpTimeout)
defer acancel()
if userWithIdHasAccessToGivenPerson(actx, session, params.XUserID, id) != accessModeWrite {
c.JSON(http.StatusUnauthorized, gin.H{"msg": "User does not have access to this person"})
if err := auth.CouldManagePersonUnknownAdmin(actx, session, id, params.XUserID); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"msg": fmt.Sprint("User does not have access to this person", err.Error())})
return
}
@@ -108,8 +138,8 @@ func (srv *server) UpdatePerson(c *gin.Context, id int, params api.UpdatePersonP
session := srv.db.NewSession(actx, neo4j.SessionConfig{})
defer closeSession(c.Request.Context(), session, srv.dbOpTimeout)
if userWithIdHasAccessToGivenPerson(actx, session, params.XUserID, id) != accessModeWrite {
c.JSON(http.StatusUnauthorized, gin.H{"msg": "User does not have access to this person"})
if err := auth.CouldManagePersonUnknownAdmin(actx, session, id, params.XUserID); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"msg": fmt.Sprint("User does not have access to this person", err.Error())})
return
}
@@ -134,8 +164,8 @@ func (srv *server) HardDeletePerson(c *gin.Context, id int, params api.HardDelet
actx, acancel := context.WithTimeout(context.Background(), srv.dbOpTimeout)
defer acancel()
if userWithIdHasAccessToGivenPerson(actx, session, params.XUserID, id) != accessModeWrite {
c.JSON(http.StatusUnauthorized, gin.H{"msg": "User does not have access to this person"})
if err := auth.CouldManagePersonUnknownAdmin(actx, session, id, params.XUserID); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"msg": fmt.Sprint("User does not have access to this person", err.Error())})
return
}

View File

@@ -27,19 +27,13 @@ func (srv *server) CreatePersonAndRelationship(c *gin.Context, id int, params ap
qctx, qCancel := context.WithTimeout(ctx, srv.dbOpTimeout)
defer qCancel()
res, err := session.ExecuteRead(qctx, memgraph.CreatePerson(qctx, &api.PersonProperties{
res, err := session.ExecuteWrite(qctx, memgraph.CreatePerson(qctx, &api.PersonProperties{
FirstName: &requestBody.Person.FirstName,
LastName: &requestBody.Person.LastName,
Born: &requestBody.Person.Born,
MothersFirstName: &requestBody.Person.MothersFirstName,
MothersLastName: &requestBody.Person.MothersLastName,
Limit: &requestBody.Person.Limit,
AllowAdminAccess: &[]struct {
Id *int "json:\"id,omitempty\""
Name *string "json:\"name,omitempty\""
}{
{Id: &params.XUserID},
},
}))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})

View File

@@ -47,7 +47,7 @@ func (srv *server) CreatePersonByGoogleIdAndInviteCode(c *gin.Context, googleId
qctx, qCancel := context.WithTimeout(context.Background(), srv.dbOpTimeout)
defer qCancel()
res, err := session.ExecuteRead(qctx, memgraph.UpdatePersonByInviteCode(qctx, person.InviteCode, person.Props))
res, err := session.ExecuteWrite(qctx, memgraph.UpdatePersonByInviteCode(qctx, person.InviteCode, person.Props))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
@@ -72,7 +72,7 @@ func (srv *server) CreatePersonByGoogleId(c *gin.Context, googleId string) {
qctx, qCancel := context.WithTimeout(context.Background(), srv.dbOpTimeout)
defer qCancel()
res, err := session.ExecuteRead(qctx, memgraph.CreatePerson(qctx, person))
res, err := session.ExecuteWrite(qctx, memgraph.CreatePerson(qctx, person))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})

View File

@@ -1 +1,77 @@
package memgraph
import (
"context"
"fmt"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
func CreateAdminRelationship(ctx context.Context, userId int, adminId int) neo4j.ManagedTransactionWork {
return func(tx neo4j.ManagedTransaction) (any, error) {
result, err := tx.Run(ctx, CreateAdminRelationshipCypherQuery, map[string]any{
"id1": adminId,
"id2": userId,
})
if err != nil {
return nil, err
}
record, err := result.Single(ctx)
if err != nil {
return nil, err
}
return record.AsMap(), nil
}
}
func DeleteAdminRelationship(ctx context.Context, userId int, adminId int) neo4j.ManagedTransactionWork {
return func(tx neo4j.ManagedTransaction) (any, error) {
result, err := tx.Run(ctx, DeleteAdminRelationshipCypherQuery, map[string]any{
"id1": adminId,
"id2": userId,
})
if err != nil {
return nil, err
}
if result.Peek(ctx) {
return nil, fmt.Errorf("there was a returned value, when deleting admin but there should be none")
}
return nil, nil
}
}
func GetAdminRelationship(ctx context.Context, userId int, adminId int) neo4j.ManagedTransactionWork {
return func(tx neo4j.ManagedTransaction) (any, error) {
result, err := tx.Run(ctx, GetAdminRelationshipCypherQuery, map[string]any{
"id1": adminId,
"id2": userId,
})
if err != nil {
return nil, err
}
record, err := result.Single(ctx)
if err != nil {
return nil, err
}
return record.AsMap(), nil
}
}
func GetProfileAdmins(ctx context.Context, userId int) neo4j.ManagedTransactionWork {
return func(tx neo4j.ManagedTransaction) (any, error) {
result, err := tx.Run(ctx, GetProfileAdminsCypherQuery, map[string]any{
"id": userId,
})
if err != nil {
return nil, err
}
return result.Collect(ctx)
}
}

View File

@@ -86,3 +86,23 @@ var DeleteRelationshipCypherQuery string
//
//go:embed queries/get_family_tree_by_id.cypher
var GetFamilyTreeByIdCypherQuery string
// Requires id1, id2 parameter.
//
//go:embed queries/create_admin_relationship.cypher
var CreateAdminRelationshipCypherQuery string
// Requires id1, id2 parameter.
//
//go:embed queries/delete_admin_relationship.cypher
var DeleteAdminRelationshipCypherQuery string
// Requires id1, id2 parameter.
//
//go:embed queries/get_admin_relationship.cypher
var GetAdminRelationshipCypherQuery string
// Requires id parameter.
//
//go:embed queries/get_profile_admins.cypher
var GetProfileAdminsCypherQuery string

View File

@@ -0,0 +1,5 @@
MATCH (a:Person), (b:Person)
WHERE id(a) = $id1 AND id(b) = $id2 AND $id1 != $id2
MERGE (a)-[r1:Admin]->(b)
ON CREATE SET r1 = {added : timestamp()}
RETURN r1 as relationship;

View File

@@ -0,0 +1,3 @@
MATCH (a)-[r1:Admin]->(b)
WHERE id(a) = $id1 AND id(b) = $id2 AND $id1 != $id2
DELETE r1;

View File

@@ -0,0 +1,3 @@
MATCH (a)-[r1:Admin]->(b)
WHERE id(a) = $id1 AND id(b) = $id2 AND $id1 != $id2
RETURN r1 as relationship;

View File

@@ -0,0 +1,3 @@
MATCH (a)-[r1:Admin]->(b)
WHERE id(b) = $id
RETURN collect(r1) as adminRelationship, collect({id: id(a), first_name: a.first_name, last_name: a.last_name}) as admins;