Compare commits

...

19 Commits

Author SHA1 Message Date
5ffb558ab8 fix(tracing): wrong attribute type
Some checks failed
Gitea Build Check / Build (push) Failing after 30s
changed attribute types in to Int
2024-08-12 11:54:57 +02:00
0a8f84bb15 fix(tracing): wrong attribute type
changed attribute types in to Int
2024-08-12 11:54:53 +02:00
6583ae7d29 fix(tracing): wrong attribute type
changed attribute types in to Int
2024-08-12 11:54:50 +02:00
e38171e53d fix(tracing): wrong attribute type
changed attribute types in to Int
2024-08-12 11:53:48 +02:00
7cf51f9b90 fix(tracing): wrong attribute type
changed attribute types in to Int
2024-08-12 11:53:17 +02:00
9d9b354698 fix(tracing): wrong attribute type
changed attribute types in to Int
2024-08-12 11:52:53 +02:00
ae7bc131f7 refactor(tracing): tracing & logging
added the custom util functions to handel telemetry
2024-08-12 11:50:45 +02:00
135e09ea8e refactor(tracing): tracing & logging
added the custom util functions to handel telemetry
2024-08-12 11:46:53 +02:00
24d51a3751 refactor(tracing): tracing & logging
added the custom util functions to handel telemetry
2024-08-12 11:39:22 +02:00
d994ead6f6 refactor(tracing): tracing & logging
added the custom util functions to handel telemetry
2024-08-12 11:36:34 +02:00
4d262c8fff refactor(tracing): tracing & logging
added the custom util functions to handel telemetry
2024-08-12 11:32:53 +02:00
ffc3164cb7 refactor(tracing): tracing & logging
added the custom util functions to handel telemetry
2024-08-12 11:17:02 +02:00
9512cd10bc feat(tracing): Add utils for OpenTelemetry tracing and event logging 2024-08-12 11:15:34 +02:00
505bc3b522 feat(tracing): Refactor error handling to include trace status update 2024-08-12 11:15:21 +02:00
99ea2d37ab chore(refactor): removed dead code 2024-08-12 11:14:53 +02:00
3f92bdb325 feat(tracing): Add tracing, logging, and error handling to tagGroup database functions 2024-08-12 10:07:42 +02:00
098ecda869 chore(tracing): Rename "tag_count" to "tag_aliases_count" 2024-08-12 10:07:29 +02:00
ca1e1a1da5 feat(tracing): added tracing 2024-08-12 09:58:14 +02:00
6bec8e3373 chore(tracing): Update log field names for clarity 2024-08-12 09:57:31 +02:00
23 changed files with 688 additions and 5560 deletions

View File

@ -1,42 +0,0 @@
package postgres
import (
"context"
"database/sql"
"errors"
"gorm.io/gorm"
)
func ExecuteRawStatement(ctx context.Context, db *gorm.DB, query string, args ...any) error {
if query == "" {
return errors.New("query can not be empty")
}
if args == nil {
return errors.New("arguments can not be nil")
}
result := db.WithContext(ctx).Exec(query, args...)
if result.Error != nil {
return result.Error
}
return nil
}
func QueryRawStatement(ctx context.Context, db *gorm.DB, query string, args ...any) (*sql.Rows, error) {
if query == "" {
return nil, errors.New("query can not be empty")
}
if args == nil {
return nil, errors.New("arguments can not be nil")
}
result, err := db.WithContext(ctx).Raw(query, args...).Rows()
if err != nil {
return nil, err
}
return result, nil
}

View File

