feat: implement db helpers and postgres pool/transactor
- Add is_blocked to core.users (002_core_tables.sql) - Add is_active to activity.sessions for soft deletes (005_activity_tables.sql) - Implement UserRepo: List, GetByID, GetByName, Create, Update, Delete - Implement MimeRepo: List, GetByID, GetByName - Implement SessionRepo: Create, GetByTokenHash, ListByUser, UpdateLastActivity, Delete, DeleteByUserID - Session deletes are soft (SET is_active = false); is_active is a SQL-only filter, not mapped to the domain type Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"tanabata/backend/internal/domain"
|
||||
"tanabata/backend/internal/port"
|
||||
)
|
||||
|
||||
type mimeRow struct {
|
||||
ID int16 `db:"id"`
|
||||
Name string `db:"name"`
|
||||
Extension string `db:"extension"`
|
||||
}
|
||||
|
||||
func toMIMEType(r mimeRow) domain.MIMEType {
|
||||
return domain.MIMEType{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Extension: r.Extension,
|
||||
}
|
||||
}
|
||||
|
||||
// MimeRepo implements port.MimeRepo using PostgreSQL.
|
||||
type MimeRepo struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
// NewMimeRepo creates a MimeRepo backed by pool.
|
||||
func NewMimeRepo(pool *pgxpool.Pool) *MimeRepo {
|
||||
return &MimeRepo{pool: pool}
|
||||
}
|
||||
|
||||
var _ port.MimeRepo = (*MimeRepo)(nil)
|
||||
|
||||
func (r *MimeRepo) List(ctx context.Context) ([]domain.MIMEType, error) {
|
||||
const sql = `SELECT id, name, extension FROM core.mime_types ORDER BY name`
|
||||
|
||||
q := connOrTx(ctx, r.pool)
|
||||
rows, err := q.Query(ctx, sql)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("MimeRepo.List: %w", err)
|
||||
}
|
||||
collected, err := pgx.CollectRows(rows, pgx.RowToStructByName[mimeRow])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("MimeRepo.List scan: %w", err)
|
||||
}
|
||||
|
||||
result := make([]domain.MIMEType, len(collected))
|
||||
for i, row := range collected {
|
||||
result[i] = toMIMEType(row)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *MimeRepo) GetByID(ctx context.Context, id int16) (*domain.MIMEType, error) {
|
||||
const sql = `SELECT id, name, extension FROM core.mime_types WHERE id = $1`
|
||||
|
||||
q := connOrTx(ctx, r.pool)
|
||||
rows, err := q.Query(ctx, sql, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("MimeRepo.GetByID: %w", err)
|
||||
}
|
||||
row, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[mimeRow])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, domain.ErrNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("MimeRepo.GetByID scan: %w", err)
|
||||
}
|
||||
m := toMIMEType(row)
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (r *MimeRepo) GetByName(ctx context.Context, name string) (*domain.MIMEType, error) {
|
||||
const sql = `SELECT id, name, extension FROM core.mime_types WHERE name = $1`
|
||||
|
||||
q := connOrTx(ctx, r.pool)
|
||||
rows, err := q.Query(ctx, sql, name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("MimeRepo.GetByName: %w", err)
|
||||
}
|
||||
row, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[mimeRow])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, domain.ErrUnsupportedMIME
|
||||
}
|
||||
return nil, fmt.Errorf("MimeRepo.GetByName scan: %w", err)
|
||||
}
|
||||
m := toMIMEType(row)
|
||||
return &m, nil
|
||||
}
|
||||
Reference in New Issue
Block a user