Compare commits

..

No commits in common. "7842976f4b4a3cb7eda14aca5c6d22f207da269c" and "536f7c044327a8f1432c1c1ec7a870e063c8a9f1" have entirely different histories.

37 changed files with 2050 additions and 787 deletions

46
internal/graph/logger.go Normal file
View File

@ -0,0 +1,46 @@
package graph
import (
"fmt"
neo4jLog "github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
log "github.com/sirupsen/logrus"
)
type graphLogger struct {
graphDebug bool
}
func NewGraphLogger(graphDebug bool) neo4jLog.Logger {
return &graphLogger{graphDebug: graphDebug}
}
func (n graphLogger) Error(name string, id string, err error) {
log.WithFields(log.Fields{
"name": name,
"id": id,
}).Errorf("database: %s", err)
}
func (n graphLogger) Warnf(name string, id string, msg string, args ...any) {
log.WithFields(log.Fields{
"name": name,
"id": id,
}).Warnf("database: %v", fmt.Sprintf(msg, args...))
}
func (n graphLogger) Infof(name string, id string, msg string, args ...any) {
log.WithFields(log.Fields{
"name": name,
"id": id,
}).Infof("database: %v", fmt.Sprintf(msg, args...))
}
func (n graphLogger) Debugf(name string, id string, msg string, args ...any) {
if n.graphDebug {
log.WithFields(log.Fields{
"name": name,
"id": id,
}).Debugf("database: %v", fmt.Sprintf(msg, args...))
}
}

116
internal/graph/post.go Normal file
View File

@ -0,0 +1,116 @@
package graph
import (
"context"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/graphModels"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
log "github.com/sirupsen/logrus"
)
func CreateAnthrovePostNode(ctx context.Context, driver neo4j.DriverWithContext, anthrovePost *graphModels.AnthrovePost) error {
query := `
CREATE (newPostNode:AnthrovePost {post_id: $anthrove_post_id, rating: $anthrove_rating})
`
params := map[string]any{
"anthrove_post_id": anthrovePost.PostID,
"anthrove_rating": anthrovePost.Rating,
}
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
log.WithFields(log.Fields{
"anthrove_post_id": anthrovePost.PostID,
"anthrove_post_rating": anthrovePost.Rating,
}).Trace("database: created anthrove post")
return nil
}
func CheckIfAnthrovePostNodeExistsByAnthroveID(ctx context.Context, driver neo4j.DriverWithContext, anthrovePost *graphModels.AnthrovePost) (*graphModels.AnthrovePost, bool, error) {
query := `
OPTIONAL MATCH (postNode:AnthrovePost {post_id: $anthrove_post_id})
RETURN postNode.post_id AS AnthrovePostID
`
params := map[string]any{
"anthrove_post_id": anthrovePost.PostID,
}
anthrovePost, exists, err := executeCheckQuery(ctx, driver, query, params)
if err != nil {
return nil, false, err
}
return anthrovePost, exists, nil
}
func CheckIfAnthrovePostNodeExistsBySourceURl(ctx context.Context, driver neo4j.DriverWithContext, sourceUrl string) (*graphModels.AnthrovePost, bool, error) {
query := `
OPTIONAL MATCH (postNode:AnthrovePost)<-[:REFERENCE {url: $source_url}]-()
RETURN postNode.post_id AS AnthrovePostID
`
params := map[string]any{
"source_url": sourceUrl,
}
anthrovePost, exists, err := executeCheckQuery(ctx, driver, query, params)
if err != nil {
return nil, false, err
}
return anthrovePost, exists, nil
}
func CheckIfAnthrovePostNodeExistsBySourceID(ctx context.Context, driver neo4j.DriverWithContext, sourcePostID string) (*graphModels.AnthrovePost, bool, error) {
query := `
OPTIONAL MATCH (postNode:AnthrovePost)<-[:REFERENCE {source_post_id: $source_post_id}]-()
RETURN postNode.post_id AS AnthrovePostID
`
params := map[string]any{
"source_post_id": sourcePostID,
}
anthrovePost, exists, err := executeCheckQuery(ctx, driver, query, params)
if err != nil {
return nil, false, err
}
return anthrovePost, exists, nil
}
func executeCheckQuery(ctx context.Context, driver neo4j.DriverWithContext, query string, params map[string]any) (*graphModels.AnthrovePost, bool, error) {
var anthrovePost graphModels.AnthrovePost
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return &anthrovePost, false, err
}
record := result.Records
anthrovePostID, isNil, err := neo4j.GetRecordValue[string](record[0], "AnthrovePostID")
exists := !isNil
if err != nil {
return &anthrovePost, exists, err
}
anthrovePost.PostID = models.AnthrovePostID(anthrovePostID)
log.WithFields(log.Fields{
"anthrove_post_id": anthrovePost.PostID,
"anthrove_post_exists": exists,
}).Trace("database: checked if post exists")
if !exists {
return nil, exists, nil
}
return &anthrovePost, exists, nil
}

View File

@ -0,0 +1,100 @@
package graph
import (
"context"
"fmt"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/graphModels"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
log "github.com/sirupsen/logrus"
)
func EstablishAnthrovePostToSourceLink(ctx context.Context, driver neo4j.DriverWithContext, anthrovePostID models.AnthrovePostID, anthroveSourceDomain string, anthrovePostRelationship *graphModels.AnthrovePostRelationship) error {
query := `
MATCH (sourceNode:Source {domain: $source_url})
MATCH (postNode:AnthrovePost {post_id: $anthrove_post_id})
MERGE (sourceNode)-[:REFERENCE {url: $source_post_url, source_post_id: $source_post_id}]->(postNode)
`
params := map[string]any{
"source_url": anthroveSourceDomain,
"anthrove_post_id": anthrovePostID,
"source_post_url": anthrovePostRelationship.Url,
"source_post_id": anthrovePostRelationship.PostID,
}
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
log.WithFields(log.Fields{
"source_url": anthroveSourceDomain,
"anthrove_post_id": anthrovePostID,
"source_post_url": anthrovePostRelationship.Url,
"source_post_id": anthrovePostRelationship.PostID,
}).Trace("database: creating anthrove post to source link")
return nil
}
func EstablishUserToPostLink(ctx context.Context, driver neo4j.DriverWithContext, anthroveUser *graphModels.AnthroveUser, anthrovePost *graphModels.AnthrovePost) error {
query := `
MATCH (user:User {user_id: $anthrove_user_id})
MATCH (anthrovePost:AnthrovePost {post_id: $anthrove_post_id})
MERGE (user)-[:FAV]->(anthrovePost)
`
params := map[string]any{
"anthrove_post_id": anthrovePost.PostID,
"anthrove_user_id": anthroveUser.UserID,
}
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
log.WithFields(log.Fields{
"anthrove_post_id": anthrovePost.PostID,
"anthrove_user_id": anthroveUser.UserID,
}).Trace("database: created user to post link")
return nil
}
func CheckUserToPostLink(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID, sourcePostID string, sourceUrl string) (bool, error) {
query := `
OPTIONAL MATCH (:User {user_id: $anthrove_user_id})-[f:FAV]->(:AnthrovePost)<-[:REFERENCE{source_post_id: $source_post_id}]-(:Source{domain: $source_domain})
RETURN COUNT(f) > 0 AS hasRelationship
`
params := map[string]any{
"anthrove_user_id": anthroveUserID,
"source_post_id": sourcePostID,
"source_domain": sourceUrl,
}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return false, err
}
if len(result.Records) == 0 {
return false, fmt.Errorf("no records found")
}
exists, _, err := neo4j.GetRecordValue[bool](result.Records[0], "hasRelationship")
if err != nil {
return false, err
}
log.WithFields(log.Fields{
"relationship_exists": exists,
"relationship_anthrove_user_id": anthroveUserID,
"relationship_e621_post_id": "",
}).Trace("database: checked user post relationship")
return exists, nil
}

113
internal/graph/source.go Normal file
View File

@ -0,0 +1,113 @@
package graph
import (
"context"
"fmt"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/graphModels"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
log "github.com/sirupsen/logrus"
)
func CreateSourceNode(ctx context.Context, driver neo4j.DriverWithContext, anthroveSource *graphModels.AnthroveSource) error {
query := `
MERGE (sourceNode:Source {domain: $source_url})
ON CREATE SET sourceNode.domain = $source_url, sourceNode.display_name = $source_display_name, sourceNode.icon = $source_icon
`
params := map[string]any{
"source_url": anthroveSource.Domain,
"source_display_name": anthroveSource.DisplayName,
"source_icon": anthroveSource.Icon,
}
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return fmt.Errorf("database: %w", err)
}
log.WithFields(log.Fields{
"node_source_url": anthroveSource.Domain,
"node_source_displayName": anthroveSource.DisplayName,
"node_source_icon": anthroveSource.Icon,
}).Trace("database: created source node")
return nil
}
func GetAllSourceNodes(ctx context.Context, driver neo4j.DriverWithContext) ([]graphModels.AnthroveSource, error) {
var sources []graphModels.AnthroveSource
query := `
MATCH (s:Source)
RETURN s as source
`
params := map[string]any{}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return nil, err
}
if len(result.Records) == 0 {
return nil, nil
}
for i := range result.Records {
record := result.Records[i]
source, _, err := neo4j.GetRecordValue[neo4j.Node](record, "source")
if err != nil {
return nil, err
}
sources = append(sources, graphModels.AnthroveSource{
DisplayName: source.Props["display_name"].(string),
Domain: source.Props["domain"].(string),
Icon: source.Props["icon"].(string),
})
}
log.WithFields(log.Fields{
"tag_amount": len(sources),
}).Trace("database: created tag node")
return sources, nil
}
func GetSourceNodesByURL(ctx context.Context, driver neo4j.DriverWithContext, sourceUrl string) (*graphModels.AnthroveSource, error) {
var source graphModels.AnthroveSource
query := `
MATCH (s:Source {domain: $source_url})
RETURN s as source
`
params := map[string]any{
"source_url": sourceUrl,
}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return nil, err
}
if len(result.Records) == 0 {
return nil, fmt.Errorf("source not found")
}
record, _, err := neo4j.GetRecordValue[neo4j.Node](result.Records[0], "source")
if err != nil {
return nil, err
}
source.DisplayName = record.Props["display_name"].(string)
source.Domain = record.Props["domain"].(string)
source.Icon = record.Props["icon"].(string)
log.WithFields(log.Fields{
"source_url": sourceUrl,
}).Trace("database: got source node")
return &source, nil
}

84
internal/graph/tag.go Normal file
View File

@ -0,0 +1,84 @@
package graph
import (
"context"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/graphModels"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
log "github.com/sirupsen/logrus"
)
func CreateTagNodeWitRelation(ctx context.Context, driver neo4j.DriverWithContext, anthrovePostID models.AnthrovePostID, anthroveTag *graphModels.AnthroveTag) error {
query := `
MATCH (anthrovePost:AnthrovePost {post_id: $anthrove_post_id})
MERGE (tagNode:Tag {name: $tag_name, type: $tag_type})
MERGE (anthrovePost)-[:HAS]->(tagNode)
`
params := map[string]interface{}{
"tag_name": anthroveTag.Name,
"tag_type": anthroveTag.Type,
"anthrove_post_id": anthrovePostID,
}
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
log.WithFields(log.Fields{
"anthrove_post_id": anthrovePostID,
"tag_name": anthroveTag.Name,
"tag_type": anthroveTag.Type,
}).Trace("database: created tag node")
return nil
}
func GetTags(ctx context.Context, driver neo4j.DriverWithContext) ([]graphModels.TagsWithFrequency, error) {
var userTags []graphModels.TagsWithFrequency
query := `
MATCH (:AnthrovePost)-[:HAS]->(t:Tag)
RETURN t as tag, COUNT(t) AS frequency
ORDER BY frequency DESC
`
params := map[string]any{}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return nil, err
}
if len(result.Records) == 0 {
return nil, nil
}
for i := range result.Records {
record := result.Records[i]
tag, _, err := neo4j.GetRecordValue[neo4j.Node](record, "tag")
if err != nil {
return nil, err
}
frequency, _, err := neo4j.GetRecordValue[int64](record, "frequency")
if err != nil {
return nil, err
}
userTags = append(userTags, graphModels.TagsWithFrequency{
Frequency: frequency,
Tags: graphModels.AnthroveTag{
Name: tag.Props["name"].(string),
Type: tag.Props["type"].(string),
},
})
}
log.WithFields(log.Fields{
"tag_amount": len(userTags),
}).Trace("database: created tag node")
return userTags, nil
}

454
internal/graph/user.go Normal file
View File