@ -1,125 +0,0 @@
package postgres
import (
"context"
"database/sql"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/test"
"gorm.io/gorm"
"reflect"
"testing"
)
func TestExecuteRawStatement(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)
// Test
type args struct {
ctx context.Context
db *gorm.DB
query string
args []any
}
tests := []struct {
name string
args args
want *sql.Rows
wantErr bool
}{
{
name: "Test 01: Empty Query",
args: args{
ctx: ctx,
db: gormDB,
query: "",
args: nil,
},
want: nil,
wantErr: true,
},
{
name: "Test 02: Nil Query",
args: args{
ctx: ctx,
db: gormDB,
query: "aasd",
args: nil,
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ExecuteRawStatement(tt.args.ctx, tt.args.db, tt.args.query, tt.args.args...)
if (err != nil) != tt.wantErr {
t.Errorf("ExecuteRawStatement() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func TestQueryRawStatement(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)
// Test
type args struct {
ctx context.Context
db *gorm.DB
query string
args []any
}
tests := []struct {
name string
args args
want *sql.Rows
wantErr bool
}{
{
name: "Test 01: Empty Query",
args: args{
ctx: ctx,
db: gormDB,
query: "",
args: nil,
},
want: nil,
wantErr: true,
},
{
name: "Test 02: Nil Query",
args: args{
ctx: ctx,
db: gormDB,
query: "aasd",
args: nil,
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := QueryRawStatement(tt.args.ctx, tt.args.db, tt.args.query, tt.args.args...)
if (err != nil) != tt.wantErr {
t.Errorf("QueryRawStatement() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("QueryRawStatement() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -1,131 +0,0 @@
package postgres
import (
"context"
"errors"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
)
func CreatePost(ctx context.Context, db *gorm.DB, anthrovePost *models.Post) error {
if anthrovePost == nil {
return &otterError.EntityValidationFailed{Reason: "anthrovePost is nil"}
}
result := db.WithContext(ctx).Create(&anthrovePost)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
if result.RowsAffected == 0 {
return &otterError.NoDataWritten{}
}
log.WithFields(log.Fields{
"anthrove_post_id": anthrovePost.ID,
"anthrove_post_rating": anthrovePost.Rating,
}).Trace("database: created anthrove post")
return nil
}
func CreatePostInBatch(ctx context.Context, db *gorm.DB, anthrovePost []models.Post, batchSize int) error {
if anthrovePost == nil {
return &otterError.EntityValidationFailed{Reason: "anthrovePost cannot be nil"}
}
if len(anthrovePost) == 0 {
return &otterError.EntityValidationFailed{Reason: "anthrovePost cannot be empty"}
}
if batchSize == 0 {
return &otterError.EntityValidationFailed{Reason: "batch size cannot be zero"}
}
result := db.WithContext(ctx).CreateInBatches(anthrovePost, batchSize)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
if result.RowsAffected == 0 {
return &otterError.NoDataWritten{}
}
log.WithFields(log.Fields{
"tag_size": len(anthrovePost),
"batch_size": batchSize,
}).Trace("database: created tag node")
return nil
}
func GetPostByAnthroveID(ctx context.Context, db *gorm.DB, anthrovePostID models.AnthrovePostID) (*models.Post, error) {
if anthrovePostID == "" {
return nil, &otterError.EntityValidationFailed{Reason: "anthrovePostID is not set"}
}
if len(anthrovePostID) != 25 {
return nil, &otterError.EntityValidationFailed{Reason: "anthrovePostID needs to be 25 characters long"}
}
var post models.Post
result := db.WithContext(ctx).First(&post, "id = ?", anthrovePostID)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
return &post, nil
}
func GetPostBySourceURL(ctx context.Context, db *gorm.DB, sourceURL string) (*models.Post, error) {
if sourceURL == "" {
return nil, &otterError.EntityValidationFailed{Reason: "sourceURL is not set"}
}
var post models.Post
result := 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)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
return &post, nil
}
func GetPostBySourceID(ctx context.Context, db *gorm.DB, sourceID models.AnthroveSourceID) (*models.Post, error) {
if sourceID == "" {
return nil, &otterError.EntityValidationFailed{Reason: "sourceID is not set"}
}
var post models.Post
result := 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)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
return &post, nil
}

View File

@ -1,469 +0,0 @@
package postgres
import (
"context"
"fmt"
"testing"
_ "github.com/lib/pq"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/test"
"gorm.io/gorm"
)
func TestCreateAnthrovePostNode(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
validPost := &models.Post{
BaseModel: models.BaseModel[models.AnthrovePostID]{
ID: models.AnthrovePostID(fmt.Sprintf("%025s", "1")),
},
Rating: "safe",
}
invalidPost := &models.Post{
Rating: "error",
}
// Test
type args struct {
ctx context.Context
db *gorm.DB
anthrovePost *models.Post
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Test 1: Valid AnthrovePostID and Rating",
args: args{
ctx: context.Background(),
db: gormDB,
anthrovePost: validPost,
},
wantErr: false,
},
{
name: "Test 2: Invalid Rating",
args: args{
ctx: context.Background(),
db: gormDB,
anthrovePost: invalidPost,
},
wantErr: true,
},
{
name: "Test 3: Nill",
args: args{
ctx: context.Background(),
db: gormDB,
anthrovePost: nil,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := CreatePost(tt.args.ctx, tt.args.db, tt.args.anthrovePost); (err != nil) != tt.wantErr {
t.Errorf("CreatePost() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestGetPostByAnthroveID(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[models.AnthrovePostID]{
ID: models.AnthrovePostID(fmt.Sprintf("%025s", "1")),
},
Rating: "safe",
}
err = CreatePost(ctx, gormDB, post)
if err != nil {
t.Fatal("Could not create post", err)
}
// Test
type args struct {
ctx context.Context
db *gorm.DB
anthrovePostID models.AnthrovePostID
}
tests := []struct {
name string
args args
want *models.Post
wantErr bool
}{
{
name: "Test 1: Valid anthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthrovePostID: post.ID,
},
want: post,
wantErr: false,
},
{
name: "Test 2: Invalid anthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthrovePostID: "1234",
},
want: nil,
wantErr: true,
},
{
name: "Test 3: No anthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthrovePostID: "",
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetPostByAnthroveID(tt.args.ctx, tt.args.db, tt.args.anthrovePostID)
if (err != nil) != tt.wantErr {
t.Errorf("GetPostByAnthroveID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !checkPost(got, tt.want) {
t.Errorf("GetPostByAnthroveID() 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[models.AnthrovePostID]{
ID: models.AnthrovePostID(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[models.AnthroveSourceID]{
ID: models.AnthroveSourceID(fmt.Sprintf("%025s", "1")),
},
DisplayName: "e621",
Domain: "e621.net",
Icon: "https://e621.net/icon.ico",
}
err = CreateSource(ctx, gormDB, &source)
if err != nil {
t.Fatal("Could not create source", err)
}
err = CreateReferenceBetweenPostAndSource(ctx, gormDB, post.ID, models.AnthroveSourceDomain(source.Domain), "http://test.org", models.PostReferenceConfig{})
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: "http://test.org",
},
want: post,
wantErr: false,
},
{
name: "Test 2: Invalid sourceURL",
args: args{
ctx: ctx,
db: gormDB,
sourceURL: "1234",
},
want: nil,
wantErr: true,
},
{
name: "Test 3: No sourceURL",
args: args{
ctx: ctx,
db: gormDB,
sourceURL: "",
},
want: nil,
wantErr: true,
},
}
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[models.AnthrovePostID]{
ID: models.AnthrovePostID(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[models.AnthroveSourceID]{
ID: models.AnthroveSourceID(fmt.Sprintf("%025s", "1")),
},
DisplayName: "e621",
Domain: "e621.net",
Icon: "https://e621.net/icon.ico",
}
err = CreateSource(ctx, gormDB, &source)
if err != nil {
t.Fatal("Could not create source", err)
}
err = CreateReferenceBetweenPostAndSource(ctx, gormDB, post.ID, models.AnthroveSourceDomain(source.Domain), "http://test.otg", models.PostReferenceConfig{})
if err != nil {
t.Fatal("Could not create source reference", err)
}
// Test
type args struct {
ctx context.Context
db *gorm.DB
sourceID models.AnthroveSourceID
}
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: true,
},
{
name: "Test 3: No sourceID",
args: args{
ctx: ctx,
db: gormDB,
sourceID: "",
},
want: nil,
wantErr: true,
},
}
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 TestCreatePostInBatch(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
validPosts := []models.Post{
{
Rating: models.SFW,
},
{
Rating: models.NSFW,
},
{
Rating: models.Questionable,
},
}
emptyPost := []models.Post{}
// Test
type args struct {
ctx context.Context
db *gorm.DB
anthrovePost []models.Post
batchSize int
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Test 1: Valid Data",
args: args{
ctx: ctx,
db: gormDB,
anthrovePost: validPosts,
batchSize: len(validPosts),
},
wantErr: false,
},
{
name: "Test 2: Emtpy Data",
args: args{
ctx: ctx,
db: gormDB,
anthrovePost: emptyPost,
batchSize: 0,
},
wantErr: true,
},
{
name: "Test 3: Nil Data",
args: args{
ctx: ctx,
db: gormDB,
anthrovePost: nil,
batchSize: 0,
},
wantErr: true,
},
{
name: "Test 4: batchSize 0",
args: args{
ctx: ctx,
db: gormDB,
anthrovePost: validPosts,
batchSize: 0,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := CreatePostInBatch(tt.args.ctx, tt.args.db, tt.args.anthrovePost, tt.args.batchSize); (err != nil) != tt.wantErr {
t.Errorf("CreatePostInBatch() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func checkPost(got *models.Post, want *models.Post) bool {
if got == nil && want == nil {
return true
} else if got == nil || want == nil {
return false
}
if got.ID != want.ID {
return false
}
if got.Rating != want.Rating {
return false
}
return true
}

View File

@ -1,126 +0,0 @@
package postgres
import (
"context"
"errors"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
)
func CreateReferenceBetweenPostAndSource(ctx context.Context, db *gorm.DB, anthrovePostID models.AnthrovePostID, sourceDomain models.AnthroveSourceDomain, postURL models.AnthrovePostURL, config models.PostReferenceConfig) error {
if anthrovePostID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthrovePostID) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
if sourceDomain == "" {
return &otterError.EntityValidationFailed{Reason: "sourceDomain cannot be empty"}
}
result := db.WithContext(ctx).Exec(`INSERT INTO "PostReference" (post_id, source_id, url, full_file_url, preview_file_url, sample_file_url, source_post_id) SELECT $1, source.id, $2, $4, $5, $6, $7 FROM "Source" AS source WHERE domain = $3;`, anthrovePostID, postURL, sourceDomain, config.FullFileURL, config.PreviewFileURL, config.SampleFileURL, config.SourcePostID)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.NoDataFound{}
}
if errors.Is(result.Error, gorm.ErrCheckConstraintViolated) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
if result.RowsAffected == 0 {
return &otterError.NoDataWritten{}
}
log.WithFields(log.Fields{
"anthrove_post_id": anthrovePostID,
"anthrove_source_domain": sourceDomain,
}).Trace("database: created anthrove post to source link")
return nil
}
func CreateReferenceBetweenUserAndPost(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) error {
if anthrovePostID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthrovePostID) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
if anthroveUserID == "" {
return &otterError.EntityValidationFailed{Reason: "anthroveUserID cannot be empty"}
}
userFavorite := models.UserFavorite{
UserID: string(anthroveUserID),
PostID: string(anthrovePostID),
}
result := db.WithContext(ctx).Create(&userFavorite)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
if result.RowsAffected == 0 {
return &otterError.NoDataWritten{}
}
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
"anthrove_post_id": anthrovePostID,
}).Trace("database: created user to post link")
return nil
}
func CheckReferenceBetweenUserAndPost(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) (bool, error) {
var count int64
if anthrovePostID == "" {
return false, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthrovePostID) != 25 {
return false, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
if anthroveUserID == "" {
return false, &otterError.EntityValidationFailed{Reason: "anthroveUserID cannot be empty"}
}
if len(anthroveUserID) != 25 {
return false, &otterError.EntityValidationFailed{Reason: "anthroveUserID needs to be 25 characters long"}
}
result := db.WithContext(ctx).Model(&models.UserFavorite{}).Where("user_id = ? AND post_id = ?", string(anthroveUserID), string(anthrovePostID)).Count(&count)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return false, &otterError.NoDataFound{}
}
return false, result.Error
}
exists := count > 0
log.WithFields(log.Fields{
"relationship_exists": exists,
"relationship_anthrove_user_id": anthroveUserID,
"relationship_anthrove_post_id": anthrovePostID,
}).Trace("database: checked user post relationship")
return exists, nil
}

View File

@ -1,392 +0,0 @@
package postgres
import (
"context"
"fmt"
"testing"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/test"
"gorm.io/gorm"
)
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
validUserID := models.AnthroveUserID(fmt.Sprintf("%025s", "User1"))
invalidUserID := models.AnthroveUserID("XXX")
validPostID := models.AnthrovePostID(fmt.Sprintf("%025s", "Post1"))
err = CreateUser(ctx, gormDB, validUserID)
if err != nil {
t.Fatal(err)
}
post := &models.Post{
BaseModel: models.BaseModel[models.AnthrovePostID]{
ID: validPostID,
},
Rating: "safe",
}
err = CreatePost(ctx, gormDB, post)
if err != nil {
t.Fatal(err)
}
err = CreateReferenceBetweenUserAndPost(ctx, gormDB, validUserID, post.ID)
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: validUserID,
anthrovePostID: post.ID,
},
want: true,
wantErr: false,
},
{
name: "Test 2: Valid AnthroveUserID and invalid AnthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: validUserID,
anthrovePostID: "qadw",
},
want: false,
wantErr: true,
},
{
name: "Test 3: Valid AnthrovePostID and invalid AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: invalidUserID,
anthrovePostID: post.ID,
},
want: false,
wantErr: true,
},
{
name: "Test 4: Invalid AnthrovePostID and invalid AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: invalidUserID,
anthrovePostID: "123456",
},
want: false,
wantErr: true,
},
{
name: "Test 5: No AnthrovePostID given",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "",
anthrovePostID: "123456",
},
want: false,
wantErr: true,
},
{
name: "Test 6: No anthrovePostID given",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: invalidUserID,
anthrovePostID: "",
},
want: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CheckReferenceBetweenUserAndPost(tt.args.ctx, tt.args.db, tt.args.anthroveUserID, tt.args.anthrovePostID)
if (err != nil) != tt.wantErr {
t.Errorf("CheckIfUserHasPostAsFavorite() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("CheckIfUserHasPostAsFavorite() got = %v, want %v", got, tt.want)
}
})
}
}
func TestCheckUserToPostLinkWithNoData(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
validUserID := models.AnthroveUserID(fmt.Sprintf("%025s", "User1"))
invalidUserID := models.AnthroveUserID("XXX")
validPostID := models.AnthrovePostID(fmt.Sprintf("%025s", "Post1"))
err = CreateUser(ctx, gormDB, validUserID)
if err != nil {
t.Fatal(err)
}
post := &models.Post{
BaseModel: models.BaseModel[models.AnthrovePostID]{
ID: validPostID,
},
Rating: "safe",
}
err = CreatePost(ctx, gormDB, post)
if err != nil {
t.Fatal(err)
}
err = CreateReferenceBetweenUserAndPost(ctx, gormDB, validUserID, post.ID)
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: validUserID,
anthrovePostID: post.ID,
},
want: true,
wantErr: false,
},
{
name: "Test 2: Valid AnthroveUserID and invalid AnthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: validUserID,
anthrovePostID: "qadw",
},
want: false,
wantErr: true,
},
{
name: "Test 3: Valid AnthrovePostID and invalid AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: invalidUserID,
anthrovePostID: post.ID,
},
want: false,
wantErr: true,
},
{
name: "Test 4: Invalid AnthrovePostID and invalid AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: invalidUserID,
anthrovePostID: "123456",
},
want: false,
wantErr: true,
},
{
name: "Test 5: No AnthrovePostID given",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "",
anthrovePostID: "123456",
},
want: false,
wantErr: true,
},
{
name: "Test 6: No anthrovePostID given",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: invalidUserID,
anthrovePostID: "",
},
want: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CheckReferenceBetweenUserAndPost(tt.args.ctx, tt.args.db, tt.args.anthroveUserID, tt.args.anthrovePostID)
if (err != nil) != tt.wantErr {
t.Errorf("CheckIfUserHasPostAsFavorite() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("CheckIfUserHasPostAsFavorite() got = %v, want %v", got, tt.want)
}
})
}
}
func TestEstablishUserToPostLink(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
validUserID := models.AnthroveUserID(fmt.Sprintf("%025s", "User1"))
invalidUserID := models.AnthroveUserID("XXX")
validPostID := models.AnthrovePostID(fmt.Sprintf("%025s", "Post1"))
err = CreateUser(ctx, gormDB, validUserID)
if err != nil {
t.Fatal(err)
}
post := &models.Post{
BaseModel: models.BaseModel[models.AnthrovePostID]{
ID: validPostID,
},
Rating: "safe",
}
err = CreatePost(ctx, gormDB, post)
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
wantErr bool
}{
{
name: "Test 1: Valid AnthroveUserID and AnthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: validUserID,
anthrovePostID: post.ID,
},
wantErr: false,
},
{
name: "Test 2: Valid AnthroveUserID and invalid AnthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: validUserID,
anthrovePostID: "123456",
},
wantErr: true,
},
{
name: "Test 3: invalid AnthroveUserID and valid AnthrovePostID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: invalidUserID,
anthrovePostID: post.ID,
},
wantErr: true,
},
{
name: "Test 4: Invalid AnthrovePostID and invalid AnthroveUserID",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: invalidUserID,
anthrovePostID: "123456",
},
wantErr: true,
},
{
name: "Test 5: AnthrovePostID is empty",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: invalidUserID,
anthrovePostID: "",
},
wantErr: true,
},
{
name: "Test 6: anthroveUserID is empty",
args: args{
ctx: ctx,
db: gormDB,
anthroveUserID: "",
anthrovePostID: validPostID,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := CreateReferenceBetweenUserAndPost(tt.args.ctx, tt.args.db, tt.args.anthroveUserID, tt.args.anthrovePostID); (err != nil) != tt.wantErr {
t.Errorf("CreateReferenceBetweenUserAndPost() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -1,85 +0,0 @@
package postgres
import (
"context"
"errors"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
)
// CreateSource creates a pgModels.Source
func CreateSource(ctx context.Context, db *gorm.DB, anthroveSource *models.Source) error {
if anthroveSource.Domain == "" {
return &otterError.EntityValidationFailed{Reason: "Domain is required"}
}
result := db.WithContext(ctx).Where(models.Source{Domain: anthroveSource.Domain}).FirstOrCreate(anthroveSource)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
if result.RowsAffected == 0 {
return &otterError.NoDataWritten{}
}
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
}
// GetAllSource returns a list of all pgModels.Source
func GetAllSource(ctx context.Context, db *gorm.DB) ([]models.Source, error) {
var sources []models.Source
result := db.WithContext(ctx).Find(&sources)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
log.WithFields(log.Fields{
"tag_amount": result.RowsAffected,
}).Trace("database: get all source nodes")
return sources, nil
}
// GetSourceByDomain returns the first source it finds based on the domain
func GetSourceByDomain(ctx context.Context, db *gorm.DB, sourceDomain models.AnthroveSourceDomain) (*models.Source, error) {
var sources models.Source
if sourceDomain == "" {
return nil, &otterError.EntityValidationFailed{Reason: "AnthroveSourceDomain is not set"}
}
result := db.WithContext(ctx).Where("domain = ?", sourceDomain).First(&sources)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
log.WithFields(log.Fields{
"tag_amount": result.RowsAffected,
}).Trace("database: get all source nodes")
return &sources, nil
}

View File

@ -1,270 +0,0 @@
package postgres
import (
"context"
"fmt"
"testing"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/test"
"gorm.io/gorm"
)
func TestCreateSourceNode(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
validPostID := models.AnthroveSourceID(fmt.Sprintf("%025s", "Post1"))
validSource := &models.Source{
BaseModel: models.BaseModel[models.AnthroveSourceID]{ID: validPostID},
DisplayName: "e621",
Domain: "e621.net",
Icon: "icon.e621.net",
}
invalidSource := &models.Source{
BaseModel: models.BaseModel[models.AnthroveSourceID]{ID: validPostID},
Domain: "notfound.intern",
}
invalidSourceDomain := &models.Source{
BaseModel: models.BaseModel[models.AnthroveSourceID]{ID: validPostID},
Domain: "",
}
// Test
type args struct {
ctx context.Context
db *gorm.DB
anthroveSource *models.Source
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Test 1: Valid anthroveSource",
args: args{
ctx: ctx,
db: gormDB,
anthroveSource: validSource,
},
wantErr: false,
},
{
name: "Test 2: inValid anthroveSource",
args: args{
ctx: ctx,
db: gormDB,
anthroveSource: invalidSourceDomain,
},
wantErr: true,
},
{
name: "Test 3: unique anthroveSource",
args: args{
ctx: ctx,
db: gormDB,
anthroveSource: invalidSource,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := CreateSource(tt.args.ctx, tt.args.db, tt.args.anthroveSource); (err != nil) != tt.wantErr {
t.Errorf("CreateSource() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestGetAllSourceNodes(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
sources := []models.Source{
{
DisplayName: "e621",
Domain: "e621.net",
Icon: "icon.e621.net",
},
{
DisplayName: "furaffinity",
Domain: "furaffinity.net",
Icon: "icon.furaffinity.net",
},
{
DisplayName: "fenpaws",
Domain: "fenpa.ws",
Icon: "icon.fenpa.ws",
},
}
for _, source := range sources {
err = CreateSource(ctx, gormDB, &source)
if err != nil {
t.Fatal(err)
}
}
// Test
type args struct {
ctx context.Context
db *gorm.DB
}
tests := []struct {
name string
args args
want []models.Source
wantErr bool
}{
{
name: "Test 1: Get all entries",
args: args{
ctx: ctx,
db: gormDB,
},
want: sources,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetAllSource(tt.args.ctx, tt.args.db)
if (err != nil) != tt.wantErr {
t.Errorf("GetAllSource() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !checkSourcesNode(got, tt.want) {
t.Errorf("GetAllSource() got = %v, want %v", got, tt.want)
}
})
}
}
func TestGetSourceNodesByURL(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 := &models.Source{
DisplayName: "e621",
Domain: "e621.net",
Icon: "icon.e621.net",
}
err = CreateSource(ctx, gormDB, source)
if err != nil {
t.Fatal(err)
}
// Test
type args struct {
ctx context.Context
db *gorm.DB
domain models.AnthroveSourceDomain
}
tests := []struct {
name string
args args
want *models.Source
wantErr bool
}{
{
name: "Test 1: Valid URL",
args: args{
ctx: ctx,
db: gormDB,
domain: "e621.net",
},
want: source,
wantErr: false,
},
{
name: "Test 2: Invalid URL",
args: args{
ctx: ctx,
db: gormDB,
domain: "eeeee.net",
},
want: nil,
wantErr: true,
},
{
name: "Test 2: No URL",
args: args{
ctx: ctx,
db: gormDB,
domain: "",
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetSourceByDomain(tt.args.ctx, tt.args.db, tt.args.domain)
if (err != nil) != tt.wantErr {
t.Errorf("GetSourceByDomain() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !checkSourceNode(got, tt.want) {
t.Errorf("GetSourceByDomain() got = %v, want %v", got, tt.want)
}
})
}
}
func checkSourcesNode(got []models.Source, want []models.Source) bool {
for i, source := range want {
if source.DisplayName != got[i].DisplayName {
return false
}
if source.Domain != got[i].Domain {
return false
}
if source.Icon != got[i].Icon {
return false
}
}
return true
}
func checkSourceNode(got *models.Source, want *models.Source) bool {
if want == nil && got == nil {
return true
}
if got.Domain != want.Domain {
return false
}
return true
}

View File

@ -1,440 +0,0 @@
package postgres
import (
"context"
"errors"
"gorm.io/gorm/clause"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
)
func CreateTag(ctx context.Context, db *gorm.DB, tagName models.AnthroveTagName, tagType models.TagType) error {
if tagName == "" {
return &otterError.EntityValidationFailed{Reason: "tagName cannot be empty"}
}
if tagType == "" {
return &otterError.EntityValidationFailed{Reason: "tagType cannot be empty"}
}
result := db.WithContext(ctx).Create(&models.Tag{Name: string(tagName), Type: tagType})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
if result.RowsAffected == 0 {
return &otterError.NoDataWritten{}
}
log.WithFields(log.Fields{
"tag_name": tagName,
"tag_type": tagType,
}).Trace("database: created tag node")
return nil
}
func CreateTagInBatchAndUpdate(ctx context.Context, db *gorm.DB, tags []models.Tag, batchSize int) error {
if len(tags) == 0 {
return &otterError.EntityValidationFailed{Reason: "tags cannot be empty"}
}
if tags == nil {
return &otterError.EntityValidationFailed{Reason: "tags cannot be nil"}
}
if batchSize == 0 {
return &otterError.EntityValidationFailed{Reason: "batch size cannot be zero"}
}
result := db.WithContext(ctx).
Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoUpdates: clause.AssignmentColumns([]string{"tag_type"}),
}).CreateInBatches(tags, batchSize)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
if result.RowsAffected == 0 {
return &otterError.NoDataWritten{}
}
log.WithFields(log.Fields{
"tag_size": len(tags),
"batch_size": batchSize,
}).Trace("database: created tag node")
return nil
}
func DeleteTag(ctx context.Context, db *gorm.DB, tagName models.AnthroveTagName) error {
if tagName == "" {
return &otterError.EntityValidationFailed{Reason: "tagName cannot be empty"}
}
result := db.WithContext(ctx).Delete(&models.Tag{Name: string(tagName)})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.NoDataFound{}
}
return result.Error
}
log.WithFields(log.Fields{
"tag_name": tagName,
}).Trace("database: deleted tag")
return nil
}
func GetAllTagByTagsType(ctx context.Context, db *gorm.DB, tagType models.TagType) ([]models.Tag, error) {
var tags []models.Tag
if tagType == "" {
return nil, &otterError.EntityValidationFailed{Reason: "tagType cannot be empty"}
}
result := db.WithContext(ctx).Model(&models.Tag{}).Where("tag_type = ?", tagType).Scan(&tags)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
log.WithFields(log.Fields{
"tags_length": len(tags),
}).Trace("database: got tag")
return tags, nil
}
func CreateTagAndReferenceToPost(ctx context.Context, db *gorm.DB, anthrovePostID models.AnthrovePostID, tag *models.Tag) error {
if anthrovePostID == "" {
return &otterError.EntityValidationFailed{Reason: "anthrovePostID cannot be empty"}
}
if len(anthrovePostID) != 25 {
return &otterError.EntityValidationFailed{Reason: "anthrovePostID needs to be 25 characters long"}
}
if tag == nil {
return &otterError.EntityValidationFailed{Reason: "Tag is nil"}
}
pgPost := models.Post{
BaseModel: models.BaseModel[models.AnthrovePostID]{
ID: anthrovePostID,
},
}
err := db.WithContext(ctx).Model(&pgPost).Association("Tags").Append(tag)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return &otterError.NoDataFound{}
}
return errors.Join(err, &otterError.NoRelationCreated{})
}
log.WithFields(log.Fields{
"anthrove_post_id": anthrovePostID,
"tag_name": tag.Name,
"tag_type": tag.Type,
}).Trace("database: created tag node")
return nil
}
func GetTags(ctx context.Context, db *gorm.DB) ([]models.Tag, error) {
var tags []models.Tag
result := db.WithContext(ctx).Find(&tags)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
log.WithFields(log.Fields{
"tag_amount": len(tags),
}).Trace("database: got tags")
return tags, nil
}
func CreateTagAlias(ctx context.Context, db *gorm.DB, tagAliasName models.AnthroveTagAliasName, tagID models.AnthroveTagID) error {
if tagAliasName == "" {
return &otterError.EntityValidationFailed{Reason: "tagAliasName cannot be empty"}
}
if tagID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveTagIDEmpty}
}
result := db.WithContext(ctx).Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: true,
}).Create(&models.TagAlias{
Name: string(tagAliasName),
TagID: string(tagID),
})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
log.WithFields(log.Fields{
"tag_alias_name": tagAliasName,
"tag_alias_tag_id": tagID,
}).Trace("database: created tagAlias")
return nil
}
func CreateTagAliasInBatch(ctx context.Context, db *gorm.DB, tagAliases []models.TagAlias, batchSize int) error {
if len(tagAliases) == 0 {
return &otterError.EntityValidationFailed{Reason: "tagAliases cannot be empty"}
}
if tagAliases == nil {
return &otterError.EntityValidationFailed{Reason: "tagAliases cannot be nil"}
}
if batchSize == 0 {
return &otterError.EntityValidationFailed{Reason: "batch size cannot be zero"}
}
result := db.WithContext(ctx).Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: true,
}).CreateInBatches(tagAliases, batchSize)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
if result.RowsAffected == 0 {
return &otterError.NoDataWritten{}
}
log.WithFields(log.Fields{
"tag_size": len(tagAliases),
"batch_size": batchSize,
}).Trace("database: created tag node")
return nil
}
func GetAllTagAlias(ctx context.Context, db *gorm.DB) ([]models.TagAlias, error) {
var tagAliases []models.TagAlias
result := db.WithContext(ctx).Find(&tagAliases)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
log.WithFields(log.Fields{
"tag_alias_length": len(tagAliases),
}).Trace("database: created tagAlias")
return tagAliases, nil
}
func GetAllTagAliasByTag(ctx context.Context, db *gorm.DB, tagID models.AnthroveTagID) ([]models.TagAlias, error) {
var tagAliases []models.TagAlias
if tagID == "" {
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveTagIDEmpty}
}
result := db.WithContext(ctx).Where("tag_id = ?", tagID).Find(&tagAliases)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
log.WithFields(log.Fields{
"tag_alias_length": len(tagAliases),
"tag_alias_tag_id": tagID,
}).Trace("database: get specific tagAlias")
return tagAliases, nil
}
func DeleteTagAlias(ctx context.Context, db *gorm.DB, tagAliasName models.AnthroveTagAliasName) error {
if tagAliasName == "" {
return &otterError.EntityValidationFailed{Reason: "tagAliasName cannot be empty"}
}
result := db.WithContext(ctx).Delete(&models.TagAlias{Name: string(tagAliasName)})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.NoDataFound{}
}
return result.Error
}
log.WithFields(log.Fields{
"tag_alias_name": tagAliasName,
}).Trace("database: deleted tagAlias")
return nil
}
func CreateTagGroup(ctx context.Context, db *gorm.DB, tagGroupName models.AnthroveTagGroupName, tagID models.AnthroveTagID) error {
if tagGroupName == "" {
return &otterError.EntityValidationFailed{Reason: "tagGroupName cannot be empty"}
}
if tagID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveTagIDEmpty}
}
result := db.WithContext(ctx).Create(&models.TagGroup{
Name: string(tagGroupName),
TagID: string(tagID),
})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
log.WithFields(log.Fields{
"tag_group_name": tagGroupName,
"tag_group_tag_id": tagID,
}).Trace("database: created tagGroup")
return nil
}
func CreateTagGroupInBatch(ctx context.Context, db *gorm.DB, tagGroups []models.TagGroup, batchSize int) error {
if len(tagGroups) == 0 {
return &otterError.EntityValidationFailed{Reason: "tagAliases cannot be empty"}
}
if tagGroups == nil {
return &otterError.EntityValidationFailed{Reason: "tagAliases cannot be nil"}
}
if batchSize == 0 {
return &otterError.EntityValidationFailed{Reason: "batch size cannot be zero"}
}
result := db.WithContext(ctx).CreateInBatches(tagGroups, batchSize)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
if result.RowsAffected == 0 {
return &otterError.NoDataWritten{}
}
log.WithFields(log.Fields{
"tag_size": len(tagGroups),
"batch_size": batchSize,
}).Trace("database: created tag node")
return nil
}
func GetAllTagGroup(ctx context.Context, db *gorm.DB) ([]models.TagGroup, error) {
var tagGroups []models.TagGroup
result := db.WithContext(ctx).Find(&tagGroups)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
log.WithFields(log.Fields{
"tag_alias_length": len(tagGroups),
}).Trace("database: created tagGroup")
return tagGroups, nil
}
func GetAllTagGroupByTag(ctx context.Context, db *gorm.DB, tagID models.AnthroveTagID) ([]models.TagGroup, error) {
var tagGroups []models.TagGroup
if tagID == "" {
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveTagIDEmpty}
}
result := db.WithContext(ctx).Where("tag_id = ?", tagID).Find(&tagGroups)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
log.WithFields(log.Fields{
"tag_alias_length": len(tagGroups),
"tag_alias_tag_id": tagID,
}).Trace("database: get specific tagGroup")
return tagGroups, nil
}
func DeleteTagGroup(ctx context.Context, db *gorm.DB, tagGroupName models.AnthroveTagGroupName) error {
if tagGroupName == "" {
return &otterError.EntityValidationFailed{Reason: "tagGroupName cannot be empty"}
}
result := db.WithContext(ctx).Delete(&models.TagGroup{Name: string(tagGroupName)})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.NoDataFound{}
}
return result.Error
}
log.WithFields(log.Fields{
"tag_alias_name": tagGroupName,
}).Trace("database: deleted tagAlias")
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -1,398 +0,0 @@
package postgres
import (
"context"
"errors"
"time"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
gonanoid "github.com/matoous/go-nanoid/v2"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
)
// Workaround, should be changed later maybe, but its not that bad right now
type selectFrequencyTag struct {
tagName string `gorm:"tag_name"`
count int64 `gorm:"count"`
tagType models.TagType `gorm:"tag_type"`
}
func CreateUser(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) error {
if anthroveUserID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthroveUserID) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
user := models.User{
BaseModel: models.BaseModel[models.AnthroveUserID]{
ID: anthroveUserID,
},
}
result := db.WithContext(ctx).FirstOrCreate(&user)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
return nil
}
func CreateUserWithRelationToSource(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, accountId string, accountUsername string) error {
if anthroveUserID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthroveUserID) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
if accountId == "" {
return &otterError.EntityValidationFailed{Reason: "accountID cannot be empty"}
}
if accountUsername == "" {
return &otterError.EntityValidationFailed{Reason: "accountUsername cannot be empty"}
}
validationCode, err := gonanoid.New(25)
if err != nil {
return err
}
result := db.WithContext(ctx).Exec(`WITH userObj AS (
INSERT INTO "User" (id)
VALUES ($1)
ON CONFLICT (id) DO NOTHING
)
INSERT INTO "UserSource" (user_id, source_id, account_username, account_id, account_validate, account_validation_key)
SELECT $2, source.id, $3, $4, false, $5
FROM "Source" AS source
WHERE source.id = $6;`, anthroveUserID, anthroveUserID, accountUsername, accountId, validationCode, sourceID)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.EntityAlreadyExists{}
}
return result.Error
}
if result.RowsAffected == 0 {
return &otterError.NoDataWritten{}
}
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
"source_id": sourceID,
"account_username": accountUsername,
"account_id": accountId,
}).Info("database: created user-source relationship")
return nil
}
func GetUserFavoritesCount(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) (int64, error) {
var count int64
if anthroveUserID == "" {
return 0, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthroveUserID) != 25 {
return 0, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
result := db.WithContext(ctx).Model(&models.UserFavorite{}).Where("user_id = ?", string(anthroveUserID)).Count(&count)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return 0, &otterError.NoDataFound{}
}
return 0, result.Error
}
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
"anthrove_user_fav_count": count,
}).Trace("database: got user favorite count")
return count, nil
}
func GetUserSourceLinks(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) (map[string]models.UserSource, error) {
var userSources []models.UserSource
userSourceMap := make(map[string]models.UserSource)
if anthroveUserID == "" {
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthroveUserID) != 25 {
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
result := db.WithContext(ctx).Model(&models.UserSource{}).Where("user_id = ?", string(anthroveUserID)).Find(&userSources)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
for _, userSource := range userSources {
var source models.Source
result = db.WithContext(ctx).Model(&models.Source{}).Where("id = ?", userSource.SourceID).First(&source)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
userSourceMap[source.DisplayName] = models.UserSource{
UserID: userSource.AccountID,
AccountUsername: userSource.AccountUsername,
Source: models.Source{
DisplayName: source.DisplayName,
Domain: source.Domain,
Icon: source.Icon,
},
}
}
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
}).Trace("database: got user source link")
return userSourceMap, nil
}
func GetUserSourceBySourceID(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID) (*models.UserSource, error) {
var userSource models.UserSource
if anthroveUserID == "" {
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthroveUserID) != 25 {
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
if sourceID == "" {
return nil, &otterError.EntityValidationFailed{Reason: "sourceID cannot be empty"}
}
if len(sourceID) != 25 {
return nil, &otterError.EntityValidationFailed{Reason: "sourceID needs to be 25 characters long"}
}
result := db.WithContext(ctx).Model(&models.UserSource{}).InnerJoins("Source", db.Where("id = ?", sourceID)).Where("user_id = ?", string(anthroveUserID)).First(&userSource)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
"source_id": sourceID,
}).Trace("database: got specified user source link")
return &userSource, nil
}
func GetAllUsers(ctx context.Context, db *gorm.DB) ([]models.User, error) {
var users []models.User
result := db.WithContext(ctx).Model(&models.User{}).Find(&users)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, result.Error
}
log.WithFields(log.Fields{
"anthrove_user_id_count": len(users),
}).Trace("database: got all anthrove user IDs")
return users, nil
}
// TODO: FIX THE TEST
func GetUserFavoriteWithPagination(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, skip int, limit int) (*models.FavoriteList, error) {
var favoritePosts []models.Post
if anthroveUserID == "" {
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthroveUserID) != 25 {
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
db.WithContext(ctx).Joins("RIGHT JOIN \"UserFavorite\" AS of ON \"Post\".id = of.post_id AND of.user_id = ?", anthroveUserID).Preload("References").Offset(skip).Limit(limit).Find(&favoritePosts)
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
"anthrove_user_fav_count": len(favoritePosts),
}).Trace("database: got all anthrove user favorites")
return &models.FavoriteList{Posts: favoritePosts}, nil
}
func GetUserTagWitRelationToFavedPosts(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) ([]models.TagsWithFrequency, error) {
var queryUserFavorites []selectFrequencyTag
if anthroveUserID == "" {
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthroveUserID) != 25 {
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
rows, err := db.WithContext(ctx).Raw(
`WITH user_posts AS (
SELECT post_id FROM "UserFavorite" WHERE user_id = $1
)
SELECT post_tags.tag_name AS tag_name, count(*) AS count, (SELECT tag_type FROM "Tag" WHERE "Tag".name = post_tags.tag_name LIMIT 1) AS tag_type FROM post_tags, user_posts WHERE post_tags.post_id IN (user_posts.post_id) GROUP BY post_tags.tag_name ORDER BY tag_type DESC, tag_name DESC`, anthroveUserID).Rows()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, &otterError.NoDataFound{}
}
return nil, err
}
var userFavoritesFrequency = make([]models.TagsWithFrequency, 0)
defer rows.Close()
for rows.Next() {
var tagName string
var count int64
var tagType string
rows.Scan(&tagName, &count, &tagType)
userFavoritesFrequency = append(userFavoritesFrequency, models.TagsWithFrequency{
Frequency: count,
Tags: models.Tag{
Name: tagName,
Type: models.TagType(tagType),
},
})
}
log.WithFields(log.Fields{
"anthrove_user_id": anthroveUserID,
"tag_amount": len(queryUserFavorites),
}).Trace("database: got user tag node with relation to faved posts")
return userFavoritesFrequency, nil
}
func UpdateUserSourceScrapeTimeInterval(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, scrapeTime models.AnthroveScrapeTimeInterval) error {
if anthroveUserID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthroveUserID) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
if sourceID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDEmpty}
}
if len(sourceID) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDToShort}
}
if scrapeTime == 0 {
return &otterError.EntityValidationFailed{Reason: "ScrapeTimeInterval cannot be empty"}
}
userSource := &models.UserSource{
UserID: string(anthroveUserID),
}
result := db.WithContext(ctx).Model(&userSource).Update("scrape_time_interval", scrapeTime)
if result.Error != nil {
return result.Error
}
return nil
}
func UpdateUserSourceLastScrapeTime(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, lastScrapeTime models.AnthroveUserLastScrapeTime) error {
if anthroveUserID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthroveUserID) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
if sourceID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDEmpty}
}
if len(sourceID) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDToShort}
}
if time.Time.IsZero(time.Time(lastScrapeTime)) {
return &otterError.EntityValidationFailed{Reason: "LastScrapeTime cannot be empty"}
}
userSource := &models.UserSource{
UserID: string(anthroveUserID),
}
result := db.WithContext(ctx).Model(&userSource).Update("last_scrape_time", time.Time(lastScrapeTime))
if result.Error != nil {
return result.Error
}
return nil
}
func UpdateUserSourceValidation(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, valid bool) error {
if anthroveUserID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
}
if len(anthroveUserID) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
}
if sourceID == "" {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDEmpty}
}
if len(sourceID) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDToShort}
}
userSource := &models.UserSource{
UserID: string(anthroveUserID),
}
result := db.WithContext(ctx).Model(&userSource).Update("account_validate", valid)
if result.Error != nil {
return result.Error
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,15 @@ package utils
import (
"context"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
func HandleError(ctx context.Context, span trace.Span, logger *log.Logger, fields log.Fields, error error) error {
logger.WithContext(ctx).WithFields(fields).Error(error)
func HandleError(ctx context.Context, span trace.Span, logger *log.Entry, error error) error {
logger.Error(error)
span.RecordError(error)
span.SetStatus(codes.Error, error.Error())
return error
}

20
internal/utils/tracing.go Normal file
View File

@ -0,0 +1,20 @@
package utils
import (
"context"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/trace"
)
func SetupTracing(ctx context.Context, tracer trace.Tracer, tracerName string) (context.Context, trace.Span, *log.Entry) {
ctx, span := tracer.Start(ctx, tracerName)
localLogger := log.WithContext(ctx)
return ctx, span, localLogger
}
func HandleEvent(span trace.Span, logger *log.Entry, eventName string) {
logger.Debug(eventName)
span.AddEvent(eventName)
}

View File

@ -4,12 +4,15 @@ import (
"context"
"embed"
"fmt"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/internal/utils"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
migrate "github.com/rubenv/sql-migrate"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/contrib/bridges/otellogrus"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"gorm.io/driver/postgres"
"gorm.io/gorm"
@ -22,12 +25,28 @@ var (
embedMigrations embed.FS
client *gorm.DB
tracer trace.Tracer
logger *log.Logger
logger = log.New()
)
func Connect(_ context.Context, config models.DatabaseConfig) error {
var localSSL string
func Connect(ctx context.Context, config models.DatabaseConfig) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "Connect")
defer span.End()
localLogger.WithFields(log.Fields{
"endpoint": config.Endpoint,
"port": config.Port,
"database": config.Database,
})
span.SetAttributes(
attribute.String("endpoint", config.Endpoint),
attribute.Int("port", config.Port),
attribute.String("database", config.Database),
)
utils.HandleEvent(span, localLogger, "Starting database connection")
var localSSL string
if config.SSL {
localSSL = "require"
} else {
@ -36,46 +55,58 @@ func Connect(_ context.Context, config models.DatabaseConfig) error {
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
return utils.HandleError(ctx, span, localLogger, err)
}
err = migrateDatabase(sqlDB, config)
err = migrateDatabase(ctx, sqlDB, config)
if err != nil {
return err
return utils.HandleError(ctx, span, localLogger, err)
}
setupTelemetry()
client = sqlDB
utils.HandleEvent(span, localLogger, "Database connected successfully")
return nil
}
func migrateDatabase(dbPool *gorm.DB, config models.DatabaseConfig) error {
func migrateDatabase(ctx context.Context, dbPool *gorm.DB, config models.DatabaseConfig) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "migrateDatabase")
defer span.End()
localLogger.WithFields(log.Fields{
"database": config.Database,
})
span.SetAttributes(
attribute.String("database", config.Database),
)
utils.HandleEvent(span, localLogger, "Starting database migration")
dialect := "postgres"
migrations := &migrate.EmbedFileSystemMigrationSource{FileSystem: embedMigrations, Root: "migrations"}
db, err := dbPool.DB()
if err != nil {
return fmt.Errorf("postgres migration: %v", err)
return utils.HandleError(ctx, span, localLogger, err)
}
n, err := migrate.Exec(db, dialect, migrations, migrate.Up)
if err != nil {
return fmt.Errorf("postgres migration: %v", err)
return utils.HandleError(ctx, span, localLogger, err)
}
if config.Debug {
if n != 0 {
log.Infof("postgres migration: applied %d migrations!", n)
localLogger.Infof("postgres migration: applied %d migrations!", n)
} else {
log.Info("postgres migration: nothing to migrate")
localLogger.Info("postgres migration: nothing to migrate")
}
}
utils.HandleEvent(span, localLogger, "Database migration completed successfully")
return nil
}
@ -84,13 +115,18 @@ func setupTelemetry() {
hook := otellogrus.NewHook(tracingName)
logger.AddHook(hook)
}
func GetGorm(ctx context.Context) (*gorm.DB, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "GetGorm")
defer span.End()
utils.HandleEvent(span, localLogger, "Retrieving GORM client")
if client == nil {
return nil, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return nil, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
utils.HandleEvent(span, localLogger, "GORM client retrieved successfully")
return client, nil
}

View File

@ -3,62 +3,105 @@ package database
import (
"context"
"errors"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/internal/utils"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"gorm.io/gorm"
)
func CreateUserFavorite(ctx context.Context, userFav models.UserFavorite) (models.UserFavorite, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateUserFavorite")
defer span.End()
localLogger.WithFields(log.Fields{
"user_favorite_id": userFav.ID,
})
span.SetAttributes(
attribute.String("user_favorite_id", string(userFav.ID)),
)
utils.HandleEvent(span, localLogger, "Starting user favorite creation")
if client == nil {
return models.UserFavorite{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return models.UserFavorite{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
result := client.WithContext(ctx).Create(&userFav)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return models.UserFavorite{}, &otterError.Database{Reason: otterError.DuplicateKey}
return models.UserFavorite{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return models.UserFavorite{}, result.Error
return models.UserFavorite{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "User favorite created successfully")
return userFav, nil
}
func CreateUserFavoriteInBatch(ctx context.Context, userFav []models.UserFavorite, batchSize int) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateUserFavoriteInBatch")
defer span.End()
localLogger.WithFields(log.Fields{
"user_favorite_count": len(userFav),
"batch_size": batchSize,
})
span.SetAttributes(
attribute.Int("batch_size", batchSize),
attribute.Int("user_favorite_count", len(userFav)),
)
utils.HandleEvent(span, localLogger, "Starting batch user favorite creation")
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if userFav == nil {
return &otterError.EntityValidationFailed{Reason: otterError.UserFavoriteListIsEmpty}
}
if len(userFav) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.UserFavoriteListIsEmpty}
if userFav == nil || len(userFav) == 0 {
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.UserFavoriteListIsEmpty})
}
if batchSize == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty})
}
result := client.WithContext(ctx).CreateInBatches(&userFav, batchSize)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.Database{Reason: otterError.DuplicateKey}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Batch user favorites created successfully")
return nil
}
func UpdateUserFavorite(ctx context.Context, userFav models.UserFavorite) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "UpdateUserFavorite")
defer span.End()
localLogger.WithFields(log.Fields{
"user_favorite_id": userFav.ID,
})
span.SetAttributes(
attribute.String("user_favorite_id", string(userFav.ID)),
)
utils.HandleEvent(span, localLogger, "Starting user favorite update")
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(userFav.ID) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.UserFavoriteIDIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.UserFavoriteIDIsEmpty})
}
if !userFav.DeletedAt.Valid {
@ -68,58 +111,87 @@ func UpdateUserFavorite(ctx context.Context, userFav models.UserFavorite) error
result := client.WithContext(ctx).Model(&userFav).Update("deleted_at", gorm.DeletedAt{})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.Database{Reason: otterError.NoDataFound}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "User favorite updated successfully")
return nil
}
func GetUserFavoritesByID(ctx context.Context, id models.UserFavoriteID) (models.UserFavorite, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "GetUserFavoritesByID")
defer span.End()
localLogger.WithFields(log.Fields{
"user_favorite_id": id,
})
span.SetAttributes(
attribute.String("user_favorite_id", string(id)),
)
utils.HandleEvent(span, localLogger, "Starting get user favorite by ID")
var userFavorites models.UserFavorite
if client == nil {
return models.UserFavorite{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return models.UserFavorite{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(id) == 0 {
return models.UserFavorite{}, &otterError.EntityValidationFailed{Reason: otterError.UserFavoriteIDIsEmpty}
return models.UserFavorite{}, utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.UserFavoriteIDIsEmpty})
}
result := client.WithContext(ctx).First(&userFavorites, "id = ?", id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return models.UserFavorite{}, &otterError.Database{Reason: otterError.NoDataFound}
return models.UserFavorite{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return models.UserFavorite{}, result.Error
return models.UserFavorite{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "User favorite retrieved successfully")
return userFavorites, nil
}
func DeleteUserFavorite(ctx context.Context, id models.UserFavoriteID) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "DeleteUserFavorite")
defer span.End()
localLogger.WithFields(log.Fields{
"user_favorite_id": id,
})
span.SetAttributes(
attribute.String("user_favorite_id", string(id)),
)
utils.HandleEvent(span, localLogger, "Starting delete user favorite")
var userFavorite models.UserFavorite
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(id) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.UserFavoriteIDIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.UserFavoriteIDIsEmpty})
}
if len(id) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.UserFavoriteIDToShort}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.UserFavoriteIDToShort})
}
result := client.WithContext(ctx).Delete(&userFavorite, "id = ?", id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.Database{Reason: otterError.NoDataFound}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "User favorite deleted successfully")
return nil
}

