Compare commits

..

No commits in common. "f798869ef5fcb9722d5b210b4c09a437e7a47c06" and "c2ab455d3e653b8d7cab9e2df812a45d6e033116" have entirely different histories.

25 changed files with 4303 additions and 245 deletions

View File

@ -1,68 +0,0 @@
package database
import (
"context"
"embed"
"fmt"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
migrate "github.com/rubenv/sql-migrate"
log "github.com/sirupsen/logrus"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
//go:embed migrations/*.sql
var embedMigrations embed.FS
var client *gorm.DB
func Connect(_ context.Context, config models.DatabaseConfig) error {
var localSSL string
if config.SSL {
localSSL = "require"
} else {
localSSL = "disable"
}
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s", config.Endpoint, config.Username, config.Password, config.Database, config.Port, localSSL, config.Timezone)
sqlDB, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
return err
}
client = sqlDB
err = migrateDatabase(sqlDB, config)
if err != nil {
return err
}
return nil
}
func migrateDatabase(dbPool *gorm.DB, config models.DatabaseConfig) error {
dialect := "postgres"
migrations := &migrate.EmbedFileSystemMigrationSource{FileSystem: embedMigrations, Root: "migrations"}
db, err := dbPool.DB()
if err != nil {
return fmt.Errorf("postgres migration: %v", err)
}
n, err := migrate.Exec(db, dialect, migrations, migrate.Up)
if err != nil {
return fmt.Errorf("postgres migration: %v", err)
}
if config.Debug {
if n != 0 {
log.Infof("postgres migration: applied %d migrations!", n)
} else {
log.Info("postgres migration: nothing to migrate")
}
}
return nil
}

36
pkg/database/database.go Normal file
View File

@ -0,0 +1,36 @@
package database
import (
"context"
"database/sql"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
)
type OtterSpace interface {
// Connect establishes a connection to the database.
Connect(ctx context.Context, config models.DatabaseConfig) error
// Post contains all function that are needed to manage Posts
Post
// User contains all function that are needed to manage the AnthroveUser
User
// Source contains all function that are needed to manage the Source
Source
// Tag contains all functions that are used to manage Tag
Tag
// TagAlias contains all function that are needed to manage the TagAlias
TagAlias
// TagGroup contains all function that are needed to manage the TagGroup
TagGroup
// ExecuteRawStatement run a custom query.
ExecuteRawStatement(ctx context.Context, query string, args ...any) error
// QueryRawStatement runs a custom query and returns the table
QueryRawStatement(ctx context.Context, query string, args ...any) (*sql.Rows, error)
}

View File