@ -0,0 +1,454 @@
package graph
import (
"context"
"fmt"
"git.dragse.it/anthrove/otter-space-sdk/internal/utils"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/graphModels"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
log "github.com/sirupsen/logrus"
)
func CreateUserNodeWithSourceRelation(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID, sourceDomain string, userID string, username string) error {
query := `
MATCH (userNode:User {user_id: $anthrove_user_id})
MATCH (sourceNode:Source {domain: $source_domain})
MERGE (userNode)-[r:HAS_ACCOUNT_AT {username: $source_user_name, user_id: $source_user_id}]->(sourceNode)
`
params := map[string]any{
"anthrove_user_id": anthroveUserID,
"source_user_id": userID,
"source_user_name": username,
"source_domain": sourceDomain,
}
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
var anthroveUserRelationship []graphModels.AnthroveUserRelationship
anthroveUserRelationship = append(anthroveUserRelationship, graphModels.AnthroveUserRelationship{
UserID: userID,
Username: username,
ScrapeTimeInterval: "",
Source: graphModels.AnthroveSource{
DisplayName: "",
Domain: sourceDomain,
Icon: "",
},
})
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
"source_user_id": userID,
"source_user_name": username,
"source_domain": sourceDomain,
}).Trace("database: crated user with relationship")
return nil
}
func GetUserFavoritesCount(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID) (int64, error) {
var userFavoriteCount int64
query := `
MATCH (userNode:User {user_id: $anthrove_user_id})
MATCH (userNode)-[:FAV]->(favPost:AnthrovePost)
MATCH (sourceNode)-[:REFERENCE]->(favPost)
RETURN count( DISTINCT favPost) AS FavoritePostsCount
`
params := map[string]any{
"anthrove_user_id": anthroveUserID,
}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return 0, err
}
if len(result.Records) == 0 {
// no matches -> user does not exist, return count 0
return userFavoriteCount, err
}
record := result.Records[0]
userFavoriteCount, _, err = neo4j.GetRecordValue[int64](record, "FavoritePostsCount")
if err != nil {
return userFavoriteCount, err
}
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
"anthrove_user_fav_count": userFavoriteCount,
}).Trace("database: got user favorite count")
return userFavoriteCount, nil
}
func GetUserSourceLink(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID) (map[string]graphModels.AnthroveUserRelationship, error) {
userSource := make(map[string]graphModels.AnthroveUserRelationship)
query := `
MATCH (user:User{user_id: $anthrove_user_id})-[r:HAS_ACCOUNT_AT]->(s:Source)
RETURN toString(r.user_id) AS sourceUserID, toString(r.username) AS sourceUsername, s as source;
`
params := map[string]any{
"anthrove_user_id": anthroveUserID,
}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return nil, err
}
if len(result.Records) == 0 {
return nil, fmt.Errorf("user has no relations")
}
for i := range result.Records {
record := result.Records[i]
source, _, err := neo4j.GetRecordValue[neo4j.Node](record, "source")
if err != nil {
return nil, err
}
sourceUserID, _, err := neo4j.GetRecordValue[string](record, "sourceUserID")
if err != nil {
return nil, err
}
sourceUsername, _, err := neo4j.GetRecordValue[string](record, "sourceUsername")
if err != nil {
return nil, err
}
displayName := source.Props["display_name"].(string)
domain := source.Props["domain"].(string)
icon := source.Props["icon"].(string)
anthroveSourceUser := graphModels.AnthroveUserRelationship{
UserID: sourceUserID,
Username: sourceUsername,
Source: graphModels.AnthroveSource{
DisplayName: displayName,
Domain: domain,
Icon: icon,
},
}
userSource[displayName] = anthroveSourceUser
}
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
"anthrove_data": userSource,
}).Trace("database: got user favorite count")
return userSource, nil
}
func GetSpecifiedUserSourceLink(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]graphModels.AnthroveUserRelationship, error) {
userSource := make(map[string]graphModels.AnthroveUserRelationship)
query := `
MATCH (user:User{user_id: $anthrove_user_id})-[r:HAS_ACCOUNT_AT]->(s:Source{display_name: $source_display_name})
RETURN toString(r.user_id) AS sourceUserID, toString(r.username) AS sourceUsername, s as source;
`
params := map[string]any{
"anthrove_user_id": anthroveUserID,
"source_display_name": sourceDisplayName,
}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return nil, err
}
if len(result.Records) == 0 {
return nil, fmt.Errorf("user has no relations with the source %s", sourceDisplayName)
}
for i := range result.Records {
record := result.Records[i]
source, _, err := neo4j.GetRecordValue[neo4j.Node](record, "source")
if err != nil {
return nil, err
}
sourceUserID, _, err := neo4j.GetRecordValue[string](record, "sourceUserID")
if err != nil {
return nil, err
}
sourceUsername, _, err := neo4j.GetRecordValue[string](record, "sourceUsername")
if err != nil {
return nil, err
}
displayName := source.Props["display_name"].(string)
domain := source.Props["domain"].(string)
icon := source.Props["icon"].(string)
anthroveSourceUser := graphModels.AnthroveUserRelationship{
UserID: sourceUserID,
Username: sourceUsername,
Source: graphModels.AnthroveSource{
DisplayName: displayName,
Domain: domain,
Icon: icon,
},
}
userSource[displayName] = anthroveSourceUser
}
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
"anthrove_data": userSource,
}).Trace("database: got user favorite count")
return userSource, nil
}
func GetAnthroveUser(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID) (*graphModels.AnthroveUser, error) {
var err error
var anthroveUser graphModels.AnthroveUser
var userSources graphModels.AnthroveSource
userRelationships := make([]graphModels.AnthroveUserRelationship, 0)
query := `
MATCH (user:User{user_id: $anthrove_user_id})-[relation:HAS_ACCOUNT_AT]->(source:Source)
RETURN user as User, relation as Relation, source as Source;
`
params := map[string]any{
"anthrove_user_id": anthroveUserID,
}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return nil, err
}
if len(result.Records) == 0 {
return nil, fmt.Errorf("user has no relations")
}
for i := range result.Records {
record := result.Records[i]
user, _, err := neo4j.GetRecordValue[neo4j.Node](record, "User")
if err != nil {
return nil, err
}
relation, _, err := neo4j.GetRecordValue[neo4j.Relationship](record, "Relation")
if err != nil {
return nil, err
}
source, _, err := neo4j.GetRecordValue[neo4j.Node](record, "Source")
if err != nil {
return nil, err
}
userRelationships = append(userRelationships, graphModels.AnthroveUserRelationship{
UserID: fmt.Sprintf("%v", utils.GetOrDefault(relation.Props, "user_id", "")),
Username: utils.GetOrDefault(relation.Props, "username", "").(string),
ScrapeTimeInterval: utils.GetOrDefault(relation.Props, "scrape_time_interval", "").(string),
})
userSources = graphModels.AnthroveSource{
DisplayName: utils.GetOrDefault(source.Props, "display_name", "").(string),
Domain: utils.GetOrDefault(source.Props, "domain", "").(string),
Icon: utils.GetOrDefault(source.Props, "icon", "").(string),
}
anthroveUser.UserID = models.AnthroveUserID(utils.GetOrDefault(user.Props, "user_id", "").(string))
anthroveUser.Relationship = userRelationships
for j := range userRelationships {
anthroveUser.Relationship[j].Source = userSources
}
}
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
}).Trace("database: got anthrove user")
return &anthroveUser, nil
}
func GetAllAnthroveUserIDs(ctx context.Context, driver neo4j.DriverWithContext) ([]models.AnthroveUserID, error) {
var err error
var anthroveUsers []models.AnthroveUserID
query := `
MATCH (anthroveUser:User)
RETURN anthroveUser
`
result, err := neo4j.ExecuteQuery(ctx, driver, query, nil, neo4j.EagerResultTransformer)
if err != nil {
return nil, err
}
if len(result.Records) == 0 {
log.Warnf("No users found, this should not be happening!")
return []models.AnthroveUserID{}, nil
}
for i := range result.Records {
record := result.Records[i]
user, _, err := neo4j.GetRecordValue[neo4j.Node](record, "anthroveUser")
if err != nil {
return nil, err
}
anthroveUsers = append(anthroveUsers, models.AnthroveUserID(fmt.Sprintf(user.Props["user_id"].(string))))
}
log.WithFields(log.Fields{
"anthrove_user_id_count": len(anthroveUsers),
}).Trace("database: got al anthrove user IDs")
return anthroveUsers, nil
}
func GetUserFavoriteNodeWithPagination(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID, skip int, limit int) (*graphModels.FavoriteList, error) {
var err error
var favoritePosts []graphModels.FavoritePost
query := `
CALL {
MATCH (user:User{user_id: $anthrove_user_id})-[r:FAV]->(p:AnthrovePost)
RETURN p.post_id AS post_id
ORDER BY id(p) ASC
SKIP $skip
LIMIT $limit
}
WITH collect(post_id) AS faves
MATCH (a:AnthrovePost)<-[r:REFERENCE]-(s:Source)
WHERE a.post_id in faves
RETURN a AS anthrovePost, r AS postRelation, s AS Source
ORDER BY id(a) ASC
`
params := map[string]any{
"anthrove_user_id": anthroveUserID,
"limit": limit,
"skip": skip,
}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return nil, err
}
if len(result.Records) == 0 {
return nil, nil
}
for i := range result.Records {
record := result.Records[i]
anthrovePost, _, err := neo4j.GetRecordValue[neo4j.Node](record, "anthrovePost")
if err != nil {
return nil, err
}
postRelation, _, err := neo4j.GetRecordValue[neo4j.Relationship](record, "postRelation")
if err != nil {
return nil, err
}
source, _, err := neo4j.GetRecordValue[neo4j.Node](record, "Source")
if err != nil {
return nil, err
}
if len(favoritePosts) != 0 && favoritePosts[len(favoritePosts)-1].AnthrovePost.PostID == models.AnthrovePostID(anthrovePost.Props["post_id"].(string)) {
favoritePosts[len(favoritePosts)-1].Relations = append(favoritePosts[len(favoritePosts)-1].Relations, graphModels.FavoriteRelations{
SourcesID: source.Props["display_name"].(string),
Relations: graphModels.AnthrovePostRelationship{
PostID: postRelation.Props["source_post_id"].(string),
Url: postRelation.Props["url"].(string),
},
})
} else {
favoritePosts = append(favoritePosts, graphModels.FavoritePost{
AnthrovePost: graphModels.AnthrovePost{
PostID: models.AnthrovePostID(anthrovePost.Props["post_id"].(string)),
Rating: models.Rating(anthrovePost.Props["rating"].(string)),
},
Relations: []graphModels.FavoriteRelations{{
SourcesID: source.Props["display_name"].(string),
Relations: graphModels.AnthrovePostRelationship{
PostID: postRelation.Props["source_post_id"].(string),
Url: postRelation.Props["url"].(string),
},
}},
})
}
}
log.WithFields(log.Fields{
"anthrove_user_fav_count": len(favoritePosts),
}).Trace("database: got al anthrove user favorites")
return &graphModels.FavoriteList{Posts: favoritePosts}, nil
}
func GetUserTagNodeWitRelationToFavedPosts(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID) ([]graphModels.TagsWithFrequency, error) {
var userTags []graphModels.TagsWithFrequency
query := `
MATCH (u:User {user_id: $anthrove_user_id})-[:FAV]->(:AnthrovePost)-[:HAS]->(t:Tag)
RETURN t as tag, COUNT(t) AS frequency
ORDER BY frequency DESC
`
params := map[string]any{
"anthrove_user_id": anthroveUserID,
}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return nil, err
}
if len(result.Records) == 0 {
return nil, nil
}
for i := range result.Records {
record := result.Records[i]
tag, _, err := neo4j.GetRecordValue[neo4j.Node](record, "tag")
if err != nil {
return nil, err
}
frequency, _, err := neo4j.GetRecordValue[int64](record, "frequency")
if err != nil {
return nil, err
}
userTags = append(userTags, graphModels.TagsWithFrequency{
Frequency: frequency,
Tags: graphModels.AnthroveTag{
Name: tag.Props["name"].(string),
Type: tag.Props["type"].(string),
},
})
}
log.WithFields(log.Fields{
"tag_amount": len(userTags),
}).Trace("database: created tag node")
return userTags, nil
}

View File

@ -5,62 +5,89 @@ import (
"errors" "errors"
"fmt" "fmt"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models" "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"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
) )
func CreatePost(ctx context.Context, db *gorm.DB, anthrovePost *models.Post) error { func CreateAnthrovePostNode(ctx context.Context, db *gorm.DB, anthrovePostID models.AnthrovePostID, anthroveRating models.Rating) error {
err := db.WithContext(ctx).Create(&anthrovePost).Error post := pgModels.Post{
BaseModel: pgModels.BaseModel{
ID: string(anthrovePostID),
},
Rating: anthroveRating,
}
err := db.WithContext(ctx).Create(&post).Error
if err != nil { if err != nil {
return err return err
} }
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"anthrove_post_id": anthrovePost.ID, "anthrove_post_id": anthrovePostID,
"anthrove_post_rating": anthrovePost.Rating, "anthrove_post_rating": anthroveRating,
}).Trace("database: created anthrove post") }).Trace("database: created anthrove post")
return nil return nil
} }
func GetPostByAnthroveID(ctx context.Context, db *gorm.DB, anthrovePostID string) (*models.Post, error) { func CheckIfAnthrovePostNodeExistsByAnthroveID(ctx context.Context, db *gorm.DB, anthrovePostID models.AnthrovePostID) (bool, error) {
if anthrovePostID == "" { if anthrovePostID == "" {
return nil, fmt.Errorf("anthrovePostID is required") return false, fmt.Errorf("anthrovePostID is required")
} }
var post models.Post return executeCheckQuery(ctx, db, "id = ?", string(anthrovePostID))
err := db.WithContext(ctx).First(&post, "id = ?", anthrovePostID).Error
if err != nil {
return nil, err
}
return &post, nil
} }
func GetPostBySourceURL(ctx context.Context, db *gorm.DB, sourceURL string) (*models.Post, error) { func CheckIfAnthrovePostNodeExistsBySourceURL(ctx context.Context, db *gorm.DB, sourceURL string) (*graphModels.AnthrovePost, bool, error) {
var post models.Post postRef := pgModels.PostReference{}
err := db.WithContext(ctx).Raw(`SELECT p.id AS id, p.rating as rating FROM "Post" AS p INNER JOIN "PostReference" AS pr ON p.id = pr.post_id AND pr.url = $1 LIMIT 1`, sourceURL).First(&post).Error err := db.WithContext(ctx).Model(&pgModels.PostReference{}).Where("url = ?", sourceURL).First(&postRef).Error
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil return nil, false, nil
} }
return nil, err return nil, false, err
} }
return &post, nil var post pgModels.Post
} err = db.WithContext(ctx).First(&post, "id = ?", postRef.PostID).Error
func GetPostBySourceID(ctx context.Context, db *gorm.DB, sourceID string) (*models.Post, error) {
var post models.Post
err := db.WithContext(ctx).Raw(`SELECT p.id AS id, p.rating as rating FROM "Post" AS p INNER JOIN "PostReference" AS pr ON p.id = pr.post_id AND pr.source_id = $1 LIMIT 1`, sourceID).First(&post).Error
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { return nil, false, err
return nil, nil
}
return nil, err
} }
return &post, nil pgPost := graphModels.AnthrovePost{
PostID: models.AnthrovePostID(post.ID),
Rating: post.Rating,
}
//TODO Now test it! :D
// Naaa
// D:
return &pgPost, true, nil
}
func CheckIfAnthrovePostNodeExistsBySourceID(ctx context.Context, db *gorm.DB, sourceID string) (bool, error) {
return executeCheckQuery(ctx, db, "source_id = ?", sourceID)
}
func executeCheckQuery(ctx context.Context, db *gorm.DB, query string, args ...interface{}) (bool, error) {
var count int64
err := db.WithContext(ctx).Model(&pgModels.Post{}).Where(query, args...).Count(&count).Error
if err != nil {
return false, err
}
exists := count > 0
log.WithFields(log.Fields{
"query": query,
"args": args,
"exists": exists,
}).Trace("database: executed check query")
return exists, nil
} }

View File

