2025-06-09 19:24:45 -03:00
|
|
|
package repository
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"database/sql"
|
|
|
|
|
"errors"
|
2025-06-26 19:11:13 -03:00
|
|
|
"fmt"
|
2025-06-09 19:24:45 -03:00
|
|
|
"log/slog"
|
2025-06-26 19:11:13 -03:00
|
|
|
"strings"
|
|
|
|
|
"time"
|
2025-06-09 19:24:45 -03:00
|
|
|
|
2025-10-13 15:26:31 -03:00
|
|
|
"code.capytal.cc/capytal/comicverse/model"
|
|
|
|
|
"code.capytal.cc/loreddev/x/tinyssert"
|
2025-06-10 15:02:18 -03:00
|
|
|
"github.com/google/uuid"
|
2025-06-09 19:24:45 -03:00
|
|
|
)
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
type Publication struct {
|
2025-06-09 19:24:45 -03:00
|
|
|
baseRepostiory
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
func NewPublication(ctx context.Context, db *sql.DB, log *slog.Logger, assert tinyssert.Assertions) (*Publication, error) {
|
2025-06-09 19:24:45 -03:00
|
|
|
b := newBaseRepostiory(ctx, db, log, assert)
|
|
|
|
|
|
|
|
|
|
tx, err := db.BeginTx(ctx, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = tx.ExecContext(ctx, `
|
2025-11-18 13:19:55 -03:00
|
|
|
CREATE TABLE IF NOT EXISTS publications (
|
2025-06-26 19:11:12 -03:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
|
|
|
title TEXT NOT NULL,
|
|
|
|
|
created_at TEXT NOT NULL,
|
|
|
|
|
updated_at TEXT NOT NULL
|
2025-06-09 19:24:45 -03:00
|
|
|
)`)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := tx.Commit(); err != nil {
|
2025-11-18 13:19:55 -03:00
|
|
|
return nil, errors.Join(errors.New("unable to create publication tables"), err)
|
2025-06-09 19:24:45 -03:00
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
return &Publication{baseRepostiory: b}, nil
|
2025-06-09 19:24:45 -03:00
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
func (repo Publication) Create(p model.Publication) error {
|
2025-06-09 19:24:59 -03:00
|
|
|
repo.assert.NotNil(repo.db)
|
|
|
|
|
repo.assert.NotNil(repo.ctx)
|
|
|
|
|
repo.assert.NotNil(repo.ctx)
|
|
|
|
|
|
|
|
|
|
if err := p.Validate(); err != nil {
|
2025-06-10 15:01:25 -03:00
|
|
|
return errors.Join(ErrInvalidInput, err)
|
2025-06-09 19:24:59 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx, err := repo.db.BeginTx(repo.ctx, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Join(ErrDatabaseConn, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
q := `
|
2025-11-18 13:19:55 -03:00
|
|
|
INSERT INTO publications (id, title, created_at, updated_at)
|
2025-06-10 15:04:13 -03:00
|
|
|
VALUES (:id, :title, :created_at, :updated_at)
|
2025-06-09 19:24:59 -03:00
|
|
|
`
|
|
|
|
|
|
2025-06-10 15:08:32 -03:00
|
|
|
log := repo.log.With(slog.String("id", p.ID.String()), slog.String("query", q))
|
2025-11-18 13:19:55 -03:00
|
|
|
log.DebugContext(repo.ctx, "Inserting new publication")
|
2025-06-09 19:24:59 -03:00
|
|
|
|
|
|
|
|
_, err = tx.ExecContext(repo.ctx, q,
|
2025-06-10 15:08:32 -03:00
|
|
|
sql.Named("id", p.ID),
|
2025-06-09 19:24:59 -03:00
|
|
|
sql.Named("title", p.Title),
|
|
|
|
|
sql.Named("created_at", p.DateCreated.Format(dateFormat)),
|
|
|
|
|
sql.Named("updated_at", p.DateUpdated.Format(dateFormat)),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
2025-11-18 13:19:55 -03:00
|
|
|
log.ErrorContext(repo.ctx, "Failed to insert publication", slog.String("error", err.Error()))
|
2025-06-09 19:24:59 -03:00
|
|
|
return errors.Join(ErrExecuteQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
|
|
|
log.ErrorContext(repo.ctx, "Failed to commit transaction", slog.String("error", err.Error()))
|
|
|
|
|
return errors.Join(ErrCommitQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
func (repo Publication) GetByID(publicationID uuid.UUID) (publication model.Publication, err error) {
|
2025-06-26 19:11:13 -03:00
|
|
|
repo.assert.NotNil(repo.db)
|
|
|
|
|
repo.assert.NotNil(repo.ctx)
|
|
|
|
|
repo.assert.NotNil(repo.log)
|
|
|
|
|
|
|
|
|
|
q := `
|
2025-11-18 13:19:55 -03:00
|
|
|
SELECT id, title, created_at, updated_at FROM publications
|
2025-06-26 19:11:13 -03:00
|
|
|
WHERE id = :id
|
|
|
|
|
`
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
log := repo.log.With(slog.String("query", q), slog.String("id", publicationID.String()))
|
|
|
|
|
log.DebugContext(repo.ctx, "Getting publication by ID")
|
2025-06-26 19:11:13 -03:00
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
row := repo.db.QueryRowContext(repo.ctx, q, sql.Named("id", publicationID))
|
2025-06-26 19:11:13 -03:00
|
|
|
|
|
|
|
|
var id uuid.UUID
|
|
|
|
|
var title string
|
|
|
|
|
var dateCreatedStr, dateUpdatedStr string
|
|
|
|
|
|
|
|
|
|
err = row.Scan(&id, &title, &dateCreatedStr, &dateUpdatedStr)
|
|
|
|
|
if err != nil {
|
2025-11-18 13:19:55 -03:00
|
|
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
|
|
|
|
return model.Publication{}, errors.Join(ErrInvalidOutput, err)
|
2025-06-26 19:11:13 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dateCreated, err := time.Parse(dateFormat, dateCreatedStr)
|
|
|
|
|
if err != nil {
|
2025-11-18 13:19:55 -03:00
|
|
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
|
|
|
|
return model.Publication{}, errors.Join(ErrInvalidOutput, err)
|
2025-06-26 19:11:13 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dateUpdated, err := time.Parse(dateFormat, dateUpdatedStr)
|
|
|
|
|
if err != nil {
|
2025-11-18 13:19:55 -03:00
|
|
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
|
|
|
|
return model.Publication{}, errors.Join(ErrInvalidOutput, err)
|
2025-06-26 19:11:13 -03:00
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
return model.Publication{
|
2025-06-26 19:11:13 -03:00
|
|
|
ID: id,
|
|
|
|
|
Title: title,
|
|
|
|
|
DateCreated: dateCreated,
|
|
|
|
|
DateUpdated: dateUpdated,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
func (repo Publication) GetByIDs(ids []uuid.UUID) (publications []model.Publication, err error) {
|
2025-06-26 19:11:13 -03:00
|
|
|
repo.assert.NotNil(repo.db)
|
|
|
|
|
repo.assert.NotNil(repo.ctx)
|
|
|
|
|
repo.assert.NotNil(repo.log)
|
|
|
|
|
|
|
|
|
|
// Begin tx so we don't read rows as they are being updated
|
|
|
|
|
tx, err := repo.db.BeginTx(repo.ctx, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Join(ErrDatabaseConn, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c := make([]string, len(ids))
|
|
|
|
|
for i, id := range ids {
|
|
|
|
|
c[i] = fmt.Sprintf("id = '%s'", id.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
q := fmt.Sprintf(`
|
2025-11-18 13:19:55 -03:00
|
|
|
SELECT id, title, created_at, updated_at FROM publications
|
2025-06-26 19:11:13 -03:00
|
|
|
WHERE %s
|
|
|
|
|
`, strings.Join(c, " OR "))
|
|
|
|
|
|
|
|
|
|
log := repo.log.With(slog.String("query", q))
|
2025-11-18 13:19:55 -03:00
|
|
|
log.DebugContext(repo.ctx, "Getting publications by IDs")
|
2025-06-26 19:11:13 -03:00
|
|
|
|
|
|
|
|
rows, err := tx.QueryContext(repo.ctx, q)
|
|
|
|
|
if err != nil {
|
2025-11-18 13:19:55 -03:00
|
|
|
log.ErrorContext(repo.ctx, "Failed to get publications by IDs", slog.String("error", err.Error()))
|
2025-06-26 19:11:13 -03:00
|
|
|
return nil, errors.Join(ErrExecuteQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
|
err = rows.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
err = errors.Join(ErrCloseConn, err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
ps := []model.Publication{}
|
2025-06-26 19:11:13 -03:00
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var id uuid.UUID
|
|
|
|
|
var title string
|
|
|
|
|
var dateCreatedStr, dateUpdatedStr string
|
|
|
|
|
|
|
|
|
|
err := rows.Scan(&id, &title, &dateCreatedStr, &dateUpdatedStr)
|
|
|
|
|
if err != nil {
|
2025-11-18 13:19:55 -03:00
|
|
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
2025-06-26 19:11:13 -03:00
|
|
|
return nil, errors.Join(ErrInvalidOutput, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dateCreated, err := time.Parse(dateFormat, dateCreatedStr)
|
|
|
|
|
if err != nil {
|
2025-11-18 13:19:55 -03:00
|
|
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
2025-06-26 19:11:13 -03:00
|
|
|
return nil, errors.Join(ErrInvalidOutput, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dateUpdated, err := time.Parse(dateFormat, dateUpdatedStr)
|
|
|
|
|
if err != nil {
|
2025-11-18 13:19:55 -03:00
|
|
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
2025-06-26 19:11:13 -03:00
|
|
|
return nil, errors.Join(ErrInvalidOutput, err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
ps = append(ps, model.Publication{
|
2025-06-26 19:11:13 -03:00
|
|
|
ID: id,
|
|
|
|
|
Title: title,
|
|
|
|
|
DateCreated: dateCreated,
|
|
|
|
|
DateUpdated: dateUpdated,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
|
|
|
log.ErrorContext(repo.ctx, "Failed to commit transaction", slog.String("error", err.Error()))
|
|
|
|
|
return nil, errors.Join(ErrCommitQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ps, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
func (repo Publication) Update(p model.Publication) error {
|
2025-06-09 19:25:10 -03:00
|
|
|
repo.assert.NotNil(repo.db)
|
|
|
|
|
repo.assert.NotNil(repo.ctx)
|
|
|
|
|
repo.assert.NotNil(repo.ctx)
|
|
|
|
|
|
|
|
|
|
if err := p.Validate(); err != nil {
|
2025-06-10 15:01:25 -03:00
|
|
|
return errors.Join(ErrInvalidInput, err)
|
2025-06-09 19:25:10 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx, err := repo.db.BeginTx(repo.ctx, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Join(ErrDatabaseConn, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
q := `
|
2025-11-18 13:19:55 -03:00
|
|
|
UPDATE publications
|
2025-06-09 19:25:10 -03:00
|
|
|
SET title = :title
|
|
|
|
|
updated_at = :updated_at
|
2025-06-10 15:04:13 -03:00
|
|
|
WHERE id = :id
|
2025-06-09 19:25:10 -03:00
|
|
|
`
|
|
|
|
|
|
2025-06-10 15:08:32 -03:00
|
|
|
log := repo.log.With(slog.String("id", p.ID.String()), slog.String("query", q))
|
2025-11-18 13:19:55 -03:00
|
|
|
log.DebugContext(repo.ctx, "Updating publication")
|
2025-06-09 19:25:10 -03:00
|
|
|
|
|
|
|
|
_, err = tx.ExecContext(repo.ctx, q,
|
|
|
|
|
sql.Named("title", p.Title),
|
|
|
|
|
sql.Named("updated_at", p.DateUpdated.Format(dateFormat)),
|
2025-06-10 15:04:13 -03:00
|
|
|
sql.Named("id", p.ID),
|
2025-06-09 19:25:10 -03:00
|
|
|
)
|
|
|
|
|
if err != nil {
|
2025-11-18 13:19:55 -03:00
|
|
|
log.ErrorContext(repo.ctx, "Failed to insert publication", slog.String("error", err.Error()))
|
2025-06-09 19:25:10 -03:00
|
|
|
return errors.Join(ErrExecuteQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
|
|
|
log.ErrorContext(repo.ctx, "Failed to commit transaction", slog.String("error", err.Error()))
|
|
|
|
|
return errors.Join(ErrCommitQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:19:55 -03:00
|
|
|
func (repo Publication) DeleteByID(id uuid.UUID) error {
|
2025-06-09 19:25:20 -03:00
|
|
|
repo.assert.NotNil(repo.db)
|
|
|
|
|
repo.assert.NotNil(repo.ctx)
|
|
|
|
|
repo.assert.NotNil(repo.ctx)
|
|
|
|
|
|
|
|
|
|
tx, err := repo.db.BeginTx(repo.ctx, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
q := `
|
2025-11-18 13:19:55 -03:00
|
|
|
DELETE FROM publications WHERE id = :id
|
2025-06-09 19:25:20 -03:00
|
|
|
`
|
|
|
|
|
|
2025-06-10 15:02:18 -03:00
|
|
|
log := repo.log.With(slog.String("id", id.String()), slog.String("query", q))
|
2025-11-18 13:19:55 -03:00
|
|
|
log.DebugContext(repo.ctx, "Deleting publication")
|
2025-06-09 19:25:20 -03:00
|
|
|
|
2025-06-10 15:02:18 -03:00
|
|
|
_, err = tx.ExecContext(repo.ctx, q, sql.Named("id", id))
|
2025-06-09 19:25:20 -03:00
|
|
|
if err != nil {
|
2025-11-18 13:19:55 -03:00
|
|
|
log.ErrorContext(repo.ctx, "Failed to delete publication", slog.String("error", err.Error()))
|
2025-06-09 19:25:20 -03:00
|
|
|
return errors.Join(ErrExecuteQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
|
|
|
log.ErrorContext(repo.ctx, "Failed to commit transaction", slog.String("error", err.Error()))
|
|
|
|
|
return errors.Join(ErrCommitQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|