@ -18,7 +18,7 @@ CREATE TYPE TagType AS ENUM (
CREATE TABLE "Post"
(
id CHAR(25) PRIMARY KEY,
id CHAR(25) PRIMARY KEY,
rating Rating,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
@ -28,9 +28,9 @@ CREATE TABLE "Post"
CREATE TABLE "Source"
(
id CHAR(25) PRIMARY KEY,
display_name TEXT NULL,
icon TEXT NULL,
domain TEXT NOT NULL UNIQUE,
display_name TEXT NULL,
icon TEXT NULL,
domain TEXT NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL
@ -47,7 +47,7 @@ CREATE TABLE "Tag"
CREATE TABLE "User"
(
id TEXT PRIMARY KEY,
id TEXT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL
@ -57,7 +57,7 @@ CREATE TABLE "PostReference"
(
post_id TEXT REFERENCES "Post" (id),
source_id TEXT REFERENCES "Source" (id),
url TEXT NOT NULL,
url TEXT NOT NULL,
full_file_url TEXT,
preview_file_url TEXT,
sample_file_url TEXT,
@ -79,12 +79,16 @@ CREATE TABLE "TagGroup"
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE "UserFavorites"
(
user_id TEXT REFERENCES "User" (id),
post_id TEXT REFERENCES "Post" (id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, post_id)
);
CREATE TABLE "UserSource"
(
id CHAR(25) PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL NULL,
user_id TEXT REFERENCES "User" (id),
source_id TEXT REFERENCES "Source" (id),
scrape_time_interval INT,
@ -93,20 +97,10 @@ CREATE TABLE "UserSource"
last_scrape_time TIMESTAMP,
account_validate BOOL DEFAULT FALSE,
account_validation_key CHAR(25),
PRIMARY KEY (user_id, source_id),
UNIQUE (source_id, account_username, account_id)
);
CREATE TABLE "UserFavorites"
(
id CHAR(25) PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL NULL,
user_id TEXT REFERENCES "User" (id),
post_id TEXT REFERENCES "Post" (id),
user_source_id CHAR(25) REFERENCES "UserSource" (id)
);
CREATE TABLE "post_tags"
(
post_id TEXT REFERENCES "Post" (id),

31
pkg/database/post.go Normal file
View File

@ -0,0 +1,31 @@
package database
import (
"context"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
)
type Post interface {
// CreatePost adds a new post to the database.
CreatePost(ctx context.Context, anthrovePost *models.Post) error
// TODO: Everything
CreatePostInBatch(ctx context.Context, anthrovePost []models.Post, batchSize int) error
// GetPostByAnthroveID retrieves a post by its Anthrove ID.
GetPostByAnthroveID(ctx context.Context, anthrovePostID models.AnthrovePostID) (*models.Post, error)
// GetPostByURL retrieves a post by its source URL.
GetPostByURL(ctx context.Context, postURL string) (*models.Post, error)
// GetPostBySourceID retrieves a post by its source ID.
GetPostBySourceID(ctx context.Context, sourceID models.AnthroveSourceID) (*models.Post, error)
// CreatePostWithReferenceToTagAnd adds a tag with a relation to a post.
CreatePostWithReferenceToTagAnd(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveTag *models.Tag) error
// CreatePostReference links a post with a source.
CreatePostReference(ctx context.Context, anthrovePostID models.AnthrovePostID, sourceDomain models.AnthroveSourceDomain, postURL models.AnthrovePostURL, config models.PostReferenceConfig) error
}

268
pkg/database/postgres.go Normal file
View File

@ -0,0 +1,268 @@
package database
import (
"context"
"database/sql"
"embed"
"fmt"
log2 "log"
"os"
"time"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/internal/postgres"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
_ "github.com/lib/pq"
migrate "github.com/rubenv/sql-migrate"
log "github.com/sirupsen/logrus"
gormPostgres "gorm.io/driver/postgres"
"gorm.io/gorm"
gormLogger "gorm.io/gorm/logger"
)
//go:embed migrations/*.sql
var embedMigrations embed.FS
type postgresqlConnection struct {
db *gorm.DB
debug bool
}
func NewPostgresqlConnection() OtterSpace {
return &postgresqlConnection{
db: nil,
}
}
func (p *postgresqlConnection) Connect(_ context.Context, config models.DatabaseConfig) error {
var localSSL string
var logLevel gormLogger.LogLevel
if config.SSL {
localSSL = "require"
} else {
localSSL = "disable"
}
p.debug = config.Debug
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s", config.Endpoint, config.Username, config.Password, config.Database, config.Port, localSSL, config.Timezone)
var err error
if p.debug {
logLevel = gormLogger.Info
} else {
logLevel = gormLogger.Silent
}
dbLogger := gormLogger.New(log2.New(os.Stdout, "\r\n", log2.LstdFlags), gormLogger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logLevel,
IgnoreRecordNotFoundError: true,
Colorful: true,
})
db, err := gorm.Open(gormPostgres.Open(dsn), &gorm.Config{
Logger: dbLogger,
})
p.db = db
if err != nil {
return err
}
log.Infof("OtterSpace: database connection established")
err = p.migrateDatabase(db)
if err != nil {
return err
}
log.Infof("OtterSpace: migration compleate")
return nil
}
func (p *postgresqlConnection) CreateUserWithRelationToSource(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, accountId string, accountUsername string) error {
return postgres.CreateUserWithRelationToSource(ctx, p.db, anthroveUserID, sourceID, accountId, accountUsername)
}
func (p *postgresqlConnection) CreateSource(ctx context.Context, anthroveSource *models.Source) error {
return postgres.CreateSource(ctx, p.db, anthroveSource)
}
func (p *postgresqlConnection) CreatePost(ctx context.Context, anthrovePost *models.Post) error {
return postgres.CreatePost(ctx, p.db, anthrovePost)
}
func (p *postgresqlConnection) CreatePostInBatch(ctx context.Context, anthrovePost []models.Post, batchSize int) error {
return postgres.CreatePostInBatch(ctx, p.db, anthrovePost, batchSize)
}
func (p *postgresqlConnection) CreatePostWithReferenceToTagAnd(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveTag *models.Tag) error {
return postgres.CreateTagAndReferenceToPost(ctx, p.db, anthrovePostID, anthroveTag)
}
func (p *postgresqlConnection) CreatePostReference(ctx context.Context, anthrovePostID models.AnthrovePostID, sourceDomain models.AnthroveSourceDomain, postURL models.AnthrovePostURL, config models.PostReferenceConfig) error {
return postgres.CreateReferenceBetweenPostAndSource(ctx, p.db, anthrovePostID, sourceDomain, postURL, config)
}
func (p *postgresqlConnection) CreateReferenceBetweenUserAndPost(ctx context.Context, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) error {
return postgres.CreateReferenceBetweenUserAndPost(ctx, p.db, anthroveUserID, anthrovePostID)
}
func (p *postgresqlConnection) CheckIfUserHasPostAsFavorite(ctx context.Context, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) (bool, error) {
return postgres.CheckReferenceBetweenUserAndPost(ctx, p.db, anthroveUserID, anthrovePostID)
}
func (p *postgresqlConnection) GetPostByAnthroveID(ctx context.Context, anthrovePostID models.AnthrovePostID) (*models.Post, error) {
return postgres.GetPostByAnthroveID(ctx, p.db, anthrovePostID)
}
func (p *postgresqlConnection) GetPostByURL(ctx context.Context, sourceUrl string) (*models.Post, error) {
return postgres.GetPostBySourceURL(ctx, p.db, sourceUrl)
}
func (p *postgresqlConnection) GetPostBySourceID(ctx context.Context, sourceID models.AnthroveSourceID) (*models.Post, error) {
return postgres.GetPostBySourceID(ctx, p.db, sourceID)
}
func (p *postgresqlConnection) GetUserFavoritesCount(ctx context.Context, anthroveUserID models.AnthroveUserID) (int64, error) {
return postgres.GetUserFavoritesCount(ctx, p.db, anthroveUserID)
}
func (p *postgresqlConnection) GetAllUserSources(ctx context.Context, anthroveUserID models.AnthroveUserID) (map[string]models.UserSource, error) {
return postgres.GetUserSourceLinks(ctx, p.db, anthroveUserID)
}
func (p *postgresqlConnection) GetUserSourceBySourceID(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID) (*models.UserSource, error) {
return postgres.GetUserSourceBySourceID(ctx, p.db, anthroveUserID, sourceID)
}
func (p *postgresqlConnection) GetAllUsers(ctx context.Context) ([]models.User, error) {
return postgres.GetAllUsers(ctx, p.db)
}
func (p *postgresqlConnection) GetAllUserFavoritesWithPagination(ctx context.Context, anthroveUserID models.AnthroveUserID, skip int, limit int) (*models.FavoriteList, error) {
return postgres.GetUserFavoriteWithPagination(ctx, p.db, anthroveUserID, skip, limit)
}
func (p *postgresqlConnection) GetAllTagsFromUser(ctx context.Context, anthroveUserID models.AnthroveUserID) ([]models.TagsWithFrequency, error) {
return postgres.GetUserTagWitRelationToFavedPosts(ctx, p.db, anthroveUserID)
}
func (p *postgresqlConnection) GetAllTags(ctx context.Context) ([]models.Tag, error) {
return postgres.GetTags(ctx, p.db)
}
func (p *postgresqlConnection) GetAllSources(ctx context.Context) ([]models.Source, error) {
return postgres.GetAllSource(ctx, p.db)
}
func (p *postgresqlConnection) GetSourceByDomain(ctx context.Context, sourceDomain models.AnthroveSourceDomain) (*models.Source, error) {
return postgres.GetSourceByDomain(ctx, p.db, sourceDomain)
}
func (p *postgresqlConnection) UpdateUserSourceScrapeTimeInterval(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, scrapeTime models.AnthroveScrapeTimeInterval) error {
return postgres.UpdateUserSourceScrapeTimeInterval(ctx, p.db, anthroveUserID, sourceID, scrapeTime)
}
func (p *postgresqlConnection) UpdateUserSourceLastScrapeTime(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, lastScrapeTime models.AnthroveUserLastScrapeTime) error {
return postgres.UpdateUserSourceLastScrapeTime(ctx, p.db, anthroveUserID, sourceID, lastScrapeTime)
}
func (p *postgresqlConnection) UpdateUserSourceValidation(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, valid bool) error {
return postgres.UpdateUserSourceValidation(ctx, p.db, anthroveUserID, sourceID, valid)
}
func (p *postgresqlConnection) CreateTagAlias(ctx context.Context, tagAliasName models.AnthroveTagAliasName, tagID models.AnthroveTagID) error {
return postgres.CreateTagAlias(ctx, p.db, tagAliasName, tagID)
}
func (p *postgresqlConnection) GetAllTagAlias(ctx context.Context) ([]models.TagAlias, error) {
return postgres.GetAllTagAlias(ctx, p.db)
}
func (p *postgresqlConnection) GetAllTagAliasByTag(ctx context.Context, tagID models.AnthroveTagID) ([]models.TagAlias, error) {
return postgres.GetAllTagAliasByTag(ctx, p.db, tagID)
}
func (p *postgresqlConnection) DeleteTagAlias(ctx context.Context, tagAliasName models.AnthroveTagAliasName) error {
return postgres.DeleteTagAlias(ctx, p.db, tagAliasName)
}
func (p *postgresqlConnection) CreateTagGroup(ctx context.Context, tagGroupName models.AnthroveTagGroupName, tagID models.AnthroveTagID) error {
return postgres.CreateTagGroup(ctx, p.db, tagGroupName, tagID)
}
func (p *postgresqlConnection) GetAllTagGroup(ctx context.Context) ([]models.TagGroup, error) {
return postgres.GetAllTagGroup(ctx, p.db)
}
func (p *postgresqlConnection) GetAllTagGroupByTag(ctx context.Context, tagID models.AnthroveTagID) ([]models.TagGroup, error) {
return postgres.GetAllTagGroupByTag(ctx, p.db, tagID)
}
func (p *postgresqlConnection) DeleteTagGroup(ctx context.Context, tagGroupName models.AnthroveTagGroupName) error {
return postgres.DeleteTagGroup(ctx, p.db, tagGroupName)
}
func (p *postgresqlConnection) CreateTag(ctx context.Context, tagName models.AnthroveTagName, tagType models.TagType) error {
return postgres.CreateTag(ctx, p.db, tagName, tagType)
}
func (p *postgresqlConnection) GetAllTagsByTagType(ctx context.Context, tagType models.TagType) ([]models.Tag, error) {
return postgres.GetAllTagByTagsType(ctx, p.db, tagType)
}
func (p *postgresqlConnection) DeleteTag(ctx context.Context, tagName models.AnthroveTagName) error {
return postgres.DeleteTag(ctx, p.db, tagName)
}
func (p *postgresqlConnection) CreateTagInBatchAndUpdate(ctx context.Context, tags []models.Tag, batchSize int) error {
return postgres.CreateTagInBatchAndUpdate(ctx, p.db, tags, batchSize)
}
func (p *postgresqlConnection) CreateTagAliasInBatch(ctx context.Context, tagAliases []models.TagAlias, batchSize int) error {
return postgres.CreateTagAliasInBatch(ctx, p.db, tagAliases, batchSize)
}
func (p *postgresqlConnection) CreateTagGroupInBatch(ctx context.Context, tagGroups []models.TagGroup, batchSize int) error {
return postgres.CreateTagGroupInBatch(ctx, p.db, tagGroups, batchSize)
}
func (p *postgresqlConnection) ExecuteRawStatement(ctx context.Context, query string, args ...any) error {
return postgres.ExecuteRawStatement(ctx, p.db, query, args...)
}
func (p *postgresqlConnection) QueryRawStatement(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
return postgres.QueryRawStatement(ctx, p.db, query, args...)
}
// HELPER
func (p *postgresqlConnection) migrateDatabase(dbPool *gorm.DB) error {
dialect := "postgres"
migrations := &migrate.EmbedFileSystemMigrationSource{FileSystem: embedMigrations, Root: "migrations"}
db, err := dbPool.DB()
if err != nil {
return fmt.Errorf("postgres migration: %v", err)
}
n, err := migrate.Exec(db, dialect, migrations, migrate.Up)
if err != nil {
return fmt.Errorf("postgres migration: %v", err)
}
if p.debug {
if n != 0 {
log.Infof("postgres migration: applied %d migrations!", n)
} else {
log.Info("postgres migration: nothing to migrate")
}
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -2,107 +2,18 @@ package database
import (
"context"
"errors"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
"gorm.io/gorm"
)
func CreateUserSource(ctx context.Context, userSource models.UserSource) (models.UserSource, error) {
if client == nil {
return models.UserSource{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
type Source interface {
result := client.WithContext(ctx).Create(&userSource)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return models.UserSource{}, &otterError.Database{Reason: otterError.DuplicateKey}
}
return models.UserSource{}, result.Error
}
// CreateSource adds a new source to the database.
CreateSource(ctx context.Context, anthroveSource *models.Source) error
return userSource, nil
}
func UpdateUserSource(ctx context.Context, userSource models.UserSource) error {
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
if len(userSource.ID) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDEmpty}
}
updatedUserSource := models.UserSource{
BaseModel: models.BaseModel[models.UserSourceID]{
ID: userSource.ID,
},
ScrapeTimeInterval: userSource.ScrapeTimeInterval,
AccountUsername: userSource.AccountUsername,
AccountID: userSource.AccountID,
LastScrapeTime: userSource.LastScrapeTime,
AccountValidate: userSource.AccountValidate,
}
result := client.WithContext(ctx).Updates(&updatedUserSource)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.Database{Reason: otterError.DuplicateKey}
}
return result.Error
}
return nil
}
func GetUserSourceByID(ctx context.Context, id models.UserSourceID) (models.UserSource, error) {
var user models.UserSource
if client == nil {
return models.UserSource{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
if len(id) == 0 {
return models.UserSource{}, &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDEmpty}
}
if len(id) != 25 {
return models.UserSource{}, &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDToShort}
}
result := client.WithContext(ctx).First(&user, id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return models.UserSource{}, &otterError.Database{Reason: otterError.NoDataFound}
}
return models.UserSource{}, result.Error
}
return user, nil
}
func DeleteUserSource(ctx context.Context, id models.UserSourceID) error {
var user models.UserSource
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
if len(id) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDEmpty}
}
if len(id) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDToShort}
}
result := client.WithContext(ctx).Delete(&user, id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.Database{Reason: otterError.NoDataFound}
}
return result.Error
}
return nil
// GetAllSources retrieves all sources.
GetAllSources(ctx context.Context) ([]models.Source, error)
// GetSourceByDomain retrieves a source by its URL.
GetSourceByDomain(ctx context.Context, sourceDomain models.AnthroveSourceDomain) (*models.Source, error)
}