@ -2,7 +2,6 @@ package postgres
import ( import (
"context" "context"
"fmt"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models" "git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/test" "git.dragse.it/anthrove/otter-space-sdk/test"
_ "github.com/lib/pq" _ "github.com/lib/pq"
@ -20,24 +19,11 @@ func TestCreateAnthrovePostNode(t *testing.T) {
} }
defer container.Terminate(ctx) defer container.Terminate(ctx)
// Setup Tests
validPost := &models.Post{
BaseModel: models.BaseModel{
ID: fmt.Sprintf("%025s", "1"),
},
Rating: "safe",
}
invalidPost := &models.Post{
Rating: "error",
}
// Test
type args struct { type args struct {
ctx context.Context ctx context.Context
db *gorm.DB db *gorm.DB
anthrovePost *models.Post anthrovePostID models.AnthrovePostID
anthroveRating models.Rating
} }
tests := []struct { tests := []struct {
name string name string
@ -47,32 +33,34 @@ func TestCreateAnthrovePostNode(t *testing.T) {
{ {
name: "Test 1: Valid AnthrovePostID and Rating", name: "Test 1: Valid AnthrovePostID and Rating",
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
db: gormDB, db: gormDB,
anthrovePost: validPost, anthrovePostID: "1234",
anthroveRating: models.Rating("safe"),
}, },
wantErr: false, wantErr: false,
}, },
{ {
name: "Test 2: Invalid Rating", name: "Test 2: Invalid AnthrovePostID and Rating",
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
db: gormDB, db: gormDB,
anthrovePost: invalidPost, anthrovePostID: "",
anthroveRating: "a4dsa4d",
}, },
wantErr: true, wantErr: true,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if err := CreatePost(tt.args.ctx, tt.args.db, tt.args.anthrovePost); (err != nil) != tt.wantErr { if err := CreateAnthrovePostNode(tt.args.ctx, tt.args.db, tt.args.anthrovePostID, tt.args.anthroveRating); (err != nil) != tt.wantErr {
t.Errorf("CreatePost() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("CreateAnthrovePostNode() error = %v, wantErr %v", err, tt.wantErr)
} }
}) })
} }
} }
func TestGetPostByAnthroveID(t *testing.T) { func TestCheckIfAnthrovePostNodeExistsByAnthroveID(t *testing.T) {
// Setup trow away container // Setup trow away container
ctx := context.Background() ctx := context.Background()
container, gormDB, err := test.StartPostgresContainer(ctx) container, gormDB, err := test.StartPostgresContainer(ctx)
@ -81,284 +69,68 @@ func TestGetPostByAnthroveID(t *testing.T) {
} }
defer container.Terminate(ctx) defer container.Terminate(ctx)
// Setup Tests // Setup Test
post := &models.Post{ err = CreateAnthrovePostNode(ctx, gormDB, "1234", "safe")
BaseModel: models.BaseModel{
ID: fmt.Sprintf("%025s", "1"),
},
Rating: "safe",
}
err = CreatePost(ctx, gormDB, post)
if err != nil { if err != nil {
t.Fatal("Could not create post", err) t.Fatal(err)
} }
// Test // Test
type args struct { type args struct {
ctx context.Context ctx context.Context
db *gorm.DB db *gorm.DB
anthrovePostID string anthrovePostID models.AnthrovePostID
} }
tests := []struct { tests := []struct {
name string name string
args args args args
want *models.Post want bool
wantErr bool wantErr bool
}{ }{
{ {
name: "Test 1: Valid anthrovePostID", name: "Test 1: Valid AnthroveID",
args: args{
ctx: ctx,
db: gormDB,
anthrovePostID: post.ID,
},
want: post,
wantErr: false,
},
{
name: "Test 2: Invalid anthrovePostID",
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthrovePostID: "1234", anthrovePostID: "1234",
}, },
want: nil, want: true,
wantErr: true, wantErr: false,
}, },
{ {
name: "Test 3: No anthrovePostID", name: "Test 2: Invalid AnthroveID",
args: args{
ctx: ctx,
db: gormDB,
anthrovePostID: "123456",
},
want: false,
wantErr: false,
},
{
name: "Test 3: No AnthroveID",
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthrovePostID: "", anthrovePostID: "",
}, },
want: nil, want: false,
wantErr: true, wantErr: true,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := GetPostByAnthroveID(tt.args.ctx, tt.args.db, tt.args.anthrovePostID) got, err := CheckIfAnthrovePostNodeExistsByAnthroveID(tt.args.ctx, tt.args.db, tt.args.anthrovePostID)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("GetPostByAnthroveID() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("CheckIfAnthrovePostNodeExistsByAnthroveID() error = %v, wantErr %v", err, tt.wantErr)
return return
} }
if !checkPost(got, tt.want) { if got != tt.want {
t.Errorf("GetPostByAnthroveID() got = %v, want %v", got, tt.want) t.Errorf("CheckIfAnthrovePostNodeExistsByAnthroveID() got = %v, want %v", got, tt.want)
} }
}) })
} }
} }
func TestGetPostBySourceURL(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 Tests
post := &models.Post{
BaseModel: models.BaseModel{
ID: fmt.Sprintf("%025s", "1"),
},
Rating: "safe",
}
err = CreatePost(ctx, gormDB, post)
if err != nil {
t.Fatal("Could not create post", err)
}
source := models.Source{
BaseModel: models.BaseModel{ID: fmt.Sprintf("%025s", "1")},
DisplayName: "e621",
Domain: "e621.net",
Icon: "https://e621.net/icon.ico",
}
err = CreateSourceNode(ctx, gormDB, &source)
if err != nil {
t.Fatal("Could not create source", err)
}
err = EstablishAnthrovePostToSourceLink(ctx, gormDB, post.ID, source.Domain)
if err != nil {
t.Fatal("Could not create source reference", err)
}
// Test
type args struct {
ctx context.Context
db *gorm.DB
sourceURL string
}
tests := []struct {
name string
args args
want *models.Post
wantErr bool
}{
{
name: "Test 1: Valid sourceURL",
args: args{
ctx: ctx,
db: gormDB,
sourceURL: source.Domain,
},
want: post,
wantErr: false,
},
{
name: "Test 2: Invalid sourceURL",
args: args{
ctx: ctx,
db: gormDB,
sourceURL: "1234",
},
want: nil,
wantErr: false,
},
{
name: "Test 3: No sourceURL",
args: args{
ctx: ctx,
db: gormDB,
sourceURL: "",
},
want: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetPostBySourceURL(tt.args.ctx, tt.args.db, tt.args.sourceURL)
if (err != nil) != tt.wantErr {
t.Errorf("GetPostBySourceURL() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !checkPost(got, tt.want) {
t.Errorf("GetPostBySourceURL() got = %v, want %v", got, tt.want)
}
})
}
}
func TestGetPostBySourceID(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 Tests
post := &models.Post{
BaseModel: models.BaseModel{
ID: fmt.Sprintf("%025s", "1"),
},
Rating: "safe",
}
err = CreatePost(ctx, gormDB, post)
if err != nil {
t.Fatal("Could not create post", err)
}
source := models.Source{
BaseModel: models.BaseModel{ID: fmt.Sprintf("%025s", "1")},
DisplayName: "e621",
Domain: "e621.net",
Icon: "https://e621.net/icon.ico",
}
err = CreateSourceNode(ctx, gormDB, &source)
if err != nil {
t.Fatal("Could not create source", err)
}
err = EstablishAnthrovePostToSourceLink(ctx, gormDB, post.ID, source.Domain)
if err != nil {
t.Fatal("Could not create source reference", err)
}
// Test
type args struct {
ctx context.Context
db *gorm.DB
sourceID string
}
tests := []struct {
name string
args args
want *models.Post
wantErr bool
}{
{
name: "Test 1: Valid sourceID",
args: args{
ctx: ctx,
db: gormDB,
sourceID: source.ID,
},
want: post,
wantErr: false,
},
{
name: "Test 2: Invalid sourceID",
args: args{
ctx: ctx,
db: gormDB,
sourceID: "1234",
},
want: nil,
wantErr: false,
},
{
name: "Test 3: No sourceID",
args: args{
ctx: ctx,
db: gormDB,
sourceID: "",
},
want: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetPostBySourceID(tt.args.ctx, tt.args.db, tt.args.sourceID)
if (err != nil) != tt.wantErr {
t.Errorf("GetPostBySourceID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !checkPost(got, tt.want) {
t.Errorf("GetPostBySourceID() got = %v, want %v", got, tt.want)
}
})
}
}
func checkPost(got *models.Post, want *models.Post) bool {
if got == nil {
return true
}
if got.ID != want.ID {
return false
}
if got.Rating != want.Rating {
return false
}
return true
}

View File

