Compare commits

..

7 Commits

Author SHA1 Message Date
f798869ef5 feat(database): implementation of functions
Some checks failed
Gitea Build Check / Build (push) Failing after 28s
- Added functionality
- Add connection checks to `CreateUserSource`, `UpdateUserSource`, `GetUserSourceByID`, and `DeleteUserSource` functions.
- Introduce validation for empty and improperly formatted IDs in `UpdateUserSource`, `GetUserSourceByID`, and `DeleteUserSource`.
- Handle duplicate key errors and record not found errors with appropriate custom error messages.
2024-08-09 22:16:03 +02:00
598ca747eb refactor(types): using correct types 2024-08-09 22:14:56 +02:00
b8e29de4fc refactor(error): error handling in database package
- Replace custom error types with string constants for common database errors.
- Simplify the Database struct to include a Reason field for error messages.
- Update Error method to format the error output using the Reason.
- Modify validation error constants for consistency and clarity.
2024-08-09 22:14:13 +02:00
fefc777888 feat(impl): Add database client and migration functionality
Implement connection to PostgreSQL database using GORM. Embed SQL migrations and handle migration execution with logging capabilities. Define configuration structure for database settings.
2024-08-09 21:39:22 +02:00
f940f22d67 chore(impl): removed old functions
removed all old implementation of the SDK

BREAKING-CHANGE: breaks database compatibility to older SDK versions
2024-08-09 21:38:35 +02:00
0e6a8fd61e feat(database): added additional columns
added more columns

BREAKING-CHANGE: breaks database compatibility to older SDK versions
2024-08-09 21:37:54 +02:00
e62280a416 feat(database): added additional columns
added more columns

BREAKING-CHANGE: breaks database compatibility to older SDK versions
2024-08-09 21:37:27 +02:00
25 changed files with 245 additions and 4303 deletions

68
pkg/database/client.go Normal file
View File

@ -0,0 +1,68 @@
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
}

View File