20
pkg/database/tag.go Normal file
View File

@ -0,0 +1,20 @@
package database
import (
"context"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
)
type Tag interface {
CreateTag(ctx context.Context, tagName models.AnthroveTagName, tagType models.TagType) error
CreateTagInBatchAndUpdate(ctx context.Context, tags []models.Tag, batchSize int) error
// GetAllTags retrieves all tags.
GetAllTags(ctx context.Context) ([]models.Tag, error)
GetAllTagsByTagType(ctx context.Context, tagType models.TagType) ([]models.Tag, error)
DeleteTag(ctx context.Context, tagName models.AnthroveTagName) error
}

19
pkg/database/tagalias.go Normal file
View File

@ -0,0 +1,19 @@
package database
import (
"context"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
)
type TagAlias interface {
CreateTagAlias(ctx context.Context, tagAliasName models.AnthroveTagAliasName, tagID models.AnthroveTagID) error
CreateTagAliasInBatch(ctx context.Context, tagsAliases []models.TagAlias, batchSize int) error
GetAllTagAlias(ctx context.Context) ([]models.TagAlias, error)
GetAllTagAliasByTag(ctx context.Context, tagID models.AnthroveTagID) ([]models.TagAlias, error)
DeleteTagAlias(ctx context.Context, tagAliasName models.AnthroveTagAliasName) error
}