@ -3,25 +3,27 @@ package postgres
import ( import (
"context" "context"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models" "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"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
) )
func EstablishAnthrovePostToSourceLink(ctx context.Context, db *gorm.DB, anthrovePostID string, sourceDomain string) error { func EstablishAnthrovePostToSourceLink(ctx context.Context, db *gorm.DB, anthrovePostID models.AnthrovePostID, anthroveSourceDomain string, anthrovePostRelationship *graphModels.AnthrovePostRelationship) error {
var source models.Source var source pgModels.Source
var err error var err error
// Find the source // Find the source
err = db.WithContext(ctx).Where("domain = ?", sourceDomain).First(&source).Error err = db.WithContext(ctx).Where("domain = ?", anthroveSourceDomain).First(&source).Error
if err != nil { if err != nil {
return err return err
} }
// Establish the relationship // Establish the relationship
err = db.WithContext(ctx).Create(models.PostReference{ err = db.WithContext(ctx).Create(pgModels.PostReference{
PostID: anthrovePostID, PostID: string(anthrovePostID),
SourceID: source.ID, SourceID: source.ID,
URL: sourceDomain, URL: anthrovePostRelationship.Url,
}).Error }).Error
if err != nil { if err != nil {
@ -30,14 +32,14 @@ func EstablishAnthrovePostToSourceLink(ctx context.Context, db *gorm.DB, anthrov
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"anthrove_post_id": anthrovePostID, "anthrove_post_id": anthrovePostID,
"anthrove_source_domain": sourceDomain, "anthrove_source_domain": anthroveSourceDomain,
}).Trace("database: created anthrove post to source link") }).Trace("database: created anthrove post to source link")
return nil return nil
} }
func EstablishUserToPostLink(ctx context.Context, db *gorm.DB, anthroveUserID string, anthrovePostID string) error { func EstablishUserToPostLink(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) error {
userFavorite := models.UserFavorite{ userFavorite := pgModels.UserFavorite{
UserID: string(anthroveUserID), UserID: string(anthroveUserID),
PostID: string(anthrovePostID), PostID: string(anthrovePostID),
} }
@ -55,9 +57,9 @@ func EstablishUserToPostLink(ctx context.Context, db *gorm.DB, anthroveUserID st
return nil return nil
} }
func CheckUserToPostLink(ctx context.Context, db *gorm.DB, anthroveUserID string, anthrovePostID string) (bool, error) { func CheckUserToPostLink(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) (bool, error) {
var count int64 var count int64
err := db.WithContext(ctx).Model(&models.UserFavorite{}).Where("user_id = ? AND post_id = ?", string(anthroveUserID), string(anthrovePostID)).Count(&count).Error err := db.WithContext(ctx).Model(&pgModels.UserFavorite{}).Where("user_id = ? AND post_id = ?", string(anthroveUserID), string(anthrovePostID)).Count(&count).Error
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -2,119 +2,15 @@ package postgres
import ( import (
"context" "context"
"fmt"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models" "git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/pgModels"
"git.dragse.it/anthrove/otter-space-sdk/test" "git.dragse.it/anthrove/otter-space-sdk/test"
"gorm.io/gorm" "gorm.io/gorm"
"testing" "testing"
) )
func TestCheckUserToPostLink(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
err = CreateUser(ctx, gormDB, "1")
if err != nil {
t.Fatal(err)
}
post := &models.Post{
BaseModel: models.BaseModel{
ID: fmt.Sprintf("%025s", "1"),
},
Rating: "safe",
}
err = CreatePost(ctx, gormDB, post)
if err != nil {
t.Fatal(err)
}
err = EstablishUserToPostLink(ctx, gormDB, "1", post.ID)
if err != nil {
t.Fatal(err)
}
// Test
type args struct {
ctx context.Context
db *gorm.DB
anthroveUserID string
anthrovePostID string
}
tests := []struct {
name string
args args
want bool
wantErr bool
}{
{
name: "Test 1: Valid AnthroveUserID and AnthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "1",
anthrovePostID: post.ID,
},
want: true,
wantErr: false,
},
{
name: "Test 2: Valid AnthroveUserID and invalid AnthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "1",
anthrovePostID: "qadw",
},
want: false,
wantErr: false,
},
{
name: "Test 3: Valid AnthrovePostID and invalid AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "123",
anthrovePostID: post.ID,
},
want: false,
wantErr: false,
},
{
name: "Test 4: Invalid AnthrovePostID and invalid AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "123",
anthrovePostID: "123456",
},
want: false,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CheckUserToPostLink(tt.args.ctx, tt.args.db, tt.args.anthroveUserID, tt.args.anthrovePostID)
if (err != nil) != tt.wantErr {
t.Errorf("CheckUserToPostLink() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("CheckUserToPostLink() got = %v, want %v", got, tt.want)
}
})
}
}
func TestEstablishAnthrovePostToSourceLink(t *testing.T) { func TestEstablishAnthrovePostToSourceLink(t *testing.T) {
// Setup trow away container // Setup trow away container
ctx := context.Background() ctx := context.Background()
container, gormDB, err := test.StartPostgresContainer(ctx) container, gormDB, err := test.StartPostgresContainer(ctx)
@ -124,19 +20,13 @@ func TestEstablishAnthrovePostToSourceLink(t *testing.T) {
defer container.Terminate(ctx) defer container.Terminate(ctx)
// Setup Test // Setup Test
post := &models.Post{
BaseModel: models.BaseModel{
ID: fmt.Sprintf("%025s", "1"),
},
Rating: "safe",
}
err = CreatePost(ctx, gormDB, post) err = CreateAnthrovePostNode(ctx, gormDB, "1234", "safe")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
source := &models.Source{ source := &pgModels.Source{
DisplayName: "e621", DisplayName: "e621",
Domain: "e621.net", Domain: "e621.net",
Icon: "icon.e621.net", Icon: "icon.e621.net",
@ -147,12 +37,12 @@ func TestEstablishAnthrovePostToSourceLink(t *testing.T) {
} }
// Test // Test
type args struct { type args struct {
ctx context.Context ctx context.Context
db *gorm.DB db *gorm.DB
anthrovePostID string anthrovePostID models.AnthrovePostID
sourceDomain string anthroveSourceDomain string
anthrovePostRelationship *models.PostReference
} }
tests := []struct { tests := []struct {
name string name string
@ -162,47 +52,47 @@ func TestEstablishAnthrovePostToSourceLink(t *testing.T) {
{ {
name: "Test 1: Valid AnthrovePostID and anthroveSourceDomain", name: "Test 1: Valid AnthrovePostID and anthroveSourceDomain",
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthrovePostID: post.ID, anthrovePostID: "1234",
sourceDomain: "e621.net", anthroveSourceDomain: "e621.net",
}, },
wantErr: false, wantErr: false,
}, },
{ {
name: "Test 2: Invalid AnthrovePostID and Valid anthroveSourceDomain", name: "Test 2: Invalid AnthrovePostID and Valid anthroveSourceDomain",
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthrovePostID: "123456", anthrovePostID: "123456",
sourceDomain: "e621.net", anthroveSourceDomain: "e621.net",
}, },
wantErr: true, wantErr: true,
}, },
{ {
name: "Test 3: Invalid anthroveSourceDomain and Valid AnthrovePostID", name: "Test 3: Invalid anthroveSourceDomain and Valid AnthrovePostID",
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthrovePostID: "1234", anthrovePostID: "1234",
sourceDomain: "fa.banana", anthroveSourceDomain: "fa.banana",
}, },
wantErr: true, wantErr: true,
}, },
{ {
name: "Test 4: Invalid anthroveSourceDomain and Invalid AnthrovePostID", name: "Test 4: Invalid anthroveSourceDomain and Invalid AnthrovePostID",
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthrovePostID: "696969", anthrovePostID: "696969",
sourceDomain: "hehe.funny.number", anthroveSourceDomain: "hehe.funny.number",
}, },
wantErr: true, wantErr: true,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if err := EstablishAnthrovePostToSourceLink(tt.args.ctx, tt.args.db, tt.args.anthrovePostID, tt.args.sourceDomain); (err != nil) != tt.wantErr { if err := EstablishAnthrovePostToSourceLink(tt.args.ctx, tt.args.db, tt.args.anthrovePostID, tt.args.anthroveSourceDomain); (err != nil) != tt.wantErr {
t.Errorf("EstablishAnthrovePostToSourceLink() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("EstablishAnthrovePostToSourceLink() error = %v, wantErr %v", err, tt.wantErr)
} }
}) })
@ -224,14 +114,7 @@ func TestEstablishUserToPostLink(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
post := &models.Post{ err = CreateAnthrovePostNode(ctx, gormDB, "1234", "safe")
BaseModel: models.BaseModel{
ID: fmt.Sprintf("%025s", "1"),
},
Rating: "safe",
}
err = CreatePost(ctx, gormDB, post)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -240,8 +123,8 @@ func TestEstablishUserToPostLink(t *testing.T) {
type args struct { type args struct {
ctx context.Context ctx context.Context
db *gorm.DB db *gorm.DB
anthroveUserID string anthroveUserID models.AnthroveUserID
anthrovePostID string anthrovePostID models.AnthrovePostID
} }
tests := []struct { tests := []struct {
name string name string
@ -254,7 +137,7 @@ func TestEstablishUserToPostLink(t *testing.T) {
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthroveUserID: "1", anthroveUserID: "1",
anthrovePostID: post.ID, anthrovePostID: "1234",
}, },
wantErr: false, wantErr: false,
}, },
@ -297,3 +180,101 @@ func TestEstablishUserToPostLink(t *testing.T) {
}) })
} }
} }
func TestCheckUserToPostLink(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
err = CreateUser(ctx, gormDB, "1")
if err != nil {
t.Fatal(err)
}
err = CreateAnthrovePostNode(ctx, gormDB, "1234", "safe")
if err != nil {
t.Fatal(err)
}
err = EstablishUserToPostLink(ctx, gormDB, "1", "1234")
if err != nil {
t.Fatal(err)
}
// Test
type args struct {
ctx context.Context
db *gorm.DB
anthroveUserID models.AnthroveUserID
anthrovePostID models.AnthrovePostID
}
tests := []struct {
name string
args args
want bool
wantErr bool
}{
{
name: "Test 1: Valid AnthroveUserID and AnthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "1",
anthrovePostID: "1234",
},
want: true,
wantErr: false,
},
{
name: "Test 2: Valid AnthroveUserID and invalid AnthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "1",
anthrovePostID: "123456",
},
want: false,
wantErr: false,
},
{
name: "Test 3: Valid AnthrovePostID and invalid AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "123",
anthrovePostID: "1234",
},
want: false,
wantErr: false,
},
{
name: "Test 4: Invalid AnthrovePostID and invalid AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "123",
anthrovePostID: "123456",
},
want: false,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CheckUserToPostLink(tt.args.ctx, tt.args.db, tt.args.anthroveUserID, tt.args.anthrovePostID)
if (err != nil) != tt.wantErr {
t.Errorf("CheckUserToPostLink() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("CheckUserToPostLink() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -3,20 +3,20 @@ package postgres
import ( import (
"context" "context"
"fmt" "fmt"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/pgModels"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
) )
// CreateSourceNode creates a pgModels.Source // CreateSourceNode creates a pgModels.Source
func CreateSourceNode(ctx context.Context, db *gorm.DB, anthroveSource *models.Source) error { func CreateSourceNode(ctx context.Context, db *gorm.DB, anthroveSource *pgModels.Source) error {
if anthroveSource.Domain == "" { if anthroveSource.Domain == "" {
return fmt.Errorf("anthroveSource domain is required") return fmt.Errorf("anthroveSource domain is required")
} }
result := db.WithContext(ctx).Where(models.Source{Domain: anthroveSource.Domain}).FirstOrCreate(anthroveSource) result := db.WithContext(ctx).Where(pgModels.Source{Domain: anthroveSource.Domain}).FirstOrCreate(anthroveSource)
if result.Error != nil { if result.Error != nil {
return result.Error return result.Error
@ -32,8 +32,8 @@ func CreateSourceNode(ctx context.Context, db *gorm.DB, anthroveSource *models.S
} }
// GetAllSourceNodes returns a list of all pgModels.Source // GetAllSourceNodes returns a list of all pgModels.Source
func GetAllSourceNodes(ctx context.Context, db *gorm.DB) ([]models.Source, error) { func GetAllSourceNodes(ctx context.Context, db *gorm.DB) ([]pgModels.Source, error) {
var sources []models.Source var sources []pgModels.Source
result := db.WithContext(ctx).Find(&sources) result := db.WithContext(ctx).Find(&sources)
@ -49,8 +49,8 @@ func GetAllSourceNodes(ctx context.Context, db *gorm.DB) ([]models.Source, error
} }
// GetSourceNodesByURL returns the first source it finds based on the domain // GetSourceNodesByURL returns the first source it finds based on the domain
func GetSourceNodesByURL(ctx context.Context, db *gorm.DB, domain string) (*models.Source, error) { func GetSourceNodesByURL(ctx context.Context, db *gorm.DB, domain string) (*pgModels.Source, error) {
var sources models.Source var sources pgModels.Source
if domain == "" { if domain == "" {
return nil, fmt.Errorf("domain is required") return nil, fmt.Errorf("domain is required")

View File

@ -2,7 +2,7 @@ package postgres
import ( import (
"context" "context"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models" "git.dragse.it/anthrove/otter-space-sdk/pkg/models/pgModels"
"git.dragse.it/anthrove/otter-space-sdk/test" "git.dragse.it/anthrove/otter-space-sdk/test"
"gorm.io/gorm" "gorm.io/gorm"
"testing" "testing"
@ -19,13 +19,13 @@ func TestCreateSourceNode(t *testing.T) {
// Setup Test // Setup Test
validSource := &models.Source{ validAnthroveSource := &pgModels.Source{
DisplayName: "e621", DisplayName: "e621",
Domain: "e621.net", Domain: "e621.net",
Icon: "icon.e621.net", Icon: "icon.e621.net",
} }
invalidSource := &models.Source{ invalidAnthroveSource := &pgModels.Source{
Domain: "", Domain: "",
} }
@ -33,7 +33,7 @@ func TestCreateSourceNode(t *testing.T) {
type args struct { type args struct {
ctx context.Context ctx context.Context
db *gorm.DB db *gorm.DB
anthroveSource *models.Source anthroveSource *pgModels.Source
} }
tests := []struct { tests := []struct {
name string name string
@ -45,7 +45,7 @@ func TestCreateSourceNode(t *testing.T) {
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthroveSource: validSource, anthroveSource: validAnthroveSource,
}, },
wantErr: false, wantErr: false,
}, },
@ -54,7 +54,7 @@ func TestCreateSourceNode(t *testing.T) {
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthroveSource: invalidSource, anthroveSource: invalidAnthroveSource,
}, },
wantErr: true, wantErr: true,
}, },
@ -63,7 +63,7 @@ func TestCreateSourceNode(t *testing.T) {
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthroveSource: validSource, anthroveSource: validAnthroveSource,
}, },
wantErr: false, wantErr: false,
}, },
@ -88,7 +88,7 @@ func TestGetAllSourceNodes(t *testing.T) {
// Setup Test // Setup Test
sources := []models.Source{ sources := []pgModels.Source{
{ {
DisplayName: "e621", DisplayName: "e621",
Domain: "e621.net", Domain: "e621.net",
@ -121,7 +121,7 @@ func TestGetAllSourceNodes(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want []models.Source want []pgModels.Source
wantErr bool wantErr bool
}{ }{
{ {
@ -159,7 +159,7 @@ func TestGetSourceNodesByURL(t *testing.T) {
// Setup Test // Setup Test
source := &models.Source{ source := &pgModels.Source{
DisplayName: "e621", DisplayName: "e621",
Domain: "e621.net", Domain: "e621.net",
Icon: "icon.e621.net", Icon: "icon.e621.net",
@ -179,7 +179,7 @@ func TestGetSourceNodesByURL(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want *models.Source want *pgModels.Source
wantErr bool wantErr bool
}{ }{
{ {
@ -227,7 +227,7 @@ func TestGetSourceNodesByURL(t *testing.T) {
} }
} }
func checkSourcesNode(got []models.Source, want []models.Source) bool { func checkSourcesNode(got []pgModels.Source, want []pgModels.Source) bool {
for i, source := range want { for i, source := range want {
if source.DisplayName != got[i].DisplayName { if source.DisplayName != got[i].DisplayName {
return false return false
@ -244,7 +244,7 @@ func checkSourcesNode(got []models.Source, want []models.Source) bool {
} }
func checkSourceNode(got *models.Source, want *models.Source) bool { func checkSourceNode(got *pgModels.Source, want *pgModels.Source) bool {
if want == nil && got == nil { if want == nil && got == nil {
return true return true

View File

@ -4,11 +4,12 @@ import (
"context" "context"
"fmt" "fmt"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models" "git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/pgModels"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
) )
func createTag(ctx context.Context, db *gorm.DB, tag *models.Tag) error { func CreateTag(ctx context.Context, db *gorm.DB, tag *pgModels.Tag) error {
resultTag := db.WithContext(ctx).Where(tag).Create(tag) resultTag := db.WithContext(ctx).Where(tag).Create(tag)
@ -24,18 +25,20 @@ func createTag(ctx context.Context, db *gorm.DB, tag *models.Tag) error {
return nil return nil
} }
func CreateTagNodeWitRelation(ctx context.Context, db *gorm.DB, PostID string, tag *models.Tag) error { func CreateTagNodeWitRelation(ctx context.Context, db *gorm.DB, PostID models.AnthrovePostID, tag *pgModels.Tag) error {
if PostID == "" { if PostID == "" {
return fmt.Errorf("PostID is empty") return fmt.Errorf("PostID is empty")
} }
if tag == nil { resultTag := db.WithContext(ctx).Where(tag).FirstOrCreate(tag)
return fmt.Errorf("tag is nill")
if resultTag.Error != nil {
return resultTag.Error
} }
pgPost := models.Post{ pgPost := pgModels.Post{
BaseModel: models.BaseModel{ BaseModel: pgModels.BaseModel{
ID: string(PostID), ID: string(PostID),
}, },
} }
@ -55,8 +58,8 @@ func CreateTagNodeWitRelation(ctx context.Context, db *gorm.DB, PostID string, t
return nil return nil
} }
func GetTags(ctx context.Context, db *gorm.DB) ([]models.Tag, error) { func GetTags(ctx context.Context, db *gorm.DB) ([]pgModels.Tag, error) {
var tags []models.Tag var tags []pgModels.Tag
result := db.WithContext(ctx).Find(&tags) result := db.WithContext(ctx).Find(&tags)
if result.Error != nil { if result.Error != nil {
return nil, result.Error return nil, result.Error

View File

@ -2,8 +2,8 @@ package postgres
import ( import (
"context" "context"
"fmt"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models" "git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/pgModels"
"git.dragse.it/anthrove/otter-space-sdk/test" "git.dragse.it/anthrove/otter-space-sdk/test"
"gorm.io/gorm" "gorm.io/gorm"
"testing" "testing"
@ -20,19 +20,12 @@ func TestCreateTagNodeWitRelation(t *testing.T) {
// Setup Test // Setup Test
post := &models.Post{ err = CreateAnthrovePostNode(ctx, gormDB, "1234", "safe")
BaseModel: models.BaseModel{
ID: fmt.Sprintf("%025s", "1"),
},
Rating: "safe",
}
err = CreatePost(ctx, gormDB, post)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
tag := &models.Tag{ tag := &pgModels.Tag{
Name: "JayTheFerret", Name: "JayTheFerret",
Type: "artist", Type: "artist",
} }
@ -41,8 +34,8 @@ func TestCreateTagNodeWitRelation(t *testing.T) {
type args struct { type args struct {
ctx context.Context ctx context.Context
db *gorm.DB db *gorm.DB
PostID string PostID models.AnthrovePostID
tag *models.Tag tag *pgModels.Tag
} }
tests := []struct { tests := []struct {
name string name string
@ -54,17 +47,17 @@ func TestCreateTagNodeWitRelation(t *testing.T) {
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
PostID: post.ID, PostID: "1234",
tag: tag, tag: tag,
}, },
wantErr: false, wantErr: false,
}, },
{ {
name: "Test 2: Valid PostID and no Tag", name: "Test 2: Valid PostID and invalid Tag",
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
PostID: post.ID, PostID: "1234",
tag: nil, tag: nil,
}, },
wantErr: true, wantErr: true,
@ -110,7 +103,7 @@ func TestGetTags(t *testing.T) {
// Setup Test // Setup Test
tags := []models.Tag{ tags := []pgModels.Tag{
{ {
Name: "JayTheFerret", Name: "JayTheFerret",
Type: "artist", Type: "artist",
@ -126,7 +119,7 @@ func TestGetTags(t *testing.T) {
} }
for _, tag := range tags { for _, tag := range tags {
err = createTag(ctx, gormDB, &tag) err = CreateTag(ctx, gormDB, &tag)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -140,7 +133,7 @@ func TestGetTags(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want []models.Tag want []pgModels.Tag
wantErr bool wantErr bool
}{ }{
{ {
@ -167,7 +160,7 @@ func TestGetTags(t *testing.T) {
} }
} }
func checkTag(got []models.Tag, want []models.Tag) bool { func checkTag(got []pgModels.Tag, want []pgModels.Tag) bool {
for i, tag := range want { for i, tag := range want {
if tag.Type != got[i].Type { if tag.Type != got[i].Type {
return false return false

View File

@ -4,23 +4,25 @@ import (
"context" "context"
"fmt" "fmt"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models" "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"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
) )
func CreateUser(ctx context.Context, db *gorm.DB, anthroveUserID string) error { func CreateUser(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) error {
if anthroveUserID == "" { if anthroveUserID == "" {
return fmt.Errorf("anthroveUserID cannot be empty") return fmt.Errorf("anthroveUserID cannot be empty")
} }
user := models.User{ user := pgModels.User{
BaseModel: models.BaseModel{ BaseModel: pgModels.BaseModel{
ID: string(anthroveUserID), ID: string(anthroveUserID),
}, },
} }
err := db.WithContext(ctx).FirstOrCreate(&user).Error err := db.WithContext(ctx).Create(&user).Error
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID, "anthrove_user_id": anthroveUserID,
@ -31,18 +33,26 @@ func CreateUser(ctx context.Context, db *gorm.DB, anthroveUserID string) error {
return nil return nil
} }
func CreateUserNodeWithSourceRelation(ctx context.Context, db *gorm.DB, anthroveUserID string, sourceDomain string, userID string, username string) error { func CreateUserNodeWithSourceRelation(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceDomain string, userID string, username string) error {
if anthroveUserID == "" || username == "" || userID == "" { if anthroveUserID == "" || username == "" || userID == "" {
return fmt.Errorf("anthroveUserID cannot be empty") return fmt.Errorf("anthroveUserID cannot be empty")
} }
err := CreateUser(ctx, db, anthroveUserID) user := pgModels.User{
if err != nil { BaseModel: pgModels.BaseModel{
ID: string(anthroveUserID),
},
}
if err := db.WithContext(ctx).FirstOrCreate(&user).Error; err != nil {
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
}).Error("database: failed to find or create user")
return err return err
} }
source := models.Source{ source := pgModels.Source{
Domain: sourceDomain, Domain: sourceDomain,
} }
@ -53,12 +63,11 @@ func CreateUserNodeWithSourceRelation(ctx context.Context, db *gorm.DB, anthrove
return err return err
} }
userSource := models.UserSource{ userSource := pgModels.UserSource{
User: models.User{BaseModel: models.BaseModel{ID: anthroveUserID}}, UserID: user.ID,
SourceID: source.ID, SourceID: source.ID,
AccountUsername: username, AccountUsername: username,
AccountID: userID, AccountID: userID,
UserID: anthroveUserID,
} }
if err := db.WithContext(ctx).FirstOrCreate(&userSource).Error; err != nil { if err := db.WithContext(ctx).FirstOrCreate(&userSource).Error; err != nil {
@ -87,7 +96,7 @@ func GetUserFavoritesCount(ctx context.Context, db *gorm.DB, anthroveUserID mode
} }
var count int64 var count int64
err := db.WithContext(ctx).Model(&models.UserFavorite{}).Where("user_id = ?", string(anthroveUserID)).Count(&count).Error err := db.WithContext(ctx).Model(&pgModels.UserFavorite{}).Where("user_id = ?", string(anthroveUserID)).Count(&count).Error
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID, "anthrove_user_id": anthroveUserID,
@ -103,11 +112,11 @@ func GetUserFavoritesCount(ctx context.Context, db *gorm.DB, anthroveUserID mode
return count, nil return count, nil
} }
func GetUserSourceLinks(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) (map[string]models.UserSource, error) { func GetUserSourceLink(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) (map[string]graphModels.AnthroveUserRelationship, error) {
var userSources []models.UserSource var userSources []pgModels.UserSource
userSourceMap := make(map[string]models.UserSource) userSourceMap := make(map[string]graphModels.AnthroveUserRelationship)
err := db.WithContext(ctx).Model(&models.UserSource{}).Where("user_id = ?", string(anthroveUserID)).Find(&userSources).Error err := db.WithContext(ctx).Model(&pgModels.UserSource{}).Where("user_id = ?", string(anthroveUserID)).Find(&userSources).Error
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID, "anthrove_user_id": anthroveUserID,
@ -116,8 +125,8 @@ func GetUserSourceLinks(ctx context.Context, db *gorm.DB, anthroveUserID models.
} }
for _, userSource := range userSources { for _, userSource := range userSources {
var source models.Source var source pgModels.Source
err = db.WithContext(ctx).Model(&models.Source{}).Where("id = ?", userSource.SourceID).First(&source).Error err = db.WithContext(ctx).Model(&pgModels.Source{}).Where("id = ?", userSource.SourceID).First(&source).Error
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"source_id": userSource.SourceID, "source_id": userSource.SourceID,
@ -125,10 +134,10 @@ func GetUserSourceLinks(ctx context.Context, db *gorm.DB, anthroveUserID models.
return nil, err return nil, err
} }
userSourceMap[source.DisplayName] = models.UserSource{ userSourceMap[source.DisplayName] = graphModels.AnthroveUserRelationship{
UserID: userSource.AccountID, UserID: userSource.AccountID,
AccountUsername: userSource.AccountUsername, Username: userSource.AccountUsername,
Source: models.Source{ Source: graphModels.AnthroveSource{
DisplayName: source.DisplayName, DisplayName: source.DisplayName,
Domain: source.Domain, Domain: source.Domain,
Icon: source.Icon, Icon: source.Icon,
@ -143,15 +152,15 @@ func GetUserSourceLinks(ctx context.Context, db *gorm.DB, anthroveUserID models.
return userSourceMap, nil return userSourceMap, nil
} }
func GetSpecifiedUserSourceLink(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]models.UserSource, error) { func GetSpecifiedUserSourceLink(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]graphModels.AnthroveUserRelationship, error) {
if anthroveUserID == "" || sourceDisplayName == "" { if anthroveUserID == "" || sourceDisplayName == "" {
return nil, fmt.Errorf("anthroveUserID or sourceDisplayName is empty") return nil, fmt.Errorf("anthroveUserID or sourceDisplayName is empty")
} }
var userSources []models.UserSource var userSources []pgModels.UserSource
userSourceMap := make(map[string]models.UserSource) userSourceMap := make(map[string]graphModels.AnthroveUserRelationship)
err := db.WithContext(ctx).Model(&models.UserSource{}).InnerJoins("Source", db.Where("display_name = ?", sourceDisplayName)).Where("user_id = ?", string(anthroveUserID)).First(&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 { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID, "anthrove_user_id": anthroveUserID,
@ -161,8 +170,8 @@ func GetSpecifiedUserSourceLink(ctx context.Context, db *gorm.DB, anthroveUserID
} }
for _, userSource := range userSources { for _, userSource := range userSources {
var source models.Source var source pgModels.Source
err = db.WithContext(ctx).Model(&models.Source{}).Where("id = ?", userSource.SourceID).First(&source).Error err = db.WithContext(ctx).Model(&pgModels.Source{}).Where("id = ?", userSource.SourceID).First(&source).Error
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"source_id": userSource.SourceID, "source_id": userSource.SourceID,
@ -170,10 +179,10 @@ func GetSpecifiedUserSourceLink(ctx context.Context, db *gorm.DB, anthroveUserID
return nil, err return nil, err
} }
userSourceMap[source.DisplayName] = models.UserSource{ userSourceMap[source.DisplayName] = graphModels.AnthroveUserRelationship{
UserID: userSource.AccountID, UserID: userSource.AccountID,
AccountUsername: userSource.AccountUsername, Username: userSource.AccountUsername,
Source: models.Source{ Source: graphModels.AnthroveSource{
DisplayName: source.DisplayName, DisplayName: source.DisplayName,
Domain: source.Domain, Domain: source.Domain,
Icon: source.Icon, Icon: source.Icon,
@ -189,18 +198,74 @@ func GetSpecifiedUserSourceLink(ctx context.Context, db *gorm.DB, anthroveUserID
return userSourceMap, nil return userSourceMap, nil
} }
func GetAllAnthroveUserIDs(ctx context.Context, db *gorm.DB) ([]string, error) { func GetAnthroveUser(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) (*graphModels.AnthroveUser, error) {
var users []models.User
var userIDs []string
err := db.WithContext(ctx).Model(&models.User{}).Find(&users).Error if anthroveUserID == "" {
return nil, fmt.Errorf("anthroveUserID cannot be empty")
}
var user pgModels.User
var userSources []pgModels.UserSource
anthroveUser := &graphModels.AnthroveUser{
UserID: anthroveUserID,
}
err := db.WithContext(ctx).First(&user, "id = ?", string(anthroveUserID)).Error
if err != nil {
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
}).Error("database: failed to get user")
return nil, err
}
err = db.WithContext(ctx).Model(&pgModels.UserSource{}).Where("user_id = ?", string(anthroveUserID)).Find(&userSources).Error
if err != nil {
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
}).Error("database: failed to get user sources")
return nil, err
}
for _, userSource := range userSources {
var source pgModels.Source
err = db.WithContext(ctx).Model(&pgModels.Source{}).Where("id = ?", userSource.SourceID).First(&source).Error
if err != nil {
log.WithFields(log.Fields{
"source_id": userSource.SourceID,
}).Error("database: failed to get source")
return nil, err
}
anthroveUser.Relationship = append(anthroveUser.Relationship, graphModels.AnthroveUserRelationship{
UserID: userSource.AccountID,
Username: userSource.AccountUsername,
Source: graphModels.AnthroveSource{
DisplayName: source.DisplayName,
Domain: source.Domain,
Icon: source.Icon,
},
})
}
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
}).Trace("database: got anthrove user")
return anthroveUser, nil
}
func GetAllAnthroveUserIDs(ctx context.Context, db *gorm.DB) ([]models.AnthroveUserID, error) {
var users []pgModels.User
var userIDs []models.AnthroveUserID
err := db.WithContext(ctx).Model(&pgModels.User{}).Find(&users).Error
if err != nil { if err != nil {
log.Error("database: failed to get all anthrove user IDs") log.Error("database: failed to get all anthrove user IDs")
return nil, err return nil, err
} }
for _, user := range users { for _, user := range users {
userIDs = append(userIDs, user.ID) userIDs = append(userIDs, models.AnthroveUserID(user.ID))
} }
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@ -210,11 +275,11 @@ func GetAllAnthroveUserIDs(ctx context.Context, db *gorm.DB) ([]string, error) {
return userIDs, nil return userIDs, nil
} }
func GetUserFavoriteNodeWithPagination(ctx context.Context, db *gorm.DB, anthroveUserID string, skip int, limit int) (*models.FavoriteList, error) { func GetUserFavoriteNodeWithPagination(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, skip int, limit int) (*graphModels.FavoriteList, error) {
var userFavorites []models.UserFavorite var userFavorites []pgModels.UserFavorite
var favoritePosts []models.Post var favoritePosts []graphModels.FavoritePost
err := db.WithContext(ctx).Model(&models.UserFavorite{}).Where("user_id = ?", string(anthroveUserID)).Offset(skip).Limit(limit).Find(&userFavorites).Error err := db.WithContext(ctx).Model(&pgModels.UserFavorite{}).Where("user_id = ?", string(anthroveUserID)).Offset(skip).Limit(limit).Find(&userFavorites).Error
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID, "anthrove_user_id": anthroveUserID,
@ -225,8 +290,8 @@ func GetUserFavoriteNodeWithPagination(ctx context.Context, db *gorm.DB, anthrov
} }
for _, userFavorite := range userFavorites { for _, userFavorite := range userFavorites {
var post models.Post var post pgModels.Post
err = db.WithContext(ctx).Model(&models.Post{}).Where("id = ?", userFavorite.PostID).First(&post).Error err = db.WithContext(ctx).Model(&pgModels.Post{}).Where("id = ?", userFavorite.PostID).First(&post).Error
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"post_id": userFavorite.PostID, "post_id": userFavorite.PostID,
@ -234,11 +299,12 @@ func GetUserFavoriteNodeWithPagination(ctx context.Context, db *gorm.DB, anthrov
return nil, err return nil, err
} }
favoritePosts = append(favoritePosts, favoritePosts = append(favoritePosts, graphModels.FavoritePost{
models.Post{ AnthrovePost: graphModels.AnthrovePost{
BaseModel: models.BaseModel{ID: post.ID}, PostID: models.AnthrovePostID(post.ID),
Rating: post.Rating, Rating: post.Rating,
}) },
})
} }
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@ -246,11 +312,11 @@ func GetUserFavoriteNodeWithPagination(ctx context.Context, db *gorm.DB, anthrov
"anthrove_user_fav_count": len(favoritePosts), "anthrove_user_fav_count": len(favoritePosts),
}).Trace("database: got all anthrove user favorites") }).Trace("database: got all anthrove user favorites")
return &models.FavoriteList{Posts: favoritePosts}, nil return &graphModels.FavoriteList{Posts: favoritePosts}, nil
} }
func GetUserTagNodeWitRelationToFavedPosts(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) ([]models.TagsWithFrequency, error) { func GetUserTagNodeWitRelationToFavedPosts(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) ([]graphModels.TagsWithFrequency, error) {
var userFavorites []models.UserFavorite var userFavorites []pgModels.UserFavorite
err := db.WithContext(ctx).Where("user_id = ?", string(anthroveUserID)).Find(&userFavorites).Error err := db.WithContext(ctx).Where("user_id = ?", string(anthroveUserID)).Find(&userFavorites).Error
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@ -263,9 +329,8 @@ func GetUserTagNodeWitRelationToFavedPosts(ctx context.Context, db *gorm.DB, ant
name string name string
typeName string typeName string
}]int) }]int)
for _, userFavorite := range userFavorites { for _, userFavorite := range userFavorites {
var post models.Post var post pgModels.Post
err = db.WithContext(ctx).Preload("Tags").First(&post, "id = ?", userFavorite.PostID).Error err = db.WithContext(ctx).Preload("Tags").First(&post, "id = ?", userFavorite.PostID).Error
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@ -282,13 +347,13 @@ func GetUserTagNodeWitRelationToFavedPosts(ctx context.Context, db *gorm.DB, ant
} }
} }
var tagsWithFrequency []models.TagsWithFrequency var tagsWithFrequency []graphModels.TagsWithFrequency
for data, frequency := range tagFrequency { for data, frequency := range tagFrequency {
tagsWithFrequency = append(tagsWithFrequency, models.TagsWithFrequency{ tagsWithFrequency = append(tagsWithFrequency, graphModels.TagsWithFrequency{
Frequency: int64(frequency), Frequency: int64(frequency),
Tags: models.Tag{ Tags: graphModels.AnthroveTag{
Name: data.name, Name: data.name,
Type: models.TagType(data.typeName), Type: data.typeName,
}, },
}) })
} }

View File

@ -4,6 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models" "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"
"git.dragse.it/anthrove/otter-space-sdk/test" "git.dragse.it/anthrove/otter-space-sdk/test"
"gorm.io/gorm" "gorm.io/gorm"
"reflect" "reflect"
@ -25,7 +27,7 @@ func TestCreateUser(t *testing.T) {
type args struct { type args struct {
ctx context.Context ctx context.Context
db *gorm.DB db *gorm.DB
anthroveUserID string anthroveUserID models.AnthroveUserID
} }
tests := []struct { tests := []struct {
name string name string
@ -71,7 +73,7 @@ func TestCreateUserNodeWithSourceRelation(t *testing.T) {
// Setup Test // Setup Test
source := &models.Source{ source := &pgModels.Source{
DisplayName: "e621", DisplayName: "e621",
Domain: "e621.net", Domain: "e621.net",
Icon: "icon.e621.net", Icon: "icon.e621.net",
@ -85,7 +87,7 @@ func TestCreateUserNodeWithSourceRelation(t *testing.T) {
type args struct { type args struct {
ctx context.Context ctx context.Context
db *gorm.DB db *gorm.DB
anthroveUserID string anthroveUserID models.AnthroveUserID
sourceDomain string sourceDomain string
userID string userID string
username string username string
@ -101,7 +103,7 @@ func TestCreateUserNodeWithSourceRelation(t *testing.T) {
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthroveUserID: "1", anthroveUserID: "1",
sourceDomain: source.Domain, sourceDomain: "e621.net",
userID: "e1", userID: "e1",
username: "marius", username: "marius",
}, },
@ -113,7 +115,7 @@ func TestCreateUserNodeWithSourceRelation(t *testing.T) {
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthroveUserID: "2", anthroveUserID: "2",
sourceDomain: source.Domain, sourceDomain: "e621.net",
userID: "e1", userID: "e1",
username: "marius", username: "marius",
}, },
@ -125,7 +127,7 @@ func TestCreateUserNodeWithSourceRelation(t *testing.T) {
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthroveUserID: "", anthroveUserID: "",
sourceDomain: source.Domain, sourceDomain: "e621.net",
userID: "e1", userID: "e1",
username: "marius", username: "marius",
}, },
@ -149,7 +151,7 @@ func TestCreateUserNodeWithSourceRelation(t *testing.T) {
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthroveUserID: "1", anthroveUserID: "1",
sourceDomain: source.Domain, sourceDomain: "e621.net",
userID: "", userID: "",
username: "marius", username: "marius",
}, },
@ -161,7 +163,7 @@ func TestCreateUserNodeWithSourceRelation(t *testing.T) {
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,
anthroveUserID: "1", anthroveUserID: "1",
sourceDomain: source.Domain, sourceDomain: "e621.net",
userID: "aa", userID: "aa",
username: "", username: "",
}, },
@ -188,7 +190,7 @@ func TestGetAllAnthroveUserIDs(t *testing.T) {
// Setup Test // Setup Test
users := []string{"1", "2", "3"} users := []models.AnthroveUserID{"1", "2", "3"}
for _, user := range users { for _, user := range users {
err = CreateUser(ctx, gormDB, user) err = CreateUser(ctx, gormDB, user)
@ -205,7 +207,7 @@ func TestGetAllAnthroveUserIDs(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want []string want []models.AnthroveUserID
wantErr bool wantErr bool
}{ }{
{ {
@ -232,7 +234,7 @@ func TestGetAllAnthroveUserIDs(t *testing.T) {
} }
} }
func TestGetSpecifiedUserSourceLink(t *testing.T) { func TestGetAnthroveUser(t *testing.T) {
// Setup trow away container // Setup trow away container
ctx := context.Background() ctx := context.Background()
container, gormDB, err := test.StartPostgresContainer(ctx) container, gormDB, err := test.StartPostgresContainer(ctx)
@ -243,32 +245,106 @@ func TestGetSpecifiedUserSourceLink(t *testing.T) {
// Setup Test // Setup Test
expectedResult := make(map[string]models.UserSource) user := graphModels.AnthroveUser{
expectedResult["e621"] = models.UserSource{ UserID: "1",
UserID: "e1",
AccountUsername: "euser",
Source: models.Source{
DisplayName: "e621",
Domain: "e621.net",
},
} }
err = CreateUser(ctx, gormDB, user.UserID)
source := &models.Source{
DisplayName: expectedResult["e621"].Source.DisplayName,
Domain: expectedResult["e621"].Source.Domain,
}
err = CreateSourceNode(ctx, gormDB, source)
if err != nil {
t.Fatal(err)
}
err = CreateUserNodeWithSourceRelation(ctx, gormDB, "1", source.Domain, expectedResult["e621"].UserID, expectedResult["e621"].AccountUsername)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Test // Test
type args struct {
ctx context.Context
db *gorm.DB
anthroveUserID models.AnthroveUserID
}
tests := []struct {
name string
args args
want *graphModels.AnthroveUser
wantErr bool
}{
{
name: "Test 1: Valid AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "1",
},
want: &user,
wantErr: false,
},
{
name: "Test 2: Invalid AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "2",
},
want: nil,
wantErr: true,
},
{
name: "Test 3: No AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "",
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetAnthroveUser(tt.args.ctx, tt.args.db, tt.args.anthroveUserID)
if (err != nil) != tt.wantErr {
t.Errorf("GetAnthroveUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetAnthroveUser() got = %v, want %v", got, tt.want)
}
})
}
}
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 { type args struct {
ctx context.Context ctx context.Context
db *gorm.DB db *gorm.DB
@ -278,7 +354,7 @@ func TestGetSpecifiedUserSourceLink(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want map[string]models.UserSource want map[string]graphModels.AnthroveUserRelationship
wantErr bool wantErr bool
}{ }{
{ {
@ -356,7 +432,7 @@ func TestGetSpecifiedUserSourceLink(t *testing.T) {
return return
} }
if !reflect.DeepEqual(got, tt.want) { 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)
} }
}) })
} }
@ -373,41 +449,52 @@ func TestGetUserFavoriteNodeWithPagination(t *testing.T) {
// Setup Test // Setup Test
expectedResultPosts := []models.Post{ expectedResultPostsGraph := []graphModels.FavoritePost{
{ {
BaseModel: models.BaseModel{ID: fmt.Sprintf("%-25s", "Post1")}, AnthrovePost: graphModels.AnthrovePost{
Rating: "safe", PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post1")),
}, Rating: "safe",
},
},
{ {
AnthrovePost: graphModels.AnthrovePost{
BaseModel: models.BaseModel{ID: fmt.Sprintf("%-25s", "Post2")}, PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post2")),
Rating: "safe", Rating: "safe",
}, },
},
{ {
BaseModel: models.BaseModel{ID: fmt.Sprintf("%-25s", "Post3")}, AnthrovePost: graphModels.AnthrovePost{
Rating: "explicit", PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post3")),
}, Rating: "explicit",
},
},
{ {
BaseModel: models.BaseModel{ID: fmt.Sprintf("%-25s", "Post4")}, AnthrovePost: graphModels.AnthrovePost{
Rating: "explicit", PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post4")),
}, Rating: "explicit",
},
},
{ {
BaseModel: models.BaseModel{ID: fmt.Sprintf("%-25s", "Post5")}, AnthrovePost: graphModels.AnthrovePost{
Rating: "questionable", PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post5")),
}, Rating: "questionable",
},
},
{ {
BaseModel: models.BaseModel{ID: fmt.Sprintf("%-25s", "Post6")}, AnthrovePost: graphModels.AnthrovePost{
Rating: "safe", PostID: models.AnthrovePostID(fmt.Sprintf("%-25s", "Post6")),
}, Rating: "safe",
},
},
} }
expectedResult := &models.FavoriteList{ expectedResult := &graphModels.FavoriteList{
Posts: expectedResultPosts, Posts: expectedResultPostsGraph,
} }
expectedResult2 := &models.FavoriteList{ expectedResult2 := &graphModels.FavoriteList{
Posts: expectedResultPosts[2:], Posts: expectedResultPostsGraph[2:],
} }
expectedResult3 := &models.FavoriteList{ expectedResult3 := &graphModels.FavoriteList{
Posts: expectedResultPosts[:3], Posts: expectedResultPostsGraph[:3],
} }
err = CreateUser(ctx, gormDB, "1") err = CreateUser(ctx, gormDB, "1")
@ -415,12 +502,39 @@ func TestGetUserFavoriteNodeWithPagination(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
for _, expectedResultPost := range expectedResultPosts { expectedResultPostsPg := []pgModels.Post{
err = CreatePost(ctx, gormDB, &expectedResultPost) {
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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = EstablishUserToPostLink(ctx, gormDB, "1", expectedResultPost.ID) err = EstablishUserToPostLink(ctx, gormDB, "1", models.AnthrovePostID(expectedResultPost.ID))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -430,14 +544,14 @@ func TestGetUserFavoriteNodeWithPagination(t *testing.T) {
type args struct { type args struct {
ctx context.Context ctx context.Context
db *gorm.DB db *gorm.DB
anthroveUserID string anthroveUserID models.AnthroveUserID
skip int skip int
limit int limit int
} }
tests := []struct { tests := []struct {
name string name string
args args args args
want *models.FavoriteList want *graphModels.FavoriteList
wantErr bool wantErr bool
}{ }{
{ {
@ -507,39 +621,39 @@ func TestGetUserFavoritesCount(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expectedResultPosts := []models.Post{ expectedResultPostsPg := []pgModels.Post{
{ {
BaseModel: models.BaseModel{ID: "Post1"}, BaseModel: pgModels.BaseModel{ID: "Post1"},
Rating: "safe", Rating: "safe",
}, },
{ {
BaseModel: models.BaseModel{ID: "Post2"}, BaseModel: pgModels.BaseModel{ID: "Post2"},
Rating: "safe", Rating: "safe",
}, },
{ {
BaseModel: models.BaseModel{ID: "Post3"}, BaseModel: pgModels.BaseModel{ID: "Post3"},
Rating: "explicit", Rating: "explicit",
}, },
{ {
BaseModel: models.BaseModel{ID: "Post4"}, BaseModel: pgModels.BaseModel{ID: "Post4"},
Rating: "explicit", Rating: "explicit",
}, },
{ {
BaseModel: models.BaseModel{ID: "Post5"}, BaseModel: pgModels.BaseModel{ID: "Post5"},
Rating: "questionable", Rating: "questionable",
}, },
{ {
BaseModel: models.BaseModel{ID: "Post6"}, BaseModel: pgModels.BaseModel{ID: "Post6"},
Rating: "safe", Rating: "safe",
}, },
} }
for _, post := range expectedResultPosts { for _, expectedResultPost := range expectedResultPostsPg {
err = CreatePost(ctx, gormDB, &post) err = CreateAnthrovePostNode(ctx, gormDB, models.AnthrovePostID(expectedResultPost.ID), expectedResultPost.Rating)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = EstablishUserToPostLink(ctx, gormDB, "1", post.ID) err = EstablishUserToPostLink(ctx, gormDB, "1", models.AnthrovePostID(expectedResultPost.ID))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -602,7 +716,7 @@ func TestGetUserFavoritesCount(t *testing.T) {
} }
} }
func TestGetUserSourceLinks(t *testing.T) { func TestGetUserSourceLink(t *testing.T) {
// Setup trow away containert // Setup trow away containert
ctx := context.Background() ctx := context.Background()
container, gormDB, err := test.StartPostgresContainer(ctx) container, gormDB, err := test.StartPostgresContainer(ctx)
@ -612,51 +726,51 @@ func TestGetUserSourceLinks(t *testing.T) {
defer container.Terminate(ctx) defer container.Terminate(ctx)
// Setup Test // Setup Test
eSource := &models.Source{
esource := &pgModels.Source{
DisplayName: "e621", DisplayName: "e621",
Domain: "e621.net", Domain: "e621.net",
} }
err = CreateSourceNode(ctx, gormDB, eSource) err = CreateSourceNode(ctx, gormDB, esource)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
faSource := &models.Source{ fasource := &pgModels.Source{
DisplayName: "fa", DisplayName: "fa",
Domain: "fa.net", Domain: "fa.net",
} }
err = CreateSourceNode(ctx, gormDB, faSource) err = CreateSourceNode(ctx, gormDB, fasource)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expectedResult := make(map[string]models.UserSource) expectedResult := make(map[string]graphModels.AnthroveUserRelationship)
expectedResult["e621"] = models.UserSource{ expectedResult["e621"] = graphModels.AnthroveUserRelationship{
UserID: "e1", UserID: "e1",
AccountUsername: "e621-user", Username: "euser",
Source: models.Source{ Source: graphModels.AnthroveSource{
DisplayName: eSource.DisplayName, DisplayName: esource.DisplayName,
Domain: eSource.Domain, Domain: esource.Domain,
}, },
} }
expectedResult["fa"] = models.UserSource{ expectedResult["fa"] = graphModels.AnthroveUserRelationship{
UserID: "fa1", UserID: "fa1",
AccountUsername: "fa-user", Username: "fauser",
Source: models.Source{ Source: graphModels.AnthroveSource{
DisplayName: faSource.DisplayName, DisplayName: fasource.DisplayName,
Domain: faSource.Domain, Domain: fasource.Domain,
}, },
} }
err = CreateUserNodeWithSourceRelation(ctx, gormDB, "1", eSource.Domain, expectedResult["e621"].UserID, expectedResult["e621"].AccountUsername) err = CreateUserNodeWithSourceRelation(ctx, gormDB, "1", esource.Domain, expectedResult["e621"].UserID, expectedResult["e621"].Username)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = CreateUserNodeWithSourceRelation(ctx, gormDB, "1", faSource.Domain, expectedResult["fa"].UserID, expectedResult["fa"].AccountUsername) err = CreateUserNodeWithSourceRelation(ctx, gormDB, "1", fasource.Domain, expectedResult["fa"].UserID, expectedResult["fa"].Username)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Test // Test
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -666,7 +780,7 @@ func TestGetUserSourceLinks(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want map[string]models.UserSource want map[string]graphModels.AnthroveUserRelationship
wantErr bool wantErr bool
}{ }{
{ {
@ -682,13 +796,13 @@ func TestGetUserSourceLinks(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := GetUserSourceLinks(tt.args.ctx, tt.args.db, tt.args.anthroveUserID) got, err := GetUserSourceLink(tt.args.ctx, tt.args.db, tt.args.anthroveUserID)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("GetUserSourceLinks() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetUserSourceLink() error = %v, wantErr %v", err, tt.wantErr)
return return
} }
if !reflect.DeepEqual(got, tt.want) { if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetUserSourceLinks() got = %v, want %v", got, tt.want) t.Errorf("GetUserSourceLink() got = %v, want %v", got, tt.want)
} }
}) })
} }
@ -709,56 +823,56 @@ func TestGetUserTagNodeWitRelationToFavedPosts(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
posts := []models.Post{ posts := []pgModels.Post{
{BaseModel: models.BaseModel{ID: fmt.Sprintf("%-25s", "Post1")}, Rating: "safe"}, {BaseModel: pgModels.BaseModel{ID: fmt.Sprintf("%-25s", "Post1")}, Rating: "safe"},
{BaseModel: models.BaseModel{ID: fmt.Sprintf("%-25s", "Post2")}, Rating: "safe"}, {BaseModel: pgModels.BaseModel{ID: fmt.Sprintf("%-25s", "Post2")}, Rating: "safe"},
{BaseModel: models.BaseModel{ID: fmt.Sprintf("%-25s", "Post3")}, Rating: "explicit"}, {BaseModel: pgModels.BaseModel{ID: fmt.Sprintf("%-25s", "Post3")}, Rating: "explicit"},
} }
for _, post := range posts { for _, post := range posts {
err = CreatePost(ctx, gormDB, &post) err = CreateAnthrovePostNode(ctx, gormDB, models.AnthrovePostID(post.ID), post.Rating)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = EstablishUserToPostLink(ctx, gormDB, "1", post.ID) err = EstablishUserToPostLink(ctx, gormDB, "1", models.AnthrovePostID(post.ID))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
tags := []models.Tag{ tags := []pgModels.Tag{
{Name: "JayTheFerret", Type: "artist"}, {Name: "JayTheFerret", Type: "artist"},
{Name: "Ferret", Type: "species"}, {Name: "Ferret", Type: "species"},
{Name: "Jay", Type: "character"}, {Name: "Jay", Type: "character"},
} }
for i, tag := range tags { for i, tag := range tags {
err = CreateTagNodeWitRelation(ctx, gormDB, posts[i].ID, &tag) err = CreateTagNodeWitRelation(ctx, gormDB, models.AnthrovePostID(posts[i].ID), &tag)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
expectedResult := []models.TagsWithFrequency{ expectedResult := []graphModels.TagsWithFrequency{
{ {
Frequency: 1, Frequency: 1,
Tags: models.Tag{ Tags: graphModels.AnthroveTag{
Name: tags[0].Name, Name: tags[0].Name,
Type: tags[0].Type, Type: string(tags[0].Type),
}, },
}, },
{ {
Frequency: 1, Frequency: 1,
Tags: models.Tag{ Tags: graphModels.AnthroveTag{
Name: tags[1].Name, Name: tags[1].Name,
Type: tags[1].Type, Type: string(tags[1].Type),
}, },
}, },
{ {
Frequency: 1, Frequency: 1,
Tags: models.Tag{ Tags: graphModels.AnthroveTag{
Name: tags[2].Name, Name: tags[2].Name,
Type: tags[2].Type, Type: string(tags[2].Type),
}, },
}, },
} }
@ -772,11 +886,11 @@ func TestGetUserTagNodeWitRelationToFavedPosts(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want []models.TagsWithFrequency want []graphModels.TagsWithFrequency
wantErr bool wantErr bool
}{ }{
{ {
name: "Test 1: Get Data", name: "",
args: args{ args: args{
ctx: ctx, ctx: ctx,
db: gormDB, db: gormDB,

View File

@ -0,0 +1,60 @@
package utils
import (
"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"
)
// GraphConvertSource converts a graphModels.AnthroveSource to a pgModels.Source
func GraphConvertSource(graphSource *graphModels.AnthroveSource) *pgModels.Source {
pgSource := &pgModels.Source{
DisplayName: graphSource.DisplayName,
Domain: graphSource.Domain,
Icon: graphSource.Icon,
}
return pgSource
}
// PostgresConvertToAnthroveSource converts a pgModels.Source to a graphModels.AnthroveSource
func PostgresConvertToAnthroveSource(pgSource *pgModels.Source) *graphModels.AnthroveSource {
graphSource := &graphModels.AnthroveSource{
DisplayName: pgSource.DisplayName,
Domain: pgSource.Domain,
Icon: pgSource.Icon,
}
return graphSource
}
// GraphConvertTag converts a graphModels.AnthroveTag to a pgModels.Tag
func GraphConvertTag(graphTag *graphModels.AnthroveTag) *pgModels.Tag {
pgTag := &pgModels.Tag{
Name: graphTag.Name,
Type: models.TagType(graphTag.Type),
}
return pgTag
}
// PostgresConvertToAnthroveTag converts a pgModels.Tag to a graphModels.AnthroveTag
func PostgresConvertToAnthroveTag(pgTag *pgModels.Tag) *graphModels.AnthroveTag {
graphTag := &graphModels.AnthroveTag{
Name: pgTag.Name,
Type: string(pgTag.Type),
}
return graphTag
}
func ConvertToTagsWithFrequency(tags []pgModels.Tag) []graphModels.TagsWithFrequency {
var tagsWithFrequency []graphModels.TagsWithFrequency
for _, tag := range tags {
graphTag := PostgresConvertToAnthroveTag(&tag)
tagsWithFrequency = append(tagsWithFrequency, graphModels.TagsWithFrequency{
Frequency: 0,
Tags: *graphTag,
})
}
return tagsWithFrequency
}

View File

@ -1,68 +1,116 @@
// Package database provides a client for using the OtterSpace API.
//
// This package provides a client to interact with the OtterSpace API. It includes
// methods for all API endpoints, and convenience methods for common tasks.
//
// This is a simple usage example:
//
// package main
//
// import (
// "context"
// "fmt"
// "git.dragse.it/anthrove/otter-space-sdk/pkg/models"
// "git.dragse.it/anthrove/otter-space-sdk/pkg/database"
// )
//
// func main() {
// client := database.NewGraphConnection()
// err := client.Connect(context.Background(), "your-endpoint", "your-username", "your-password")
// if err != nil {
// fmt.Println(err)
// return
// }
// // further usage of the client...
// }
package database package database
import ( import (
"context" "context"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models" "git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/graphModels"
) )
// OtterSpace provides an interface for interacting with the OtterSpace API.
// It includes methods for connecting to the API, adding and linking users, posts, and sources,
// and retrieving information about users and posts.
type OtterSpace interface { type OtterSpace interface {
// Connect establishes a connection to the database. // Connect sets up a connection to the OtterSpace API endpoint using the provided username and password.
// It returns an error if the connection cannot be established.
Connect(ctx context.Context, endpoint string, username string, password string, database string, port int, ssl string, timezone string) error Connect(ctx context.Context, endpoint string, username string, password string, database string, port int, ssl string, timezone string) error
// AddUserWithRelationToSource adds a user with a relation to a source. // AddUserWithRelationToSource adds a new user to the OtterSpace database and associates them with a source.
// It returns the newly created user and an error if the operation fails.
AddUserWithRelationToSource(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDomain string, userID string, username string) error AddUserWithRelationToSource(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDomain string, userID string, username string) error
// AddSource adds a new source to the database. // AddSource adds a new source to the OtterSpace database.
AddSource(ctx context.Context, anthroveSource *models.Source) error // It returns an error if the operation fails.
AddSource(ctx context.Context, anthroveSource *graphModels.AnthroveSource) error
// AddPost adds a new post to the database. // AddPost adds a new post to the OtterSpace database.
AddPost(ctx context.Context, anthrovePost *models.Post) error // It returns an error if the operation fails.
AddPost(ctx context.Context, anthrovePost *graphModels.AnthrovePost) error
// AddTagWithRelationToPost adds a tag with a relation to a post. // AddTagWithRelationToPost adds a new tag to the OtterSpace database and associates it with a post.
AddTagWithRelationToPost(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveTag *models.Tag) error // It returns an error if the operation fails.
AddTagWithRelationToPost(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveTag *graphModels.AnthroveTag) error
// LinkPostWithSource links a post with a source. // LinkPostWithSource establishes a link between a post and a source in the OtterSpace database.
LinkPostWithSource(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveSourceDomain string, anthrovePostRelationship *models.PostReference) error // It returns an error if the operation fails.
LinkPostWithSource(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveSourceDomain string, anthrovePostRelationship *graphModels.AnthrovePostRelationship) error
// LinkUserWithPost links a user with a post. // LinkUserWithPost establishes a link between a user and a post in the OtterSpace database.
LinkUserWithPost(ctx context.Context, anthroveUser *models.User, anthrovePost *models.Post) error // It returns an error if the operation fails.
LinkUserWithPost(ctx context.Context, anthroveUser *graphModels.AnthroveUser, anthrovePost *graphModels.AnthrovePost) error
// CheckUserPostLink checks if a user-post link exists. // CheckUserPostLink checks if a link between a user and a post exists in the OtterSpace database.
// It returns true if the link exists, false otherwise, and an error if the operation fails.
CheckUserPostLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourcePostID string, sourceUrl string) (bool, error) CheckUserPostLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourcePostID string, sourceUrl string) (bool, error)
// GetPostByAnthroveID retrieves a post by its Anthrove ID. // CheckPostNodeExistsByAnthroveID checks if a post node exists in the OtterSpace database by its Anthrove ID.
GetPostByAnthroveID(ctx context.Context, anthrovePost *models.Post) (*models.Post, error) // It returns the post if it exists, a boolean indicating whether the post was found, and an error if the operation fails.
CheckPostNodeExistsByAnthroveID(ctx context.Context, anthrovePost *graphModels.AnthrovePost) (*graphModels.AnthrovePost, bool, error)
// GetPostBySourceURL retrieves a post by its source URL. // CheckPostNodeExistsBySourceURL checks if a post node exists in the OtterSpace database by its source URL.
GetPostBySourceURL(ctx context.Context, sourceUrl string) (*models.Post, error) // It returns the post if it exists, a boolean indicating whether the post was found, and an error if the operation fails.
CheckPostNodeExistsBySourceURL(ctx context.Context, sourceUrl string) (*graphModels.AnthrovePost, bool, error)
// GetPostBySourceID retrieves a post by its source ID. // CheckPostNodeExistsBySourceID checks if a post node exists in the OtterSpace database by its source ID.
GetPostBySourceID(ctx context.Context, sourcePostID string) (*models.Post, error) // It returns the post if it exists, a boolean indicating whether the post was found, and an error if the operation fails.
CheckPostNodeExistsBySourceID(ctx context.Context, sourcePostID string) (*graphModels.AnthrovePost, bool, error)
// GetUserFavoriteCount retrieves the count of a user's favorites. // GetUserFavoriteCount retrieves the count of a user's favorite posts from the OtterSpace database.
// It returns the count and an error if the operation fails.
GetUserFavoriteCount(ctx context.Context, anthroveUserID models.AnthroveUserID) (int64, error) GetUserFavoriteCount(ctx context.Context, anthroveUserID models.AnthroveUserID) (int64, error)
// GetUserSourceLinks retrieves the source links of a user. // GetUserSourceLinks retrieves the links between a user and sources in the OtterSpace database.
GetUserSourceLinks(ctx context.Context, anthroveUserID models.AnthroveUserID) (map[string]models.UserSource, error) // It returns a map of source domains to user-source relationships, and an error if the operation fails.
GetUserSourceLinks(ctx context.Context, anthroveUserID models.AnthroveUserID) (map[string]graphModels.AnthroveUserRelationship, error)
// GetSpecifiedUserSourceLink retrieves a specified source link of a user. // GetSpecifiedUserSourceLink GetUserSourceLinks retrieves the links between a user and a specific source in the OtterSpace database.
GetSpecifiedUserSourceLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]models.UserSource, error) // It returns a map of source domains to user-source relationships, and an error if the operation fails.
GetSpecifiedUserSourceLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]graphModels.AnthroveUserRelationship, error)
// GetAllAnthroveUserIDs retrieves all Anthrove user IDs. // GetAnthroveUser retrieves a user from the OtterSpace database by their ID.
// It returns the user and an error if the operation fails.
GetAnthroveUser(ctx context.Context, anthroveUserID models.AnthroveUserID) (*graphModels.AnthroveUser, error)
// GetAllAnthroveUserIDs retrieves all user IDs from the OtterSpace database.
// It returns a slice of user IDs and an error if the operation fails.
GetAllAnthroveUserIDs(ctx context.Context) ([]models.AnthroveUserID, error) GetAllAnthroveUserIDs(ctx context.Context) ([]models.AnthroveUserID, error)
// GetUserFavoritePostsWithPagination retrieves a user's favorite posts with pagination. // GetUserFavoritePostsWithPagination gets all user favorites with relation and sources for the given user
GetUserFavoritePostsWithPagination(ctx context.Context, anthroveUserID models.AnthroveUserID, skip int, limit int) (*models.FavoriteList, error) GetUserFavoritePostsWithPagination(ctx context.Context, anthroveUserID models.AnthroveUserID, skip int, limit int) (*graphModels.FavoriteList, error)
// GetUserTagsTroughFavedPosts retrieves a user's tags through their favorited posts. // GetUserTagsTroughFavedPosts returns a list of Tags that the user hs favorites through a post
GetUserTagsTroughFavedPosts(ctx context.Context, anthroveUserID models.AnthroveUserID) ([]models.TagsWithFrequency, error) GetUserTagsTroughFavedPosts(ctx context.Context, anthroveUserID models.AnthroveUserID) ([]graphModels.TagsWithFrequency, error)
// GetAllTags retrieves all tags. // GetAllTags returns a list of Tags that the user hs favorites through a post
GetAllTags(ctx context.Context) ([]models.TagsWithFrequency, error) GetAllTags(ctx context.Context) ([]graphModels.TagsWithFrequency, error)
// GetAllSources retrieves all sources. // GetAllSources returns a list of Sources in the database
GetAllSources(ctx context.Context) ([]models.Source, error) GetAllSources(ctx context.Context) ([]graphModels.AnthroveSource, error)
// GetSourceByURL retrieves a source by its URL. // GetSourceByURL returns the Source Node based on the URL
GetSourceByURL(ctx context.Context, sourceUrl string) (*models.Source, error) GetSourceByURL(ctx context.Context, sourceUrl string) (*graphModels.AnthroveSource, error)
} }

123
pkg/database/graph.go Normal file
View File

@ -0,0 +1,123 @@
package database
import (
"context"
"git.dragse.it/anthrove/otter-space-sdk/internal/graph"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/graphModels"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
"github.com/neo4j/neo4j-go-driver/v5/neo4j/config"
)
type graphConnection struct {
driver neo4j.DriverWithContext
graphDebug bool
}
func NewGraphConnection(graphDebug bool) OtterSpace {
return &graphConnection{
driver: nil,
graphDebug: graphDebug,
}
}
func (g *graphConnection) Connect(ctx context.Context, endpoint string, username string, password string, _ string, _ int, _ string, _ string) error {
driver, err := neo4j.NewDriverWithContext(endpoint, neo4j.BasicAuth(username, password, ""),
logger(g.graphDebug))
if err != nil {
return err
}
err = driver.VerifyAuthentication(ctx, nil)
if err != nil {
return err
}
g.driver = driver
return nil
}
func (g *graphConnection) AddUserWithRelationToSource(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDomain string, userID string, username string) error {
return graph.CreateUserNodeWithSourceRelation(ctx, g.driver, anthroveUserID, sourceDomain, userID, username)
}
func (g *graphConnection) AddSource(ctx context.Context, anthroveSource *graphModels.AnthroveSource) error {
return graph.CreateSourceNode(ctx, g.driver, anthroveSource)
}
func (g *graphConnection) AddPost(ctx context.Context, anthrovePost *graphModels.AnthrovePost) error {
return graph.CreateAnthrovePostNode(ctx, g.driver, anthrovePost)
}
func (g *graphConnection) AddTagWithRelationToPost(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveTag *graphModels.AnthroveTag) error {
return graph.CreateTagNodeWitRelation(ctx, g.driver, anthrovePostID, anthroveTag)
}
func (g *graphConnection) LinkPostWithSource(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveSourceDomain string, anthrovePostRelationship *graphModels.AnthrovePostRelationship) error {
return graph.EstablishAnthrovePostToSourceLink(ctx, g.driver, anthrovePostID, anthroveSourceDomain, anthrovePostRelationship)
}
func (g *graphConnection) LinkUserWithPost(ctx context.Context, anthroveUser *graphModels.AnthroveUser, anthrovePost *graphModels.AnthrovePost) error {
return graph.EstablishUserToPostLink(ctx, g.driver, anthroveUser, anthrovePost)
}
func (g *graphConnection) CheckUserPostLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourcePostID string, sourceUrl string) (bool, error) {
return graph.CheckUserToPostLink(ctx, g.driver, anthroveUserID, sourcePostID, sourceUrl)
}
func (g *graphConnection) CheckPostNodeExistsByAnthroveID(ctx context.Context, anthrovePost *graphModels.AnthrovePost) (*graphModels.AnthrovePost, bool, error) {
return graph.CheckIfAnthrovePostNodeExistsByAnthroveID(ctx, g.driver, anthrovePost)
}
func (g *graphConnection) CheckPostNodeExistsBySourceURL(ctx context.Context, sourceUrl string) (*graphModels.AnthrovePost, bool, error) {
return graph.CheckIfAnthrovePostNodeExistsBySourceURl(ctx, g.driver, sourceUrl)
}
func (g *graphConnection) CheckPostNodeExistsBySourceID(ctx context.Context, sourcePostID string) (*graphModels.AnthrovePost, bool, error) {
return graph.CheckIfAnthrovePostNodeExistsBySourceID(ctx, g.driver, sourcePostID)
}
func (g *graphConnection) GetUserFavoriteCount(ctx context.Context, anthroveUserID models.AnthroveUserID) (int64, error) {
return graph.GetUserFavoritesCount(ctx, g.driver, anthroveUserID)
}
func (g *graphConnection) GetUserSourceLinks(ctx context.Context, anthroveUserID models.AnthroveUserID) (map[string]graphModels.AnthroveUserRelationship, error) {
return graph.GetUserSourceLink(ctx, g.driver, anthroveUserID)
}
func (g *graphConnection) GetSpecifiedUserSourceLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]graphModels.AnthroveUserRelationship, error) {
return graph.GetSpecifiedUserSourceLink(ctx, g.driver, anthroveUserID, sourceDisplayName)
}
func (g *graphConnection) GetAnthroveUser(ctx context.Context, anthroveUserID models.AnthroveUserID) (*graphModels.AnthroveUser, error) {
return graph.GetAnthroveUser(ctx, g.driver, anthroveUserID)
}
func (g *graphConnection) GetAllAnthroveUserIDs(ctx context.Context) ([]models.AnthroveUserID, error) {
return graph.GetAllAnthroveUserIDs(ctx, g.driver)
}
func (g *graphConnection) GetUserFavoritePostsWithPagination(ctx context.Context, anthroveUserID models.AnthroveUserID, skip int, limit int) (*graphModels.FavoriteList, error) {
return graph.GetUserFavoriteNodeWithPagination(ctx, g.driver, anthroveUserID, skip, limit)
}
func (g *graphConnection) GetUserTagsTroughFavedPosts(ctx context.Context, anthroveUserID models.AnthroveUserID) ([]graphModels.TagsWithFrequency, error) {
return graph.GetUserTagNodeWitRelationToFavedPosts(ctx, g.driver, anthroveUserID)
}
func (g *graphConnection) GetAllTags(ctx context.Context) ([]graphModels.TagsWithFrequency, error) {
return graph.GetTags(ctx, g.driver)
}
func (g *graphConnection) GetAllSources(ctx context.Context) ([]graphModels.AnthroveSource, error) {
return graph.GetAllSourceNodes(ctx, g.driver)
}
func (g *graphConnection) GetSourceByURL(ctx context.Context, sourceUrl string) (*graphModels.AnthroveSource, error) {
return graph.GetSourceNodesByURL(ctx, g.driver, sourceUrl)
}
func logger(graphDebug bool) func(config *config.Config) {
return func(config *config.Config) {
config.Log = graph.NewGraphLogger(graphDebug)
}
}

View File

@ -89,11 +89,10 @@ CREATE TABLE "UserFavorites"
CREATE TABLE "UserSource" CREATE TABLE "UserSource"
( (
user_id TEXT REFERENCES "User" (id), user_id TEXT REFERENCES "User" (id),
source_id TEXT REFERENCES "Source" (id), source_id TEXT REFERENCES "Source" (id),
scrape_time_interval TEXT, account_username TEXT,
account_username TEXT, account_id TEXT,
account_id TEXT,
PRIMARY KEY (user_id, source_id), PRIMARY KEY (user_id, source_id),
UNIQUE (account_username, account_id) UNIQUE (account_username, account_id)
); );

View File

@ -6,7 +6,9 @@ import (
"embed" "embed"
"fmt" "fmt"
"git.dragse.it/anthrove/otter-space-sdk/internal/postgres" "git.dragse.it/anthrove/otter-space-sdk/internal/postgres"
"git.dragse.it/anthrove/otter-space-sdk/internal/utils"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models" "git.dragse.it/anthrove/otter-space-sdk/pkg/models"
"git.dragse.it/anthrove/otter-space-sdk/pkg/models/graphModels"
_ "github.com/lib/pq" _ "github.com/lib/pq"
migrate "github.com/rubenv/sql-migrate" migrate "github.com/rubenv/sql-migrate"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -68,76 +70,97 @@ func (p *postgresqlConnection) AddUserWithRelationToSource(ctx context.Context,
return postgres.CreateUserNodeWithSourceRelation(ctx, p.db, anthroveUserID, sourceDomain, userID, userID) return postgres.CreateUserNodeWithSourceRelation(ctx, p.db, anthroveUserID, sourceDomain, userID, userID)
} }
func (p *postgresqlConnection) AddSource(ctx context.Context, anthroveSource *models.Source) error { func (p *postgresqlConnection) AddSource(ctx context.Context, anthroveSource *graphModels.AnthroveSource) error {
return postgres.CreateSourceNode(ctx, p.db, anthroveSource) source := utils.GraphConvertSource(anthroveSource)
return postgres.CreateSourceNode(ctx, p.db, source)
} }
func (p *postgresqlConnection) AddPost(ctx context.Context, anthrovePost *models.Post) error { func (p *postgresqlConnection) AddPost(ctx context.Context, anthrovePost *graphModels.AnthrovePost) error {
return postgres.CreatePost(ctx, p.db, anthrovePost) return postgres.CreateAnthrovePostNode(ctx, p.db, anthrovePost.PostID, anthrovePost.Rating)
} }
func (p *postgresqlConnection) AddTagWithRelationToPost(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveTag *models.Tag) error { func (p *postgresqlConnection) AddTagWithRelationToPost(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveTag *graphModels.AnthroveTag) error {
return postgres.CreateTagNodeWitRelation(ctx, p.db, anthrovePostID, anthroveTag) return postgres.CreateTagNodeWitRelation(ctx, p.db, anthrovePostID, utils.GraphConvertTag(anthroveTag))
} }
func (p *postgresqlConnection) LinkPostWithSource(ctx context.Context, anthrovePostID models.AnthrovePostID, sourceDomain string, anthrovePostRelationship *models.PostReference) error { func (p *postgresqlConnection) LinkPostWithSource(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveSourceDomain string, anthrovePostRelationship *graphModels.AnthrovePostRelationship) error {
return postgres.EstablishAnthrovePostToSourceLink(ctx, p.db, anthrovePostID, sourceDomain, anthrovePostRelationship) return postgres.EstablishAnthrovePostToSourceLink(ctx, p.db, anthrovePostID, anthroveSourceDomain, anthrovePostRelationship)
} }
func (p *postgresqlConnection) LinkUserWithPost(ctx context.Context, anthroveUser *models.User, anthrovePost *models.Post) error { func (p *postgresqlConnection) LinkUserWithPost(ctx context.Context, anthroveUser *graphModels.AnthroveUser, anthrovePost *graphModels.AnthrovePost) error {
return postgres.EstablishUserToPostLink(ctx, p.db, anthroveUser.ID, anthrovePost.ID) return postgres.EstablishUserToPostLink(ctx, p.db, anthroveUser.UserID, anthrovePost.PostID)
} }
func (p *postgresqlConnection) CheckUserPostLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourcePostID string, sourceUrl string) (bool, error) { func (p *postgresqlConnection) CheckUserPostLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourcePostID string, sourceUrl string) (bool, error) {
return postgres.CheckUserToPostLink(ctx, p.db, anthroveUserID, models.AnthrovePostID(sourcePostID)) return postgres.CheckUserToPostLink(ctx, p.db, anthroveUserID, models.AnthrovePostID(sourcePostID))
} }
func (p *postgresqlConnection) GetPostByAnthroveID(ctx context.Context, anthrovePost *models.Post) (*models.Post, error) { func (p *postgresqlConnection) CheckPostNodeExistsByAnthroveID(ctx context.Context, anthrovePost *graphModels.AnthrovePost) (*graphModels.AnthrovePost, bool, error) {
return postgres.GetPostByAnthroveID(ctx, p.db, anthrovePost.ID) exists, err := postgres.CheckIfAnthrovePostNodeExistsByAnthroveID(ctx, p.db, anthrovePost.PostID)
return anthrovePost, exists, err
} }
func (p *postgresqlConnection) GetPostBySourceURL(ctx context.Context, sourceUrl string) (*models.Post, error) { // CheckPostNodeExistsBySourceURL NOT WORKING! TODO!
return postgres.GetPostBySourceURL(ctx, p.db, sourceUrl) func (p *postgresqlConnection) CheckPostNodeExistsBySourceURL(ctx context.Context, sourceUrl string) (*graphModels.AnthrovePost, bool, error) {
post, exists, err := postgres.CheckIfAnthrovePostNodeExistsBySourceURL(ctx, p.db, sourceUrl)
return post, exists, err
} }
func (p *postgresqlConnection) GetPostBySourceID(ctx context.Context, sourcePostID string) (*models.Post, error) { // CheckPostNodeExistsBySourceID NOT WORKING! TODO!
return postgres.GetPostBySourceID(ctx, p.db, sourcePostID) func (p *postgresqlConnection) CheckPostNodeExistsBySourceID(ctx context.Context, sourcePostID string) (*graphModels.AnthrovePost, bool, error) {
var post graphModels.AnthrovePost
exists, err := postgres.CheckIfAnthrovePostNodeExistsBySourceID(ctx, p.db, sourcePostID)
return &post, exists, err
} }
func (p *postgresqlConnection) GetUserFavoriteCount(ctx context.Context, anthroveUserID models.AnthroveUserID) (int64, error) { func (p *postgresqlConnection) GetUserFavoriteCount(ctx context.Context, anthroveUserID models.AnthroveUserID) (int64, error) {
return postgres.GetUserFavoritesCount(ctx, p.db, anthroveUserID) return postgres.GetUserFavoritesCount(ctx, p.db, anthroveUserID)
} }
func (p *postgresqlConnection) GetUserSourceLinks(ctx context.Context, anthroveUserID models.AnthroveUserID) (map[string]models.UserSource, error) { func (p *postgresqlConnection) GetUserSourceLinks(ctx context.Context, anthroveUserID models.AnthroveUserID) (map[string]graphModels.AnthroveUserRelationship, error) {
return postgres.GetUserSourceLinks(ctx, p.db, anthroveUserID) return postgres.GetUserSourceLink(ctx, p.db, anthroveUserID)
} }
func (p *postgresqlConnection) GetSpecifiedUserSourceLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]models.UserSource, error) { func (p *postgresqlConnection) GetSpecifiedUserSourceLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]graphModels.AnthroveUserRelationship, error) {
return postgres.GetSpecifiedUserSourceLink(ctx, p.db, anthroveUserID, sourceDisplayName) return postgres.GetSpecifiedUserSourceLink(ctx, p.db, anthroveUserID, sourceDisplayName)
} }
func (p *postgresqlConnection) GetAnthroveUser(ctx context.Context, anthroveUserID models.AnthroveUserID) (*graphModels.AnthroveUser, error) {
return postgres.GetAnthroveUser(ctx, p.db, anthroveUserID)
}
func (p *postgresqlConnection) GetAllAnthroveUserIDs(ctx context.Context) ([]models.AnthroveUserID, error) { func (p *postgresqlConnection) GetAllAnthroveUserIDs(ctx context.Context) ([]models.AnthroveUserID, error) {
return postgres.GetAllAnthroveUserIDs(ctx, p.db) return postgres.GetAllAnthroveUserIDs(ctx, p.db)
} }
func (p *postgresqlConnection) GetUserFavoritePostsWithPagination(ctx context.Context, anthroveUserID models.AnthroveUserID, skip int, limit int) (*models.FavoriteList, error) { func (p *postgresqlConnection) GetUserFavoritePostsWithPagination(ctx context.Context, anthroveUserID models.AnthroveUserID, skip int, limit int) (*graphModels.FavoriteList, error) {
return postgres.GetUserFavoriteNodeWithPagination(ctx, p.db, anthroveUserID, skip, limit) return postgres.GetUserFavoriteNodeWithPagination(ctx, p.db, anthroveUserID, skip, limit)
} }
func (p *postgresqlConnection) GetUserTagsTroughFavedPosts(ctx context.Context, anthroveUserID models.AnthroveUserID) ([]models.TagsWithFrequency, error) { func (p *postgresqlConnection) GetUserTagsTroughFavedPosts(ctx context.Context, anthroveUserID models.AnthroveUserID) ([]graphModels.TagsWithFrequency, error) {
return postgres.GetUserTagNodeWitRelationToFavedPosts(ctx, p.db, anthroveUserID) return postgres.GetUserTagNodeWitRelationToFavedPosts(ctx, p.db, anthroveUserID)
} }
func (p *postgresqlConnection) GetAllTags(ctx context.Context) ([]models.TagsWithFrequency, error) { func (p *postgresqlConnection) GetAllTags(ctx context.Context) ([]graphModels.TagsWithFrequency, error) {
return postgres.GetTags(ctx, p.db) tags, err := postgres.GetTags(ctx, p.db)
return utils.ConvertToTagsWithFrequency(tags), err
} }
func (p *postgresqlConnection) GetAllSources(ctx context.Context) ([]models.Source, error) { func (p *postgresqlConnection) GetAllSources(ctx context.Context) ([]graphModels.AnthroveSource, error) {
return postgres.GetAllSourceNodes(ctx, p.db) var anthroveSources []graphModels.AnthroveSource
source, err := postgres.GetAllSourceNodes(ctx, p.db)
for _, v := range source {
anthroveSource := utils.PostgresConvertToAnthroveSource(&v)
anthroveSources = append(anthroveSources, *anthroveSource)
}
return nil, err
} }
func (p *postgresqlConnection) GetSourceByURL(ctx context.Context, sourceUrl string) (*models.Source, error) { func (p *postgresqlConnection) GetSourceByURL(ctx context.Context, sourceUrl string) (*graphModels.AnthroveSource, error) {
return postgres.GetSourceNodesByURL(ctx, p.db, sourceUrl) source, err := postgres.GetSourceNodesByURL(ctx, p.db, sourceUrl)
return utils.PostgresConvertToAnthroveSource(source), err
} }
func (p *postgresqlConnection) migrateDatabase(connectionString string) error { func (p *postgresqlConnection) migrateDatabase(connectionString string) error {

84
pkg/models/README.md Normal file
View File

@ -0,0 +1,84 @@
# Postgres
https://www.dbdiagram.io/d
````
Table User {
id string [primary key]
created_at timestamp
}
Table Post {
id varchar(25) [primary key]
rating Rating
created_at timestamp
}
Enum Rating {
safe
questionable
explicit
}
Table Source {
id varchar(25) [primary key]
display_name text
domain text [not null, unique]
}
Table Tag {
name text [primary key]
type TagType
}
Enum TagType {
general
species
character
artist
lore
meta
invalid
}
Table TagAlias {
name text [primary key]
tag_id text
}
Table TagGroup {
name text [primary key]
tag_id text
}
Table UserFavorites {
user_id text [primary key]
post_id text [primary key]
created_at timestamp
}
Table UserSource {
user_id text [primary key]
source_id text [primary key]
account_username text
account_id text
}
Table PostReference {
post_id text [primary key]
source_id text [primary key]
url text [not null, unique]
source_post_id text
}
Ref: Tag.name > TagAlias.tag_id
Ref: Tag.name > TagGroup.tag_id
Ref: Tag.name <> Post.id
Ref: UserFavorites.user_id > User.id
Ref: UserFavorites.post_id > Post.id
Ref: UserSource.user_id > User.id
Ref: UserSource.source_id > Source.id
Ref: PostReference.post_id > Post.id
Ref: PostReference.source_id > Source.id
````

View File

@ -0,0 +1,20 @@
package graphModels
type FavoriteRelations struct {
SourcesID string `json:"sources_id"`
Relations AnthrovePostRelationship `json:"relations"`
}
type FavoritePost struct {
AnthrovePost AnthrovePost `json:"anthrove_post"`
Relations []FavoriteRelations `json:"relations"`
}
type FavoriteList struct {
Posts []FavoritePost `json:"posts,omitempty"`
}
type TagsWithFrequency struct {
Frequency int64 `json:"frequency"`
Tags AnthroveTag `json:"tags"`
}

View File

@ -0,0 +1,8 @@
package graphModels
import "git.dragse.it/anthrove/otter-space-sdk/pkg/models"
type AnthrovePost struct {
PostID models.AnthrovePostID `json:"post_id"`
Rating models.Rating `json:"rating"`
}

View File

@ -0,0 +1,12 @@
package graphModels
type AnthroveUserRelationship struct {
UserID string `json:"user_id"`
Username string `json:"username"`
ScrapeTimeInterval string `json:"scrape_time_interval"`
Source AnthroveSource `json:"source"`
}
type AnthrovePostRelationship struct {
PostID string `json:"post_id"`
Url string `json:"url"`
}

View File

@ -0,0 +1,7 @@
package graphModels
type AnthroveSource struct {
DisplayName string `json:"display_name"`
Domain string `json:"domain"`
Icon string `json:"icon"`
}

View File

@ -0,0 +1,6 @@
package graphModels
type AnthroveTag struct {
Name string `json:"name"`
Type string `json:"type"`
}

View File

@ -0,0 +1,8 @@
package graphModels
import "git.dragse.it/anthrove/otter-space-sdk/pkg/models"
type AnthroveUser struct {
UserID models.AnthroveUserID `json:"user_id"`
Relationship []AnthroveUserRelationship `json:"relationship"`
}

View File

@ -1,4 +1,4 @@
package models package pgModels
import ( import (
gonanoid "github.com/matoous/go-nanoid/v2" gonanoid "github.com/matoous/go-nanoid/v2"

View File

@ -1,9 +1,13 @@
package models package pgModels
import (
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
)
// Post model // Post model
type Post struct { type Post struct {
BaseModel BaseModel
Rating Rating `gorm:"type:enum('safe','questionable','explicit')"` Rating models.Rating `gorm:"type:enum('safe','questionable','explicit')"`
Tags []Tag `gorm:"many2many:post_tags;"` Tags []Tag `gorm:"many2many:post_tags;"`
Favorites []UserFavorite `gorm:"foreignKey:PostID"` Favorites []UserFavorite `gorm:"foreignKey:PostID"`
References []PostReference `gorm:"foreignKey:PostID"` References []PostReference `gorm:"foreignKey:PostID"`

View File

@ -1,4 +1,4 @@
package models package pgModels
type PostReference struct { type PostReference struct {
PostID string `gorm:"primaryKey"` PostID string `gorm:"primaryKey"`

View File

@ -1,4 +1,4 @@
package models package pgModels
// Source model // Source model
type Source struct { type Source struct {

View File

@ -1,12 +1,14 @@
package models package pgModels
import "git.dragse.it/anthrove/otter-space-sdk/pkg/models"
// Tag models // Tag models
type Tag struct { type Tag struct {
Name string `gorm:"primaryKey"` Name string `gorm:"primaryKey"`
Type TagType `gorm:"column:tag_type"` Type models.TagType `gorm:"column:tag_type"`
Aliases []TagAlias `gorm:"foreignKey:TagID"` Aliases []TagAlias `gorm:"foreignKey:TagID"`
Groups []TagGroup `gorm:"foreignKey:TagID"` Groups []TagGroup `gorm:"foreignKey:TagID"`
Posts []Post `gorm:"many2many:post_tags;"` Posts []Post `gorm:"many2many:post_tags;"`
} }
func (Tag) TableName() string { func (Tag) TableName() string {
@ -32,8 +34,3 @@ type TagGroup struct {
func (TagGroup) TableName() string { func (TagGroup) TableName() string {
return "TagGroup" return "TagGroup"
} }
type TagsWithFrequency struct {
Frequency int64 `json:"frequency"`
Tags Tag `json:"tags"`
}

View File

@ -1,4 +1,4 @@
package models package pgModels
// User model // User model
type User struct { type User struct {

View File

@ -1,4 +1,4 @@
package models package pgModels
import "time" import "time"
@ -11,7 +11,3 @@ type UserFavorite struct {
func (UserFavorite) TableName() string { func (UserFavorite) TableName() string {
return "UserFavorites" return "UserFavorites"
} }
type FavoriteList struct {
Posts []Post `json:"posts,omitempty"`
}

View File

@ -0,0 +1,13 @@
package pgModels
type UserSource struct {
UserID string `gorm:"primaryKey"`
Source Source `gorm:"foreignKey:ID;references:SourceID"`
SourceID string `gorm:"primaryKey"`
AccountUsername string
AccountID string
}
func (UserSource) TableName() string {
return "UserSource"
}

View File

@ -1,15 +0,0 @@
package models
type UserSource struct {
User User `gorm:"foreignKey:ID;references:UserID"`
UserID string `gorm:"primaryKey"`
Source Source `gorm:"foreignKey:ID;references:SourceID"`
SourceID string `gorm:"primaryKey"`
ScrapeTimeInterval string
AccountUsername string
AccountID string
}
func (UserSource) TableName() string {
return "UserSource"
}