View File

@ -3,84 +3,141 @@ package database
import (
"context"
"errors"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/internal/utils"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"gorm.io/gorm"
)
func CreatePost(ctx context.Context, post models.Post) (models.Post, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreatePost")
defer span.End()
localLogger.WithFields(log.Fields{
"post_id": post.ID,
})
span.SetAttributes(
attribute.String("post_id", string(post.ID)),
)
utils.HandleEvent(span, localLogger, "Starting post creation")
if client == nil {
return models.Post{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return models.Post{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
result := client.WithContext(ctx).Create(&post)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return models.Post{}, &otterError.Database{Reason: otterError.DuplicateKey}
return models.Post{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return models.Post{}, result.Error
return models.Post{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Post created successfully")
return post, nil
}
func CreatePostInBatch(ctx context.Context, post []models.Post, batchSize int) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreatePostInBatch")
defer span.End()
localLogger.WithFields(log.Fields{
"post_count": len(post),
"batch_size": batchSize,
})
span.SetAttributes(
attribute.Int("batch_size", batchSize),
attribute.Int("post_count", len(post)),
)
utils.HandleEvent(span, localLogger, "Starting batch post creation")
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if post == nil {
return &otterError.EntityValidationFailed{Reason: otterError.PostListIsEmpty}
}
if len(post) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.PostListIsEmpty}
if post == nil || len(post) == 0 {
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.PostListIsEmpty})
}
if batchSize == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty})
}
result := client.WithContext(ctx).CreateInBatches(&post, batchSize)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.Database{Reason: otterError.DuplicateKey}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Batch posts created successfully")
return nil
}
func GetPostByID(ctx context.Context, id models.PostID) (models.Post, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "GetPostByID")
defer span.End()
localLogger.WithFields(log.Fields{
"post_id": id,
})
span.SetAttributes(
attribute.String("post_id", string(id)),
)
utils.HandleEvent(span, localLogger, "Starting get post by ID")
var post models.Post
if client == nil {
return models.Post{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return models.Post{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(id) == 0 {
return models.Post{}, &otterError.EntityValidationFailed{Reason: otterError.PostIDIsEmpty}
return models.Post{}, utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.PostIDIsEmpty})
}
result := client.WithContext(ctx).First(&post, "id = ?", id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return models.Post{}, &otterError.Database{Reason: otterError.NoDataFound}
return models.Post{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return models.Post{}, result.Error
return models.Post{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Post retrieved successfully")
return post, nil
}
func UpdatePost(ctx context.Context, anthrovePost models.Post) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "UpdatePost")
defer span.End()
localLogger.WithFields(log.Fields{
"post_id": anthrovePost.ID,
})
span.SetAttributes(
attribute.String("post_id", string(anthrovePost.ID)),
)
utils.HandleEvent(span, localLogger, "Starting post update")
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(anthrovePost.ID) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.PostIDIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.PostIDIsEmpty})
}
updatePost := models.Post{
@ -92,36 +149,51 @@ func UpdatePost(ctx context.Context, anthrovePost models.Post) error {
result := client.WithContext(ctx).Model(&updatePost).Update("deleted_at", gorm.DeletedAt{})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.Database{Reason: otterError.NoDataFound}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Post updated successfully")
return nil
}
func DeletePost(ctx context.Context, id models.PostID) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "DeletePost")
defer span.End()
localLogger.WithFields(log.Fields{
"post_id": id,
})
span.SetAttributes(
attribute.String("post_id", string(id)),
)
utils.HandleEvent(span, localLogger, "Starting delete post")
var userFavorite models.UserFavorite
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(id) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.PostIDIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.PostIDIsEmpty})
}
if len(id) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.PostIDToShort}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.PostIDToShort})
}
result := client.WithContext(ctx).Delete(&userFavorite, "id = ?", id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.Database{Reason: otterError.NoDataFound}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Post deleted successfully")
return nil
}

