229 lines
5.7 KiB
Go
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,
|
|
}
|
|
}
|