19
pkg/database/taggroup.go Normal file
View File

@ -0,0 +1,19 @@
package database
import (
"context"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
)
type TagGroup interface {
CreateTagGroup(ctx context.Context, tagGroupName models.AnthroveTagGroupName, tagID models.AnthroveTagID) error
CreateTagGroupInBatch(ctx context.Context, tagsGroups []models.TagGroup, batchSize int) error
GetAllTagGroup(ctx context.Context) ([]models.TagGroup, error)
GetAllTagGroupByTag(ctx context.Context, tagID models.AnthroveTagID) ([]models.TagGroup, error)
DeleteTagGroup(ctx context.Context, tagGroupName models.AnthroveTagGroupName) error
}

42
pkg/database/user.go Normal file
View File

@ -0,0 +1,42 @@
package database
import (
"context"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
)
type User interface {
// CreateUserWithRelationToSource adds a user with a relation to a source.
CreateUserWithRelationToSource(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, accountId string, accountUsername string) error
// CreateReferenceBetweenUserAndPost links a user with a post.
CreateReferenceBetweenUserAndPost(ctx context.Context, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) error
UpdateUserSourceScrapeTimeInterval(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, scrapeTime models.AnthroveScrapeTimeInterval) error
UpdateUserSourceLastScrapeTime(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, lastScrapeTime models.AnthroveUserLastScrapeTime) error
UpdateUserSourceValidation(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, valid bool) error
GetAllUsers(ctx context.Context) ([]models.User, error)
// GetUserFavoritesCount retrieves the count of a user's favorites.
GetUserFavoritesCount(ctx context.Context, anthroveUserID models.AnthroveUserID) (int64, error)
// GetAllUserSources retrieves the source links of a user.
GetAllUserSources(ctx context.Context, anthroveUserID models.AnthroveUserID) (map[string]models.UserSource, error)
// GetUserSourceBySourceID retrieves a specified source link of a user.
GetUserSourceBySourceID(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID) (*models.UserSource, error)
// GetAllUserFavoritesWithPagination retrieves a user's favorite posts with pagination.
GetAllUserFavoritesWithPagination(ctx context.Context, anthroveUserID models.AnthroveUserID, skip int, limit int) (*models.FavoriteList, error)
// GetAllTagsFromUser retrieves a user's tags through their favorite posts.
GetAllTagsFromUser(ctx context.Context, anthroveUserID models.AnthroveUserID) ([]models.TagsWithFrequency, error)
// CheckIfUserHasPostAsFavorite checks if a user-post link exists.
CheckIfUserHasPostAsFavorite(ctx context.Context, anthroveUserID models.AnthroveUserID, sourcePostID models.AnthrovePostID) (bool, error)
}

