2025-06-26 19:11:11 -03:00
|
|
|
package repository
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"database/sql"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"log/slog"
|
|
|
|
|
"time"
|
|
|
|
|
|
2025-10-13 15:26:31 -03:00
|
|
|
"code.capytal.cc/capytal/comicverse/model"
|
|
|
|
|
"code.capytal.cc/loreddev/x/tinyssert"
|
2025-06-26 19:11:11 -03:00
|
|
|
"github.com/google/uuid"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Permissions struct {
|
|
|
|
|
baseRepostiory
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Must be initiated after [User] and [Project]
|
|
|
|
|
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 project_permissions (
|
|
|
|
|
project_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(project_id, user_id)
|
|
|
|
|
FOREIGN KEY(project_id)
|
|
|
|
|
REFERENCES projects (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 project tables"), err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &Permissions{baseRepostiory: b}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (repo Permissions) Create(project, 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 project_permissions (project_id, user_id, permissions_value, _permissions_text, created_at, updated_at)
|
|
|
|
|
VALUES (:project_id, :user_id, :permissions_value, :permissions_text, :created_at, :updated_at)
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
|
|
log := repo.log.With(slog.String("project_id", project.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 project permissions")
|
|
|
|
|
|
|
|
|
|
_, err = tx.ExecContext(repo.ctx, q,
|
|
|
|
|
sql.Named("project_id", project),
|
|
|
|
|
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 project 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(project 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 project_permissions
|
|
|
|
|
WHERE project_id = :project_id
|
|
|
|
|
AND user_id = :user_id
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
log := repo.log.With(slog.String("projcet_id", project.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("project_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 project_id-to-permissions map containing all projects 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 project_id, permissions_value FROM project_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 project uuid.UUID
|
|
|
|
|
var permissions model.Permissions
|
|
|
|
|
|
|
|
|
|
err := rows.Scan(&project, &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[project] = 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(project, 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 project_permissions
|
|
|
|
|
SET permissions_value = :permissions_value
|
|
|
|
|
_permissions_text = :permissions_text
|
|
|
|
|
updated_at = :updated_at
|
|
|
|
|
WHERE project_uuid = :project_uuid
|
|
|
|
|
AND user_uuid = :user_uuid
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
log := repo.log.With(slog.String("project_id", project.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 project 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("project_id", project),
|
|
|
|
|
sql.Named("user_id", user),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.ErrorContext(repo.ctx, "Failed to update project 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(project, 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 project_permissions
|
|
|
|
|
WHERE project_id = :project_id
|
|
|
|
|
AND user_id = :user_id
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
log := repo.log.With(slog.String("project_id", project.String()),
|
|
|
|
|
slog.String("user_id", user.String()),
|
|
|
|
|
slog.String("query", q))
|
|
|
|
|
log.DebugContext(repo.ctx, "Deleting project permissions")
|
|
|
|
|
|
|
|
|
|
_, err = tx.ExecContext(repo.ctx, q,
|
|
|
|
|
sql.Named("project_id", project),
|
|
|
|
|
sql.Named("user_id", user),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.ErrorContext(repo.ctx, "Failed to delete project 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
|
|
|
|
|
}
|