diff --git a/internal/postgres/user.go b/internal/postgres/user.go index 5181cbe..921af51 100644 --- a/internal/postgres/user.go +++ b/internal/postgres/user.go @@ -91,6 +91,10 @@ func CreateUserNodeWithSourceRelation(ctx context.Context, db *gorm.DB, anthrove } func GetUserFavoritesCount(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) (int64, error) { + if anthroveUserID == "" { + return 0, fmt.Errorf("anthroveUserID cannot be empty") + } + var count int64 err := db.WithContext(ctx).Model(&pgModels.UserFavorite{}).Where("user_id = ?", string(anthroveUserID)).Count(&count).Error if err != nil { @@ -149,10 +153,14 @@ func GetUserSourceLink(ctx context.Context, db *gorm.DB, anthroveUserID models.A } func GetSpecifiedUserSourceLink(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]graphModels.AnthroveUserRelationship, error) { + if anthroveUserID == "" || sourceDisplayName == "" { + return nil, fmt.Errorf("anthroveUserID or sourceDisplayName is empty") + } + var userSources []pgModels.UserSource userSourceMap := make(map[string]graphModels.AnthroveUserRelationship) - err := db.WithContext(ctx).Model(&pgModels.UserSource{}).Joins("Source").Where("user_id = ? AND display_name = ?", string(anthroveUserID), sourceDisplayName).Find(&userSources).Error + err := db.WithContext(ctx).Model(&pgModels.UserSource{}).InnerJoins("Source", db.Where("display_name = ?", sourceDisplayName)).Where("user_id = ?", string(anthroveUserID)).First(&userSources).Error if err != nil { log.WithFields(log.Fields{ "anthrove_user_id": anthroveUserID, @@ -317,10 +325,13 @@ func GetUserTagNodeWitRelationToFavedPosts(ctx context.Context, db *gorm.DB, ant return nil, err } - tagFrequency := make(map[string]int) + tagFrequency := make(map[struct { + name string + typeName string + }]int) for _, userFavorite := range userFavorites { - var postTags []pgModels.Tag - err = db.WithContext(ctx).Model(&pgModels.Post{}).Where("id = ?", userFavorite.PostID).Association("Tags").Find(&postTags) + var post pgModels.Post + err = db.WithContext(ctx).Preload("Tags").First(&post, "id = ?", userFavorite.PostID).Error if err != nil { log.WithFields(log.Fields{ "post_id": userFavorite.PostID, @@ -328,17 +339,21 @@ func GetUserTagNodeWitRelationToFavedPosts(ctx context.Context, db *gorm.DB, ant return nil, err } - for _, tag := range postTags { - tagFrequency[tag.Name]++ + for _, tag := range post.Tags { + tagFrequency[struct { + name string + typeName string + }{name: tag.Name, typeName: string(tag.Type)}]++ } } var tagsWithFrequency []graphModels.TagsWithFrequency - for tagName, frequency := range tagFrequency { + for data, frequency := range tagFrequency { tagsWithFrequency = append(tagsWithFrequency, graphModels.TagsWithFrequency{ Frequency: int64(frequency), Tags: graphModels.AnthroveTag{ - Name: tagName, + Name: data.name, + Type: data.typeName, }, }) } diff --git a/internal/postgres/user_test.go b/internal/postgres/user_test.go index 26f316f..531e13e 100644 --- a/internal/postgres/user_test.go +++ b/internal/postgres/user_test.go @@ -2,6 +2,7 @@ package postgres import ( "context" + "fmt" "git.dragse.it/anthrove/otter-space-sdk/pkg/models" "git.dragse.it/anthrove/otter-space-sdk/pkg/models/graphModels" "git.dragse.it/anthrove/otter-space-sdk/pkg/models/pgModels" @@ -310,6 +311,40 @@ func TestGetAnthroveUser(t *testing.T) { } func TestGetSpecifiedUserSourceLink(t *testing.T) { + // Setup trow away container + ctx := context.Background() + container, gormDB, err := test.StartPostgresContainer(ctx) + if err != nil { + t.Fatalf("Could not start PostgreSQL container: %v", err) + } + defer container.Terminate(ctx) + + // Setup Test + source := &pgModels.Source{ + DisplayName: "e621", + Domain: "e621.net", + } + err = CreateSourceNode(ctx, gormDB, source) + if err != nil { + t.Fatal(err) + } + + expectedResult := make(map[string]graphModels.AnthroveUserRelationship) + expectedResult["e621"] = graphModels.AnthroveUserRelationship{ + UserID: "e1", + Username: "euser", + Source: graphModels.AnthroveSource{ + DisplayName: source.DisplayName, + Domain: source.Domain, + }, + } + + err = CreateUserNodeWithSourceRelation(ctx, gormDB, "1", source.Domain, expectedResult["e621"].UserID, expectedResult["e621"].Username) + if err != nil { + t.Fatal(err) + } + // Test + type args struct { ctx context.Context db *gorm.DB @@ -322,7 +357,72 @@ func TestGetSpecifiedUserSourceLink(t *testing.T) { want map[string]graphModels.AnthroveUserRelationship wantErr bool }{ - // TODO: Add test cases. + { + name: "Test 1: Valid AnthroveUserID and SourceDisplayName", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "1", + sourceDisplayName: "e621", + }, + want: expectedResult, + wantErr: false, + }, + { + name: "Test 2: Invalid AnthroveUserID and valid SourceDisplayName", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "22", + sourceDisplayName: "e621", + }, + want: nil, + wantErr: true, + }, + { + name: "Test 3: Valid AnthroveUserID and invalid SourceDisplayName", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "1", + sourceDisplayName: "fa", + }, + want: nil, + wantErr: true, + }, + { + name: "Test 4: No AnthroveUserID and Valid SourceDisplayName", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "", + sourceDisplayName: "e621", + }, + want: nil, + wantErr: true, + }, + { + name: "Test 5: Valid AnthroveUserID and No SourceDisplayName", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "1", + sourceDisplayName: "", + }, + want: nil, + wantErr: true, + }, + { + name: "Test 6: No AnthroveUserID and No SourceDisplayName", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "", + sourceDisplayName: "", + }, + want: nil, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -332,13 +432,115 @@ func TestGetSpecifiedUserSourceLink(t *testing.T) { return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetSpecifiedUserSourceLink() got = %v, want %v", got, tt.want) + t.Errorf("GetSpecifiedUserSourceLink() got = %v, expectedResult %v", got, tt.want) } }) } } func TestGetUserFavoriteNodeWithPagination(t *testing.T) { + // Setup trow away containert + ctx := context.Background() + container, gormDB, err := test.StartPostgresContainer(ctx) + if err != nil { + t.Fatalf("Could not start PostgreSQL container: %v", err) + } + defer container.Terminate(ctx) + + // Setup Test + + expectedResultPostsGraph := []graphModels.FavoritePost{ + { + AnthrovePost: graphModels.AnthrovePost{ + PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post1")), + Rating: "safe", + }, + }, + { + AnthrovePost: graphModels.AnthrovePost{ + PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post2")), + Rating: "safe", + }, + }, + { + AnthrovePost: graphModels.AnthrovePost{ + PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post3")), + Rating: "explicit", + }, + }, + { + AnthrovePost: graphModels.AnthrovePost{ + PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post4")), + Rating: "explicit", + }, + }, + { + AnthrovePost: graphModels.AnthrovePost{ + PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post5")), + Rating: "questionable", + }, + }, + { + AnthrovePost: graphModels.AnthrovePost{ + PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post6")), + Rating: "safe", + }, + }, + } + expectedResult := &graphModels.FavoriteList{ + Posts: expectedResultPostsGraph, + } + expectedResult2 := &graphModels.FavoriteList{ + Posts: expectedResultPostsGraph[2:], + } + expectedResult3 := &graphModels.FavoriteList{ + Posts: expectedResultPostsGraph[:3], + } + + err = CreateUser(ctx, gormDB, "1") + if err != nil { + t.Fatal(err) + } + + expectedResultPostsPg := []pgModels.Post{ + { + BaseModel: pgModels.BaseModel{ID: "Post1"}, + Rating: "safe", + }, + { + BaseModel: pgModels.BaseModel{ID: "Post2"}, + Rating: "safe", + }, + { + BaseModel: pgModels.BaseModel{ID: "Post3"}, + Rating: "explicit", + }, + { + BaseModel: pgModels.BaseModel{ID: "Post4"}, + Rating: "explicit", + }, + { + BaseModel: pgModels.BaseModel{ID: "Post5"}, + Rating: "questionable", + }, + { + BaseModel: pgModels.BaseModel{ID: "Post6"}, + Rating: "safe", + }, + } + + for _, expectedResultPost := range expectedResultPostsPg { + err = CreateAnthrovePostNode(ctx, gormDB, models.AnthrovePostID(expectedResultPost.ID), expectedResultPost.Rating) + if err != nil { + t.Fatal(err) + } + err = EstablishUserToPostLink(ctx, gormDB, "1", models.AnthrovePostID(expectedResultPost.ID)) + if err != nil { + t.Fatal(err) + } + } + + // Test type args struct { ctx context.Context db *gorm.DB @@ -352,7 +554,42 @@ func TestGetUserFavoriteNodeWithPagination(t *testing.T) { want *graphModels.FavoriteList wantErr bool }{ - // TODO: Add test cases. + { + name: "Test 1: Valid AnthroveUserID", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "1", + skip: 0, + limit: 2000, + }, + want: expectedResult, + wantErr: false, + }, + { + name: "Test 2: Skip first two", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "1", + skip: 2, + limit: 2000, + }, + want: expectedResult2, + wantErr: false, + }, + { + name: "Test 3: Limit of 3", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "1", + skip: 0, + limit: 3, + }, + want: expectedResult3, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -369,6 +606,60 @@ func TestGetUserFavoriteNodeWithPagination(t *testing.T) { } func TestGetUserFavoritesCount(t *testing.T) { + // Setup trow away containert + ctx := context.Background() + container, gormDB, err := test.StartPostgresContainer(ctx) + if err != nil { + t.Fatalf("Could not start PostgreSQL container: %v", err) + } + defer container.Terminate(ctx) + + // Setup Test + + err = CreateUser(ctx, gormDB, "1") + if err != nil { + t.Fatal(err) + } + + expectedResultPostsPg := []pgModels.Post{ + { + BaseModel: pgModels.BaseModel{ID: "Post1"}, + Rating: "safe", + }, + { + BaseModel: pgModels.BaseModel{ID: "Post2"}, + Rating: "safe", + }, + { + BaseModel: pgModels.BaseModel{ID: "Post3"}, + Rating: "explicit", + }, + { + BaseModel: pgModels.BaseModel{ID: "Post4"}, + Rating: "explicit", + }, + { + BaseModel: pgModels.BaseModel{ID: "Post5"}, + Rating: "questionable", + }, + { + BaseModel: pgModels.BaseModel{ID: "Post6"}, + Rating: "safe", + }, + } + + for _, expectedResultPost := range expectedResultPostsPg { + err = CreateAnthrovePostNode(ctx, gormDB, models.AnthrovePostID(expectedResultPost.ID), expectedResultPost.Rating) + if err != nil { + t.Fatal(err) + } + err = EstablishUserToPostLink(ctx, gormDB, "1", models.AnthrovePostID(expectedResultPost.ID)) + if err != nil { + t.Fatal(err) + } + } + + // Test type args struct { ctx context.Context db *gorm.DB @@ -380,7 +671,36 @@ func TestGetUserFavoritesCount(t *testing.T) { want int64 wantErr bool }{ - // TODO: Add test cases. + { + name: "Test 1: Valid anthroveUserID and 6 favorite posts", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "1", + }, + want: 6, + wantErr: false, + }, + { + name: "Test 2: Invalid anthroveUserID and 6 favorite posts", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "2", + }, + want: 0, + wantErr: false, + }, + { + name: "Test 3: no anthroveUserID and 6 favorite posts", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "", + }, + want: 0, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -397,6 +717,61 @@ func TestGetUserFavoritesCount(t *testing.T) { } func TestGetUserSourceLink(t *testing.T) { + // Setup trow away containert + ctx := context.Background() + container, gormDB, err := test.StartPostgresContainer(ctx) + if err != nil { + t.Fatalf("Could not start PostgreSQL container: %v", err) + } + defer container.Terminate(ctx) + + // Setup Test + + esource := &pgModels.Source{ + DisplayName: "e621", + Domain: "e621.net", + } + err = CreateSourceNode(ctx, gormDB, esource) + if err != nil { + t.Fatal(err) + } + + fasource := &pgModels.Source{ + DisplayName: "fa", + Domain: "fa.net", + } + err = CreateSourceNode(ctx, gormDB, fasource) + if err != nil { + t.Fatal(err) + } + + expectedResult := make(map[string]graphModels.AnthroveUserRelationship) + expectedResult["e621"] = graphModels.AnthroveUserRelationship{ + UserID: "e1", + Username: "euser", + Source: graphModels.AnthroveSource{ + DisplayName: esource.DisplayName, + Domain: esource.Domain, + }, + } + expectedResult["fa"] = graphModels.AnthroveUserRelationship{ + UserID: "fa1", + Username: "fauser", + Source: graphModels.AnthroveSource{ + DisplayName: fasource.DisplayName, + Domain: fasource.Domain, + }, + } + + err = CreateUserNodeWithSourceRelation(ctx, gormDB, "1", esource.Domain, expectedResult["e621"].UserID, expectedResult["e621"].Username) + if err != nil { + t.Fatal(err) + } + err = CreateUserNodeWithSourceRelation(ctx, gormDB, "1", fasource.Domain, expectedResult["fa"].UserID, expectedResult["fa"].Username) + if err != nil { + t.Fatal(err) + } + // Test type args struct { ctx context.Context db *gorm.DB @@ -408,7 +783,16 @@ func TestGetUserSourceLink(t *testing.T) { want map[string]graphModels.AnthroveUserRelationship wantErr bool }{ - // TODO: Add test cases. + { + name: "Test 1: Get Data", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "1", + }, + want: expectedResult, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -425,6 +809,75 @@ func TestGetUserSourceLink(t *testing.T) { } func TestGetUserTagNodeWitRelationToFavedPosts(t *testing.T) { + // Setup trow away containert + ctx := context.Background() + container, gormDB, err := test.StartPostgresContainer(ctx) + if err != nil { + t.Fatalf("Could not start PostgreSQL container: %v", err) + } + defer container.Terminate(ctx) + + // Setup Test + err = CreateUser(ctx, gormDB, "1") + if err != nil { + t.Fatal(err) + } + + posts := []pgModels.Post{ + {BaseModel: pgModels.BaseModel{ID: fmt.Sprintf("%-25s", "Post1")}, Rating: "safe"}, + {BaseModel: pgModels.BaseModel{ID: fmt.Sprintf("%-25s", "Post2")}, Rating: "safe"}, + {BaseModel: pgModels.BaseModel{ID: fmt.Sprintf("%-25s", "Post3")}, Rating: "explicit"}, + } + + for _, post := range posts { + err = CreateAnthrovePostNode(ctx, gormDB, models.AnthrovePostID(post.ID), post.Rating) + if err != nil { + t.Fatal(err) + } + err = EstablishUserToPostLink(ctx, gormDB, "1", models.AnthrovePostID(post.ID)) + if err != nil { + t.Fatal(err) + } + } + + tags := []pgModels.Tag{ + {Name: "JayTheFerret", Type: "artist"}, + {Name: "Ferret", Type: "species"}, + {Name: "Jay", Type: "character"}, + } + + for i, tag := range tags { + err = CreateTagNodeWitRelation(ctx, gormDB, models.AnthrovePostID(posts[i].ID), &tag) + if err != nil { + t.Fatal(err) + } + } + + expectedResult := []graphModels.TagsWithFrequency{ + { + Frequency: 1, + Tags: graphModels.AnthroveTag{ + Name: tags[0].Name, + Type: string(tags[0].Type), + }, + }, + { + Frequency: 1, + Tags: graphModels.AnthroveTag{ + Name: tags[1].Name, + Type: string(tags[1].Type), + }, + }, + { + Frequency: 1, + Tags: graphModels.AnthroveTag{ + Name: tags[2].Name, + Type: string(tags[2].Type), + }, + }, + } + + // Test type args struct { ctx context.Context db *gorm.DB @@ -436,7 +889,16 @@ func TestGetUserTagNodeWitRelationToFavedPosts(t *testing.T) { want []graphModels.TagsWithFrequency wantErr bool }{ - // TODO: Add test cases. + { + name: "", + args: args{ + ctx: ctx, + db: gormDB, + anthroveUserID: "1", + }, + want: expectedResult, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/database/migrations/001_inital_database.sql b/pkg/database/migrations/001_inital_database.sql index c78c150..7e16f9b 100644 --- a/pkg/database/migrations/001_inital_database.sql +++ b/pkg/database/migrations/001_inital_database.sql @@ -17,7 +17,7 @@ CREATE TYPE TagType AS ENUM ( CREATE TABLE "Post" ( - id CHAR(25) UNIQUE PRIMARY KEY, + id CHAR(25) PRIMARY KEY, rating Rating, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, @@ -26,7 +26,7 @@ CREATE TABLE "Post" CREATE TABLE "Source" ( - id CHAR(25) UNIQUE PRIMARY KEY, + id CHAR(25) PRIMARY KEY, display_name TEXT NULL, icon TEXT NULL, domain TEXT NOT NULL UNIQUE, @@ -46,7 +46,7 @@ CREATE TABLE "Tag" CREATE TABLE "User" ( - id TEXT UNIQUE PRIMARY KEY, + id TEXT PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP NULL @@ -80,8 +80,8 @@ CREATE TABLE "TagGroup" CREATE TABLE "UserFavorites" ( - user_id TEXT UNIQUE REFERENCES "User" (id), - post_id TEXT UNIQUE REFERENCES "Post" (id), + user_id TEXT REFERENCES "User" (id), + post_id TEXT REFERENCES "Post" (id), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id, post_id) ); diff --git a/pkg/models/pgModels/orm.go b/pkg/models/pgModels/orm.go index c637fa5..4fd79bb 100644 --- a/pkg/models/pgModels/orm.go +++ b/pkg/models/pgModels/orm.go @@ -16,7 +16,7 @@ type BaseModel struct { func (base *BaseModel) BeforeCreate(db *gorm.DB) error { if base.ID == "" { - id, err := gonanoid.New() + id, err := gonanoid.New(25) if err != nil { return err } diff --git a/pkg/models/pgModels/userSource.go b/pkg/models/pgModels/userSource.go index a87494f..04371fe 100644 --- a/pkg/models/pgModels/userSource.go +++ b/pkg/models/pgModels/userSource.go @@ -2,6 +2,7 @@ package pgModels type UserSource struct { UserID string `gorm:"primaryKey"` + Source Source `gorm:"foreignKey:ID;references:SourceID"` SourceID string `gorm:"primaryKey"` AccountUsername string AccountID string diff --git a/test/helper.go b/test/helper.go index 4f50991..501ac62 100644 --- a/test/helper.go +++ b/test/helper.go @@ -7,6 +7,7 @@ import ( postgrescontainer "github.com/testcontainers/testcontainers-go/modules/postgres" "gorm.io/driver/postgres" "gorm.io/gorm" + "gorm.io/gorm/logger" "time" "github.com/testcontainers/testcontainers-go" @@ -73,5 +74,7 @@ func migrateDatabase(connectionString string) error { } func getGormDB(connectionString string) (*gorm.DB, error) { - return gorm.Open(postgres.Open(connectionString), &gorm.Config{}) + return gorm.Open(postgres.Open(connectionString), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) }