View File

@ -1,19 +1,25 @@
package error
import "fmt"
type EntityAlreadyExists struct{}
const (
EntityAlreadyExists = "EntityAlreadyExists"
NoDataWritten = "NoDataWritten"
NoDataFound = "NoDataFound"
DatabaseIsNotConnected = "database is not connected"
DuplicateKey = "DuplicateKey"
)
type Database struct {
Reason string
func (e *EntityAlreadyExists) Error() string {
return "EntityAlreadyExists error"
}
func (e Database) Error() string {
return fmt.Sprintf("Database error: %s", e.Reason)
type NoDataWritten struct{}
func (e *NoDataWritten) Error() string {
return "NoDataWritten error"
}
type NoDataFound struct{}
func (e *NoDataFound) Error() string {
return "NoDataFound error"
}
type NoRelationCreated struct{}
func (e *NoRelationCreated) Error() string {
return "relationship creation error"
}

View File

@ -3,13 +3,11 @@ package error
import "fmt"
const (
UserIDIsEmpty = "anthrovePostID cannot be empty"
UserIDToShort = "anthrovePostID needs to be 25 characters long"
SourceIDEmpty = "anthroveSourceID cannot be empty"
SourceIDToShort = "anthroveSourceID needs to be 25 characters long"
UserSourceIDEmpty = "anthroveUserSourceID cannot be empty"
UserSourceIDToShort = "anthroveUserSourceID needs to be 25 characters long"
TagIDEmpty = "tagID cannot be empty"
AnthroveUserIDIsEmpty = "anthrovePostID cannot be empty"
AnthroveUserIDToShort = "anthrovePostID needs to be 25 characters long"
AnthroveSourceIDEmpty = "anthroveSourceID cannot be empty"
AnthroveSourceIDToShort = "anthroveSourceID needs to be 25 characters long"
AnthroveTagIDEmpty = "tagID cannot be empty"
)
type EntityValidationFailed struct {

View File

@ -2,25 +2,20 @@ package models
import "time"
type (
UserID string
PostID string
SourceID string
SourceDomain string
PostURL string
TagGroupName string
TagAliasName string
TagID string
ScrapeTimeInterval int
UserLastScrapeTime time.Time
TagName string
type AnthroveUserID string
type AnthrovePostID string
type AnthroveSourceID string
type AnthroveSourceDomain string
type AnthrovePostURL string
type AnthroveTagGroupName string
type AnthroveTagAliasName string
type AnthroveTagID string
type AnthroveScrapeTimeInterval int
type AnthroveUserLastScrapeTime time.Time
type AnthroveTagName string
Rating string
TagType string
UserSourceID string
UserFavoriteID string
)
type Rating string
type TagType string
const (
SFW Rating = "safe"

View File

@ -8,7 +8,7 @@ import (
)
type ID interface {
UserID | SourceID | PostID | UserSourceID | UserFavoriteID
AnthroveUserID | AnthroveSourceID | AnthrovePostID
}
type BaseModel[T ID] struct {

View File

@ -42,8 +42,8 @@ func TestBaseModel_BeforeCreate(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
base := &BaseModel[PostID]{
ID: PostID(tt.fields.ID),
base := &BaseModel[AnthrovePostID]{
ID: AnthrovePostID(tt.fields.ID),
CreatedAt: tt.fields.CreatedAt,
UpdatedAt: tt.fields.UpdatedAt,
DeletedAt: tt.fields.DeletedAt,

View File

@ -2,7 +2,7 @@ package models
// Post model
type Post struct {
BaseModel[PostID]
BaseModel[AnthrovePostID]
Rating Rating `json:"rating" gorm:"type:enum('safe','questionable','explicit')"`
Tags []Tag `json:"-" gorm:"many2many:post_tags;"`
Favorites []UserFavorites `json:"-" gorm:"foreignKey:PostID"`

View File

@ -4,7 +4,7 @@ import "testing"
func TestPost_TableName(t *testing.T) {
type fields struct {
BaseModel BaseModel[PostID]
BaseModel BaseModel[AnthrovePostID]
Rating Rating
Tags []Tag
Favorites []UserFavorites

View File

@ -2,7 +2,7 @@ package models
// Source model
type Source struct {
BaseModel[SourceID]
BaseModel[AnthroveSourceID]
DisplayName string `json:"display_name" `
Domain string `json:"domain" gorm:"not null;unique"`
Icon string `json:"icon" gorm:"not null"`

View File

@ -4,7 +4,7 @@ import "testing"
func TestSource_TableName(t *testing.T) {
type fields struct {
BaseModel BaseModel[SourceID]
BaseModel BaseModel[AnthroveSourceID]
DisplayName string
Domain string
Icon string

View File

@ -2,7 +2,7 @@ package models
// User model
type User struct {
BaseModel[UserID]
BaseModel[AnthroveUserID]
Favorites []UserFavorites `json:"-" gorm:"foreignKey:UserID"`
Sources []UserSource `json:"-" gorm:"foreignKey:UserID"`
}

View File

@ -3,12 +3,9 @@ package models
import "time"
type UserFavorites struct {
BaseModel[UserFavoriteID]
UserID string `json:"user_id"`
PostID string `json:"post_id"`
UserSourceID UserSourceID `json:"user_source_id"`
UserSource UserSource `json:"-" gorm:"foreignKey:ID;references:UserSourceID"`
CreatedAt time.Time `json:"created_at"`
UserID string `json:"user_id" gorm:"primaryKey"`
PostID string `json:"post_id" gorm:"primaryKey"`
CreatedAt time.Time `json:"-"`
}
func (UserFavorites) TableName() string {

View File

@ -3,11 +3,10 @@ package models
import "time"
type UserSource struct {
BaseModel[UserSourceID]
User User `json:"user" gorm:"foreignKey:ID;references:UserID"`
UserID UserID `json:"user_id"`
UserID string `json:"user_id" gorm:"primaryKey"`
Source Source `json:"source" gorm:"foreignKey:ID;references:SourceID"`
SourceID SourceID `json:"source_id"`
SourceID string `json:"source_id" gorm:"primaryKey"`
ScrapeTimeInterval string `json:"scrape_time_interval"`
AccountUsername string `json:"account_username"`
AccountID string `json:"account_id"`

View File

@ -5,9 +5,9 @@ import "testing"
func TestUserSource_TableName(t *testing.T) {
type fields struct {
User User
UserID UserID
UserID string
Source Source
SourceID SourceID
SourceID string
ScrapeTimeInterval string
AccountUsername string
AccountID string

View File

@ -4,7 +4,7 @@ import "testing"
func TestUser_TableName(t *testing.T) {
type fields struct {
BaseModel BaseModel[UserID]
BaseModel BaseModel[AnthroveUserID]
Favorites []UserFavorites
Sources []UserSource
}