Files
comicverse/repository/permission.go

284 lines
8.0 KiB
Go

package repository
import (
"context"
"database/sql"
"errors"
"fmt"
"log/slog"
"time"
"code.capytal.cc/capytal/comicverse/model"
"code.capytal.cc/loreddev/x/tinyssert"
"github.com/google/uuid"
)
type Permissions struct {
baseRepostiory
}
// Must be initiated after [User] and [Publication]
func NewPermissions(
ctx context.Context,
db *sql.DB,
log *slog.Logger,
assert tinyssert.Assertions,
) (*Permissions, error) {
b := newBaseRepostiory(ctx, db, log, assert)
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
q := fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS publication_permissions (
publication_id TEXT NOT NULL,
user_id TEXT NOT NULL,
permissions_value INTEGER NOT NULL DEFAULT '0',
_permissions_text TEXT NOT NULL DEFAULT '', -- For display purposes only, may not always be up-to-date
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY(publication_id, user_id)
FOREIGN KEY(publication_id)
REFERENCES publications (id)
ON DELETE CASCADE
ON UPDATE RESTRICT,
FOREIGN KEY(user_id)
REFERENCES users (id)
ON DELETE CASCADE
ON UPDATE RESTRICT
)
`)
_, err = tx.ExecContext(ctx, q)
if err != nil {
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, errors.Join(errors.New("unable to create publication tables"), err)
}
return &Permissions{baseRepostiory: b}, nil
}
func (repo Permissions) Create(publication, user uuid.UUID, permissions model.Permissions) error {
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 errors.Join(ErrDatabaseConn, err)
}
q := `
INSERT INTO publication_permissions (publication_id, user_id, permissions_value, _permissions_text, created_at, updated_at)
VALUES (:publication_id, :user_id, :permissions_value, :permissions_text, :created_at, :updated_at)
`
now := time.Now()
log := repo.log.With(slog.String("publication_id", publication.String()),
slog.String("user_id", user.String()),
slog.String("permissions", fmt.Sprintf("%d", permissions)),
slog.String("permissions_text", permissions.String()),
slog.String("query", q))
log.DebugContext(repo.ctx, "Inserting new publication permissions")
_, err = tx.ExecContext(repo.ctx, q,
sql.Named("publication_id", publication),
sql.Named("user_id", user),
sql.Named("permissions_value", permissions),
sql.Named("permissions_text", permissions.String()),
sql.Named("created_at", now.Format(dateFormat)),
sql.Named("updated_at", now.Format(dateFormat)),
)
if err != nil {
log.ErrorContext(repo.ctx, "Failed to insert publication permissions", slog.String("error", err.Error()))
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
}
func (repo Permissions) GetByID(publication uuid.UUID, user uuid.UUID) (model.Permissions, error) {
repo.assert.NotNil(repo.db)
repo.assert.NotNil(repo.ctx)
repo.assert.NotNil(repo.log)
q := `
SELECT permissions_value FROM publication_permissions
WHERE publication_id = :publication_id
AND user_id = :user_id
`
log := repo.log.With(slog.String("projcet_id", publication.String()),
slog.String("user_id", user.String()),
slog.String("query", q))
log.DebugContext(repo.ctx, "Getting by ID")
row := repo.db.QueryRowContext(repo.ctx, q,
sql.Named("publication_id", user),
sql.Named("user_id", user))
var p model.Permissions
if err := row.Scan(&p); err != nil {
log.ErrorContext(repo.ctx, "Failed to get permissions by ID", slog.String("error", err.Error()))
return model.Permissions(0), errors.Join(ErrExecuteQuery, err)
}
return p, nil
}
// GetByUserID returns a publication_id-to-permissions map containing all publications and permissions that said userID
// has relation to.
func (repo Permissions) GetByUserID(user uuid.UUID) (permissions map[uuid.UUID]model.Permissions, err error) {
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)
}
q := `
SELECT publication_id, permissions_value FROM publication_permissions
WHERE user_id = :user_id
`
log := repo.log.With(slog.String("user_id", user.String()),
slog.String("query", q))
log.DebugContext(repo.ctx, "Getting by user ID")
rows, err := tx.QueryContext(repo.ctx, q, sql.Named("user_id", user))
if err != nil {
log.ErrorContext(repo.ctx, "Failed to get permissions by user ID", slog.String("error", err.Error()))
return nil, errors.Join(ErrExecuteQuery, err)
}
defer func() {
err = rows.Close()
if err != nil {
err = errors.Join(ErrCloseConn, err)
}
}()
ps := map[uuid.UUID]model.Permissions{}
for rows.Next() {
var publication uuid.UUID
var permissions model.Permissions
err := rows.Scan(&publication, &permissions)
if err != nil {
log.ErrorContext(repo.ctx, "Failed to scan permissions of user id", slog.String("error", err.Error()))
return nil, errors.Join(ErrInvalidOutput, err)
}
ps[publication] = permissions
}
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
}
func (repo Permissions) Update(publication, user uuid.UUID, permissions model.Permissions) error {
repo.assert.NotNil(repo.db)
repo.assert.NotNil(repo.ctx)
repo.assert.NotNil(repo.log)
tx, err := repo.db.BeginTx(repo.ctx, nil)
if err != nil {
return errors.Join(ErrDatabaseConn, err)
}
q := `
UPDATE publication_permissions
SET permissions_value = :permissions_value
_permissions_text = :permissions_text
updated_at = :updated_at
WHERE publication_uuid = :publication_uuid
AND user_uuid = :user_uuid
`
log := repo.log.With(slog.String("publication_id", publication.String()),
slog.String("user_id", user.String()),
slog.String("permissions", fmt.Sprintf("%d", permissions)),
slog.String("permissions_text", permissions.String()),
slog.String("query", q))
log.DebugContext(repo.ctx, "Updating publication permissions")
now := time.Now()
_, err = tx.ExecContext(repo.ctx, q,
sql.Named("permissions_value", permissions),
sql.Named("permissions_text", permissions.String()),
sql.Named("updated_at", now.Format(dateFormat)),
sql.Named("publication_id", publication),
sql.Named("user_id", user),
)
if err != nil {
log.ErrorContext(repo.ctx, "Failed to update publication permissions", slog.String("error", err.Error()))
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
}
func (repo Permissions) Delete(publication, user uuid.UUID) error {
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 := `
DELETE FROM publication_permissions
WHERE publication_id = :publication_id
AND user_id = :user_id
`
log := repo.log.With(slog.String("publication_id", publication.String()),
slog.String("user_id", user.String()),
slog.String("query", q))
log.DebugContext(repo.ctx, "Deleting publication permissions")
_, err = tx.ExecContext(repo.ctx, q,
sql.Named("publication_id", publication),
sql.Named("user_id", user),
)
if err != nil {
log.ErrorContext(repo.ctx, "Failed to delete publication permissions", slog.String("error", err.Error()))
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
}