View File

@ -3,62 +3,105 @@ package database
import (
"context"
"errors"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/internal/utils"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"gorm.io/gorm"
)
func CreateSource(ctx context.Context, source models.Source) (models.Source, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateSource")
defer span.End()
localLogger.WithFields(log.Fields{
"source_id": source.ID,
})
span.SetAttributes(
attribute.String("source_id", string(source.ID)),
)
utils.HandleEvent(span, localLogger, "Starting source creation")
if client == nil {
return models.Source{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return models.Source{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
result := client.WithContext(ctx).Create(&source)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return models.Source{}, &otterError.Database{Reason: otterError.DuplicateKey}
return models.Source{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return models.Source{}, result.Error
return models.Source{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Source created successfully")
return source, nil
}
func CreateSourceInBatch(ctx context.Context, source []models.Source, batchSize int) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateSourceInBatch")
defer span.End()
localLogger.WithFields(log.Fields{
"source_count": len(source),
"batch_size": batchSize,
})
span.SetAttributes(
attribute.Int("batch_size", batchSize),
attribute.Int("source_count", len(source)),
)
utils.HandleEvent(span, localLogger, "Starting batch source creation")
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if source == nil {
return &otterError.EntityValidationFailed{Reason: otterError.SourceListIsEmpty}
}
if len(source) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.SourceListIsEmpty}
if source == nil || len(source) == 0 {
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.SourceListIsEmpty})
}
if batchSize == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty})
}
result := client.WithContext(ctx).CreateInBatches(&source, batchSize)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.Database{Reason: otterError.DuplicateKey}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Batch sources created successfully")
return nil
}
func UpdateSource(ctx context.Context, source models.Source) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "UpdateSource")
defer span.End()
localLogger.WithFields(log.Fields{
"source_id": source.ID,
})
span.SetAttributes(
attribute.String("source_id", string(source.ID)),
)
utils.HandleEvent(span, localLogger, "Starting source update")
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(source.ID) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.SourceIDIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.SourceIDIsEmpty})
}
updateSource := models.Source{
@ -70,84 +113,127 @@ func UpdateSource(ctx context.Context, source models.Source) error {
result := client.WithContext(ctx).Model(&updateSource).Update("deleted_at", gorm.DeletedAt{})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.Database{Reason: otterError.NoDataFound}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Source updated successfully")
return nil
}
func GetSourceByID(ctx context.Context, id models.SourceID) (models.Source, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "GetSourceByID")
defer span.End()
localLogger.WithFields(log.Fields{
"source_id": id,
})
span.SetAttributes(
attribute.String("source_id", string(id)),
)
utils.HandleEvent(span, localLogger, "Starting get source by ID")
var source models.Source
if client == nil {
return models.Source{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return models.Source{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(id) == 0 {
return models.Source{}, &otterError.EntityValidationFailed{Reason: otterError.SourceIDIsEmpty}
return models.Source{}, utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.SourceIDIsEmpty})
}
if len(id) != 25 {
return models.Source{}, &otterError.EntityValidationFailed{Reason: otterError.SourceIDToShort}
return models.Source{}, utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.SourceIDToShort})
}
result := client.WithContext(ctx).First(&source, "id = ?", id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return models.Source{}, &otterError.Database{Reason: otterError.NoDataFound}
return models.Source{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return models.Source{}, result.Error
return models.Source{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Source retrieved successfully")
return source, nil
}
func GetSourceByDomain(ctx context.Context, sourceDomain models.SourceDomain) (models.Source, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "GetSourceByDomain")
defer span.End()
localLogger.WithFields(log.Fields{
"source_domain": sourceDomain,
})
span.SetAttributes(
attribute.String("source_domain", string(sourceDomain)),
)
utils.HandleEvent(span, localLogger, "Starting get source by domain")
var source models.Source
if client == nil {
return models.Source{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return models.Source{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(sourceDomain) == 0 {
return models.Source{}, &otterError.EntityValidationFailed{Reason: otterError.SourceDomainIsEmpty}
return models.Source{}, utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.SourceDomainIsEmpty})
}
result := client.WithContext(ctx).First(&source, "domain = ?", sourceDomain)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return models.Source{}, &otterError.Database{Reason: otterError.NoDataFound}
return models.Source{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return models.Source{}, result.Error
return models.Source{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Source retrieved successfully")
return source, nil
}
func DeleteSource(ctx context.Context, id models.SourceID) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "DeleteSource")
defer span.End()
localLogger.WithFields(log.Fields{
"source_id": id,
})
span.SetAttributes(
attribute.String("source_id", string(id)),
)
utils.HandleEvent(span, localLogger, "Starting delete source")
var source models.Source
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(id) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.SourceIDIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.SourceIDIsEmpty})
}
if len(id) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.SourceIDToShort}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.SourceIDToShort})
}
result := client.WithContext(ctx).Delete(&source, id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.Database{Reason: otterError.NoDataFound}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Source deleted successfully")
return nil
}