@ -1,36 +0,0 @@
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" CREATE TABLE "Post"
( (
id CHAR(25) PRIMARY KEY, id CHAR(25) PRIMARY KEY,
rating Rating, rating Rating,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
@ -28,9 +28,9 @@ CREATE TABLE "Post"
CREATE TABLE "Source" CREATE TABLE "Source"
( (
id CHAR(25) PRIMARY KEY, id CHAR(25) PRIMARY KEY,
display_name TEXT NULL, display_name TEXT NULL,
icon TEXT NULL, icon TEXT NULL,
domain TEXT NOT NULL UNIQUE, domain TEXT NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL deleted_at TIMESTAMP NULL
@ -47,7 +47,7 @@ CREATE TABLE "Tag"
CREATE TABLE "User" CREATE TABLE "User"
( (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL deleted_at TIMESTAMP NULL
@ -57,7 +57,7 @@ CREATE TABLE "PostReference"
( (
post_id TEXT REFERENCES "Post" (id), post_id TEXT REFERENCES "Post" (id),
source_id TEXT REFERENCES "Source" (id), source_id TEXT REFERENCES "Source" (id),
url TEXT NOT NULL, url TEXT NOT NULL,
full_file_url TEXT, full_file_url TEXT,
preview_file_url TEXT, preview_file_url TEXT,
sample_file_url TEXT, sample_file_url TEXT,
@ -79,16 +79,12 @@ CREATE TABLE "TagGroup"
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 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" 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), user_id TEXT REFERENCES "User" (id),
source_id TEXT REFERENCES "Source" (id), source_id TEXT REFERENCES "Source" (id),
scrape_time_interval INT, scrape_time_interval INT,
@ -97,10 +93,20 @@ CREATE TABLE "UserSource"
last_scrape_time TIMESTAMP, last_scrape_time TIMESTAMP,
account_validate BOOL DEFAULT FALSE, account_validate BOOL DEFAULT FALSE,
account_validation_key CHAR(25), account_validation_key CHAR(25),
PRIMARY KEY (user_id, source_id),
UNIQUE (source_id, account_username, account_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" CREATE TABLE "post_tags"
( (
post_id TEXT REFERENCES "Post" (id), post_id TEXT REFERENCES "Post" (id),

View File

@ -1,31 +0,0 @@
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
}

View File

@ -1,268 +0,0 @@
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,18 +2,107 @@ package database
import ( import (
"context" "context"
"errors"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models" "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
"gorm.io/gorm"
) )
type Source interface { func CreateUserSource(ctx context.Context, userSource models.UserSource) (models.UserSource, error) {
if client == nil {
return models.UserSource{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
// CreateSource adds a new source to the database. result := client.WithContext(ctx).Create(&userSource)
CreateSource(ctx context.Context, anthroveSource *models.Source) error if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return models.UserSource{}, &otterError.Database{Reason: otterError.DuplicateKey}
}
return models.UserSource{}, result.Error
}
// GetAllSources retrieves all sources. return userSource, nil
GetAllSources(ctx context.Context) ([]models.Source, error) }
// GetSourceByDomain retrieves a source by its URL. func UpdateUserSource(ctx context.Context, userSource models.UserSource) error {
GetSourceByDomain(ctx context.Context, sourceDomain models.AnthroveSourceDomain) (*models.Source, 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
} }

View File

@ -1,20 +0,0 @@
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
}

View File

@ -1,19 +0,0 @@
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
}

View File

@ -1,19 +0,0 @@
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
}

View File

@ -1,42 +0,0 @@
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,25 +1,19 @@
package error package error
type EntityAlreadyExists struct{} import "fmt"
func (e *EntityAlreadyExists) Error() string { const (
return "EntityAlreadyExists error" EntityAlreadyExists = "EntityAlreadyExists"
NoDataWritten = "NoDataWritten"
NoDataFound = "NoDataFound"
DatabaseIsNotConnected = "database is not connected"
DuplicateKey = "DuplicateKey"
)
type Database struct {
Reason string
} }
type NoDataWritten struct{} func (e Database) Error() string {
return fmt.Sprintf("Database error: %s", e.Reason)
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,11 +3,13 @@ package error
import "fmt" import "fmt"
const ( const (
AnthroveUserIDIsEmpty = "anthrovePostID cannot be empty" UserIDIsEmpty = "anthrovePostID cannot be empty"
AnthroveUserIDToShort = "anthrovePostID needs to be 25 characters long" UserIDToShort = "anthrovePostID needs to be 25 characters long"
AnthroveSourceIDEmpty = "anthroveSourceID cannot be empty" SourceIDEmpty = "anthroveSourceID cannot be empty"
AnthroveSourceIDToShort = "anthroveSourceID needs to be 25 characters long" SourceIDToShort = "anthroveSourceID needs to be 25 characters long"
AnthroveTagIDEmpty = "tagID cannot be empty" UserSourceIDEmpty = "anthroveUserSourceID cannot be empty"
UserSourceIDToShort = "anthroveUserSourceID needs to be 25 characters long"
TagIDEmpty = "tagID cannot be empty"
) )
type EntityValidationFailed struct { type EntityValidationFailed struct {

View File

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

View File

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

View File

@ -42,8 +42,8 @@ func TestBaseModel_BeforeCreate(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) {
base := &BaseModel[AnthrovePostID]{ base := &BaseModel[PostID]{
ID: AnthrovePostID(tt.fields.ID), ID: PostID(tt.fields.ID),
CreatedAt: tt.fields.CreatedAt, CreatedAt: tt.fields.CreatedAt,
UpdatedAt: tt.fields.UpdatedAt, UpdatedAt: tt.fields.UpdatedAt,
DeletedAt: tt.fields.DeletedAt, DeletedAt: tt.fields.DeletedAt,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,9 +3,12 @@ package models
import "time" import "time"
type UserFavorites struct { type UserFavorites struct {
UserID string `json:"user_id" gorm:"primaryKey"` BaseModel[UserFavoriteID]
PostID string `json:"post_id" gorm:"primaryKey"` UserID string `json:"user_id"`
CreatedAt time.Time `json:"-"` 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"`
} }
func (UserFavorites) TableName() string { func (UserFavorites) TableName() string {

View File

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

View File

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

View File

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