Files
license/internal/subject/repository.go
2026-07-31 16:59:55 +08:00

229 lines
5.7 KiB
Go

package subject
import (
"context"
"errors"
"fmt"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgconn"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type Repository struct {
db *gorm.DB
}
type subjectRow struct {
ID uuid.UUID `gorm:"column:id;type:uuid;primaryKey"`
PlatformName string `gorm:"column:platform_name"`
Workspace string `gorm:"column:workspace"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
LicenceCount int64 `gorm:"column:licence_count;->"`
}
func (subjectRow) TableName() string {
return "authorization_subjects"
}
func NewRepository(db *gorm.DB) (*Repository, error) {
if db == nil {
return nil, ErrInvalidInput
}
return &Repository{db: db}, nil
}
func (r *Repository) Create(ctx context.Context, item Subject) (Subject, error) {
row := rowFromSubject(item)
if err := r.db.WithContext(ctx).Create(&row).Error; err != nil {
return Subject{}, mapDatabaseError(err)
}
return row.subject(), nil
}
func (r *Repository) Get(ctx context.Context, id uuid.UUID) (Subject, error) {
var row subjectRow
err := r.db.WithContext(ctx).
Table("authorization_subjects AS subjects").
Select(`subjects.id, subjects.platform_name, subjects.workspace, subjects.created_at, subjects.updated_at,
(SELECT COUNT(*) FROM licence_issuances WHERE subject_id = subjects.id) AS licence_count`).
Where("subjects.id = ?", id).
Take(&row).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return Subject{}, ErrNotFound
}
if err != nil {
return Subject{}, err
}
return row.subject(), nil
}
func (r *Repository) GetForUpdate(ctx context.Context, tx *gorm.DB, id uuid.UUID) (Subject, error) {
if r == nil || r.db == nil || ctx == nil || tx == nil || id == uuid.Nil {
return Subject{}, ErrInvalidInput
}
row, err := lockSubject(tx.WithContext(ctx), id)
if err != nil {
return Subject{}, err
}
return row.subject(), nil
}
func (r *Repository) List(ctx context.Context, query string, page, pageSize int) ([]Subject, int64, error) {
base := r.db.WithContext(ctx).Table("authorization_subjects AS subjects")
if query != "" {
base = base.Where(
"STRPOS(LOWER(subjects.platform_name), LOWER(?)) > 0 OR STRPOS(LOWER(subjects.workspace), LOWER(?)) > 0",
query,
query,
)
}
var total int64
if err := base.Count(&total).Error; err != nil {
return nil, 0, err
}
var rows []subjectRow
err := base.
Select(`subjects.id, subjects.platform_name, subjects.workspace, subjects.created_at, subjects.updated_at,
(SELECT COUNT(*) FROM licence_issuances WHERE subject_id = subjects.id) AS licence_count`).
Order("subjects.created_at DESC, subjects.id DESC").
Offset((page - 1) * pageSize).
Limit(pageSize).
Find(&rows).Error
if err != nil {
return nil, 0, err
}
items := make([]Subject, len(rows))
for index := range rows {
items[index] = rows[index].subject()
}
return items, total, nil
}
func (r *Repository) UpdateIfUnsigned(ctx context.Context, id uuid.UUID, input UpsertInput) (Subject, error) {
var updated Subject
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
row, err := lockSubject(tx, id)
if err != nil {
return err
}
licenceCount, err := countLicences(tx, id)
if err != nil {
return err
}
if licenceCount != 0 {
return ErrHasLicences
}
row.PlatformName = input.PlatformName
row.Workspace = input.Workspace
row.UpdatedAt = time.Now().UTC()
result := tx.Model(&subjectRow{}).
Where("id = ?", id).
Updates(map[string]any{
"platform_name": row.PlatformName,
"workspace": row.Workspace,
"updated_at": row.UpdatedAt,
})
if result.Error != nil {
return mapDatabaseError(result.Error)
}
if result.RowsAffected != 1 {
return ErrNotFound
}
updated = row.subject()
return nil
})
if err != nil {
return Subject{}, err
}
return updated, nil
}
func (r *Repository) DeleteIfUnsigned(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
row, err := lockSubject(tx, id)
if err != nil {
return err
}
licenceCount, err := countLicences(tx, id)
if err != nil {
return err
}
if licenceCount != 0 {
return ErrHasLicences
}
result := tx.Delete(&row)
if result.Error != nil {
return mapDatabaseError(result.Error)
}
if result.RowsAffected != 1 {
return ErrNotFound
}
return nil
})
}
func lockSubject(tx *gorm.DB, id uuid.UUID) (subjectRow, error) {
var row subjectRow
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", id).Take(&row).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return subjectRow{}, ErrNotFound
}
if err != nil {
return subjectRow{}, err
}
return row, nil
}
func countLicences(tx *gorm.DB, id uuid.UUID) (int64, error) {
var count int64
if err := tx.Table("licence_issuances").Where("subject_id = ?", id).Count(&count).Error; err != nil {
return 0, fmt.Errorf("统计授权对象的许可证记录: %w", err)
}
return count, nil
}
func mapDatabaseError(err error) error {
var postgresError *pgconn.PgError
if errors.As(err, &postgresError) {
switch postgresError.Code {
case "23505":
return ErrDuplicate
case "23503":
return ErrHasLicences
}
}
return err
}
func rowFromSubject(item Subject) subjectRow {
return subjectRow{
ID: item.ID,
PlatformName: item.PlatformName,
Workspace: item.Workspace,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
LicenceCount: item.LicenceCount,
}
}
func (r subjectRow) subject() Subject {
return Subject{
ID: r.ID,
PlatformName: r.PlatformName,
Workspace: r.Workspace,
CreatedAt: r.CreatedAt,
UpdatedAt: r.UpdatedAt,
LicenceCount: r.LicenceCount,
}
}