View File

@ -3,6 +3,7 @@ package database
import (
"context"
"errors"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/internal/utils"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
@ -12,18 +13,23 @@ import (
)
func CreateTag(ctx context.Context, tagName models.TagName, tagType models.TagType) (models.Tag, error) {
ctx, span := tracer.Start(ctx, "CreateTag")
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateTag")
defer span.End()
localLogger.WithFields(log.Fields{
"tag_name": tagName,
"tag_type": tagType,
})
span.SetAttributes(
attribute.String("tag_name", string(tagName)),
attribute.String("tag_type", string(tagType)),
)
span.AddEvent("Starting tag creation")
utils.HandleEvent(span, localLogger, "Starting tag creation")
if client == nil {
return models.Tag{}, utils.HandleError(ctx, span, logger, nil, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
return models.Tag{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
tag := models.Tag{
@ -31,131 +37,86 @@ func CreateTag(ctx context.Context, tagName models.TagName, tagType models.TagTy
Type: tagType,
}
logger.WithContext(ctx).WithFields(log.Fields{
"name": tagName,
"type": tagType,
}).Debug("attempting to create tag")
result := client.WithContext(ctx).Create(&tag)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
loggerFields := log.Fields{
"name": tagName,
"type": tagType,
}
return models.Tag{}, utils.HandleError(ctx, span, logger, loggerFields, &otterError.Database{Reason: otterError.DuplicateKey})
return models.Tag{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
loggerFields := log.Fields{
"name": tagName,
"type": tagType,
}
return models.Tag{}, utils.HandleError(ctx, span, logger, loggerFields, result.Error)
return models.Tag{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
logger.WithContext(ctx).WithFields(log.Fields{
"name": tagName,
"type": tagType,
}).Debug("tag created")
span.AddEvent("Tag created successfully")
utils.HandleEvent(span, localLogger, "Tag created successfully")
return tag, nil
}
func CreateTagInBatch(ctx context.Context, tags []models.Tag, batchSize int) error {
ctx, span := tracer.Start(ctx, "CreateTagInBatch")
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateTagInBatch")
defer span.End()
localLogger.WithFields(log.Fields{
"tag_count": len(tags),
"batch_size": batchSize,
})
span.SetAttributes(
attribute.Int64("batch_size", int64(batchSize)),
attribute.Int64("tag_count", int64(len(tags))),
attribute.Int("batch_size", batchSize),
attribute.Int("tag_count", len(tags)),
)
span.AddEvent("Starting batch tag creation")
utils.HandleEvent(span, localLogger, "Starting batch tag creation")
if client == nil {
return utils.HandleError(ctx, span, logger, nil, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if tags == nil || len(tags) == 0 {
return utils.HandleError(ctx, span, logger, nil, &otterError.EntityValidationFailed{Reason: otterError.TagListIsEmpty})
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.TagListIsEmpty})
}
if batchSize == 0 {
return utils.HandleError(ctx, span, logger, nil, &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty})
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty})
}
logger.WithContext(ctx).WithFields(log.Fields{
"tag_length": len(tags),
}).Debug("attempting to create tags")
result := client.WithContext(ctx).CreateInBatches(&tags, batchSize)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
loggerFields := log.Fields{
"tag_length": len(tags),
}
return utils.HandleError(ctx, span, logger, loggerFields, &otterError.Database{Reason: otterError.DuplicateKey})
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
loggerFields := log.Fields{
"tag_length": len(tags),
}
return utils.HandleError(ctx, span, logger, loggerFields, result.Error)
return utils.HandleError(ctx, span, localLogger, result.Error)
}
logger.WithContext(ctx).WithFields(log.Fields{
"tag_length": len(tags),
}).Debug("tags created")
span.AddEvent("Batch tags created successfully")
utils.HandleEvent(span, localLogger, "Batch tags created successfully")
return nil
}
func DeleteTag(ctx context.Context, tagName models.TagName) error {
ctx, span := tracer.Start(ctx, "DeleteTag")
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "DeleteTag")
defer span.End()
localLogger.WithFields(log.Fields{
"tag_name": tagName,
})
span.SetAttributes(
attribute.String("tag_name", string(tagName)),
)
span.AddEvent("Starting tag deletion")
utils.HandleEvent(span, localLogger, "Starting tag deletion")
var tag models.Tag
if client == nil {
return utils.HandleError(ctx, span, logger, nil, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(tagName) == 0 {
return utils.HandleError(ctx, span, logger, nil, &otterError.Database{Reason: otterError.TagNameIsEmpty})
}
logger.WithContext(ctx).WithFields(log.Fields{
"tag_name": tagName,
}).Debug("attempting to delete tag")
result := client.WithContext(ctx).Delete(&tag, tagName)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
loggerFields := log.Fields{
"tag_name": tagName,
}
return utils.HandleError(ctx, span, logger, loggerFields, &otterError.Database{Reason: otterError.NoDataFound})
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
loggerFields := log.Fields{
"tag_name": tagName,
}
return utils.HandleError(ctx, span, logger, loggerFields, result.Error)
return utils.HandleError(ctx, span, localLogger, result.Error)
}
logger.WithContext(ctx).WithFields(log.Fields{
"tag_name": tagName,
}).Debug("tag deleted")
span.AddEvent("Tag deleted successfully")
utils.HandleEvent(span, localLogger, "Tag deleted successfully")
return nil
}

View File

@ -3,14 +3,33 @@ package database
import (
"context"
"errors"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/internal/utils"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"gorm.io/gorm"
)
func CreateTagAlias(ctx context.Context, tagAliasName models.TagAliasName, tagName models.TagName) (models.TagAlias, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateTagAlias")
defer span.End()
localLogger.WithFields(log.Fields{
"tag_alias_name": tagAliasName,
"tag_name": tagName,
})
span.SetAttributes(
attribute.String("tag_alias_name", string(tagAliasName)),
attribute.String("tag_name", string(tagName)),
)
utils.HandleEvent(span, localLogger, "Starting tag alias creation")
if client == nil {
return models.TagAlias{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return models.TagAlias{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
tagAlias := models.TagAlias{
@ -21,60 +40,83 @@ func CreateTagAlias(ctx context.Context, tagAliasName models.TagAliasName, tagNa
result := client.WithContext(ctx).Create(&tagAlias)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return models.TagAlias{}, &otterError.Database{Reason: otterError.DuplicateKey}
return models.TagAlias{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return models.TagAlias{}, result.Error
return models.TagAlias{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Tag alias created successfully")
return tagAlias, nil
}
func CreateTagAliasInBatch(ctx context.Context, tagsAliases []models.TagAlias, batchSize int) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateTagAliasInBatch")
defer span.End()
localLogger.WithFields(log.Fields{
"tag_aliases_count": len(tagsAliases),
"batch_size": batchSize,
})
span.SetAttributes(
attribute.Int("batch_size", batchSize),
attribute.Int("tag_aliases_count", len(tagsAliases)),
)
utils.HandleEvent(span, localLogger, "Starting batch tag alias creation")
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if tagsAliases == nil {
return &otterError.EntityValidationFailed{Reason: otterError.TagAliasListIsEmpty}
}
if len(tagsAliases) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.TagAliasListIsEmpty}
if tagsAliases == nil || len(tagsAliases) == 0 {
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.TagAliasListIsEmpty})
}
if batchSize == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty})
}
result := client.WithContext(ctx).CreateInBatches(&tagsAliases, batchSize)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.Database{Reason: otterError.DuplicateKey}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Batch tags aliases created successfully")
return nil
}
func DeleteTagAlias(ctx context.Context, tagAliasName models.TagAliasName) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "DeleteTagAlias")
defer span.End()
localLogger.WithFields(log.Fields{
"tag_alias_name": tagAliasName,
})
span.SetAttributes(
attribute.String("tag_alias_name", string(tagAliasName)),
)
utils.HandleEvent(span, localLogger, "Starting tag alias deletion")
var tagAlias models.TagAlias
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
if len(tagAliasName) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.TagAliasNameIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
result := client.WithContext(ctx).Delete(&tagAlias, tagAliasName)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.Database{Reason: otterError.NoDataFound}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Tag alias deleted successfully")
return nil
}

View File

@ -3,14 +3,34 @@ package database
import (
"context"
"errors"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/internal/utils"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"gorm.io/gorm"
)
func CreateTagGroup(ctx context.Context, tagGroupName models.TagGroupName, tagName models.TagName) (models.TagGroup, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateTagGroup")
defer span.End()
localLogger.WithFields(log.Fields{
"tag_group_name": tagGroupName,
"tag_name": tagName,
})
span.SetAttributes(
attribute.String("tag_group_name", string(tagGroupName)),
attribute.String("tag_name", string(tagName)),
)
utils.HandleEvent(span, localLogger, "Starting tag group creation")
if client == nil {
return models.TagGroup{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return models.TagGroup{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
tagGroup := models.TagGroup{
@ -21,60 +41,89 @@ func CreateTagGroup(ctx context.Context, tagGroupName models.TagGroupName, tagNa
result := client.WithContext(ctx).Create(&tagGroup)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return models.TagGroup{}, &otterError.Database{Reason: otterError.DuplicateKey}
return models.TagGroup{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return models.TagGroup{}, result.Error
return models.TagGroup{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Tag group created successfully")
return tagGroup, nil
}
func CreateTagGroupInBatch(ctx context.Context, tagsGroups []models.TagGroup, batchSize int) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateTagAliasInBatch")
defer span.End()
localLogger.WithFields(log.Fields{
"tag_groups_count": len(tagsGroups),
"batch_size": batchSize,
})
span.SetAttributes(
attribute.Int("batch_size", batchSize),
attribute.Int("tag_group_count", len(tagsGroups)),
)
utils.HandleEvent(span, localLogger, "Starting batch tag group creation")
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if tagsGroups == nil {
return &otterError.EntityValidationFailed{Reason: otterError.TagGroupListIsEmpty}
}
if len(tagsGroups) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.TagGroupListIsEmpty}
if tagsGroups == nil || len(tagsGroups) == 0 {
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.TagGroupListIsEmpty})
}
if batchSize == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty})
}
result := client.WithContext(ctx).CreateInBatches(&tagsGroups, batchSize)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.Database{Reason: otterError.DuplicateKey}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Tag group created successfully")
return nil
}
func DeleteTagGroup(ctx context.Context, tagGroupName models.TagGroupName) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "DeleteTagGroup")
defer span.End()
span.SetAttributes(
attribute.String("tag_group_name", string(tagGroupName)),
)
localLogger.WithFields(log.Fields{
"tag_group_name": tagGroupName,
})
utils.HandleEvent(span, localLogger, "Starting tag group deletion")
var tagGroup models.TagGroup
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(tagGroupName) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.TagGroupNameIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.TagGroupNameIsEmpty})
}
result := client.WithContext(ctx).Delete(&tagGroup, tagGroupName)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return &otterError.Database{Reason: otterError.NoDataFound}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "Tag group deleted successfully")
return nil
}

View File

@ -3,77 +3,106 @@ package database
import (
"context"
"errors"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/internal/utils"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"gorm.io/gorm"
)
func CreateUser(ctx context.Context, id models.UserID) (models.User, error) {
if client == nil {
return models.User{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
func CreateUser(ctx context.Context, user models.User) (models.User, error) {
user := models.User{
BaseModel: models.BaseModel[models.UserID]{
ID: id,
},
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateUser")
defer span.End()
localLogger.WithFields(log.Fields{
"user_id": user.ID,
})
span.SetAttributes(
attribute.String("user_id", string(user.ID)),
)
utils.HandleEvent(span, localLogger, "Starting user creation")
if client == nil {
return models.User{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
result := client.WithContext(ctx).Create(&user)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return models.User{}, &otterError.Database{Reason: otterError.DuplicateKey}
return models.User{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return models.User{}, result.Error
return models.User{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "User created successfully")
return user, nil
}
func GetUserByID(ctx context.Context, id models.UserID) (models.User, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "GetUserByID")
defer span.End()
span.SetAttributes(
attribute.String("user_id", string(id)),
)
localLogger.WithFields(log.Fields{
"user_id": id,
})
utils.HandleEvent(span, localLogger, "Starting user retrieval")
var user models.User
if client == nil {
return models.User{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
if len(id) == 0 {
return models.User{}, &otterError.EntityValidationFailed{Reason: otterError.UserIDIsEmpty}
}
if len(id) != 25 {
return models.User{}, &otterError.EntityValidationFailed{Reason: otterError.UserIDToShort}
return models.User{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
result := client.WithContext(ctx).First(&user, "id = ?", id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return models.User{}, &otterError.Database{Reason: otterError.NoDataFound}
return models.User{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return models.User{}, result.Error
return models.User{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "User retrieved successfully")
return user, nil
}
func DeleteUser(ctx context.Context, id models.UserID) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "DeleteUser")
defer span.End()
span.SetAttributes(
attribute.String("user_id", string(id)),
)
localLogger.WithFields(log.Fields{
"user_id": id,
})
utils.HandleEvent(span, localLogger, "Starting user deletion")
var user models.User
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
if len(id) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.UserIDIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
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 utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "User deleted successfully")
return nil
}

View File

@ -3,34 +3,65 @@ package database
import (
"context"
"errors"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/internal/utils"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"gorm.io/gorm"
)
func CreateUserSource(ctx context.Context, userSource models.UserSource) (models.UserSource, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateUserSource")
defer span.End()
localLogger.WithFields(log.Fields{
"user_source_id": userSource.ID,
})
span.SetAttributes(
attribute.String("user_source_id", string(userSource.SourceID)),
)
utils.HandleEvent(span, localLogger, "Starting user source creation")
if client == nil {
return models.UserSource{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return models.UserSource{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
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{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return models.UserSource{}, result.Error
return models.UserSource{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "User source created successfully")
return userSource, nil
}
func UpdateUserSource(ctx context.Context, userSource models.UserSource) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "UpdateUserSource")
defer span.End()
localLogger.WithFields(log.Fields{
"user_source_id": userSource.ID,
})
span.SetAttributes(
attribute.String("user_source_id", string(userSource.ID)),
)
utils.HandleEvent(span, localLogger, "Starting user source update")
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(userSource.ID) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.UserSourceIDIsEmpty})
}
updatedUserSource := models.UserSource{
@ -47,62 +78,89 @@ func UpdateUserSource(ctx context.Context, userSource models.UserSource) error {
result := client.WithContext(ctx).Updates(&updatedUserSource)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
return &otterError.Database{Reason: otterError.DuplicateKey}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DuplicateKey})
}
return result.Error
return utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "User source updated successfully")
return nil
}
func GetUserSourceByID(ctx context.Context, id models.UserSourceID) (models.UserSource, error) {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "GetUserSourceByID")
defer span.End()
localLogger.WithFields(log.Fields{
"user_source_id": id,
})
span.SetAttributes(
attribute.String("user_source_id", string(id)),
)
utils.HandleEvent(span, localLogger, "Starting get user source by ID")
var user models.UserSource
if client == nil {
return models.UserSource{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return models.UserSource{}, utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(id) == 0 {
return models.UserSource{}, &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDIsEmpty}
return models.UserSource{}, utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDIsEmpty})
}
if len(id) != 25 {
return models.UserSource{}, &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDToShort}
return models.UserSource{}, utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDToShort})
}
result := client.WithContext(ctx).First(&user, "id = ?", id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return models.UserSource{}, &otterError.Database{Reason: otterError.NoDataFound}
return models.UserSource{}, utils.HandleError(ctx, span, localLogger, otterError.Database{Reason: otterError.NoDataFound})
}
return models.UserSource{}, result.Error
return models.UserSource{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
utils.HandleEvent(span, localLogger, "User source retrieved successfully")
return user, nil
}
func DeleteUserSource(ctx context.Context, id models.UserSourceID) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "DeleteUserSource")
defer span.End()
localLogger.WithFields(log.Fields{
"user_source_id": id,
})
span.SetAttributes(
attribute.String("user_source_id", string(id)),
)
utils.HandleEvent(span, localLogger, "Starting delete user source")
var user models.UserSource
if client == nil {
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
return utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.DatabaseIsNotConnected})
}
if len(id) == 0 {
return &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDIsEmpty}
return utils.HandleError(ctx, span, localLogger, &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDIsEmpty})
}
if len(id) != 25 {
return &otterError.EntityValidationFailed{Reason: otterError.UserSourceIDToShort}
return utils.HandleError(ctx, span, localLogger, &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 utils.HandleError(ctx, span, localLogger, &otterError.Database{Reason: otterError.NoDataFound})
}
return result.Error
}
utils.HandleEvent(span, localLogger, "User source deleted successfully")
return nil
}