Files
NianAIGC/backend/internal/administration/model.go

174 lines
4.8 KiB
Go

package administration
import (
"context"
"errors"
"time"
)
type Role string
const (
RoleSuperAdmin Role = "super_admin"
RoleOrganizationAdmin Role = "organization_admin"
RoleUser Role = "user"
)
type Status string
const (
StatusActive Status = "active"
StatusDisabled Status = "disabled"
)
type Actor struct {
ID string
Role Role
OrganizationID string
}
type Account struct {
ID, Phone, DisplayName string
Role Role
OrganizationID string
Status Status
PasswordHash, PasswordSalt string
FailedLoginCount int
LockedUntil *time.Time
SessionVersion int
LastLoginAt *time.Time
LegacySubject string
CreatedAt, UpdatedAt time.Time
}
type Organization struct {
ID, Name string
Status Status
ArchiveOwnerID string
CreatedAt, UpdatedAt time.Time
}
type AccountProjection struct {
ID, Phone, DisplayName string
Role Role
OrganizationID string
Status Status
CreatedAt time.Time
LastLoginAt, LockedUntil *time.Time
}
type OrganizationProjection struct {
ID, Name string
Status Status
}
type AccountFilters struct {
OrganizationID string
Role Role
IncludeDisabled bool
}
type PasswordHash struct{ Hash, Salt string }
type Store interface {
ListAccounts(context.Context, AccountFilters) ([]Account, error)
GetAccount(context.Context, string) (Account, bool, error)
CreateAccount(context.Context, Account) (Account, error)
UpdateAccount(context.Context, Account) (Account, error)
DeleteAccount(context.Context, string, string) error
ListOrganizations(context.Context, bool) ([]Organization, error)
GetOrganization(context.Context, string) (Organization, bool, error)
CreateOrganization(context.Context, Organization) (Organization, error)
UpdateOrganization(context.Context, Organization) (Organization, error)
DeleteOrganization(context.Context, string) error
CountOrganizationMembers(context.Context, string) (int, error)
}
type ErrorKind string
const (
ErrorValidation ErrorKind = "validation"
ErrorForbidden ErrorKind = "forbidden"
ErrorNotFound ErrorKind = "not_found"
ErrorConflict ErrorKind = "conflict"
ErrorInfrastructure ErrorKind = "infrastructure"
)
type Error struct {
Kind ErrorKind
Message string
Err error
}
func (e *Error) Error() string { return e.Message }
func (e *Error) Unwrap() error { return e.Err }
func IsKind(err error, kind ErrorKind) bool {
var target *Error
return errors.As(err, &target) && target.Kind == kind
}
func StatusCode(err error) int {
var target *Error
if !errors.As(err, &target) {
return 500
}
switch target.Kind {
case ErrorValidation:
return 400
case ErrorForbidden:
return 403
case ErrorNotFound:
return 404
case ErrorConflict:
return 409
default:
return 500
}
}
func problem(kind ErrorKind, message string) error { return &Error{Kind: kind, Message: message} }
func infrastructure(operation string, err error) error {
if err == nil {
return nil
}
var typed *Error
if errors.As(err, &typed) {
return err
}
return &Error{Kind: ErrorInfrastructure, Message: operation + ": " + err.Error(), Err: err}
}
type CreateAccountInput struct {
Phone, DisplayName, Password string
Role Role
OrganizationID, LegacySubject string
}
type UpdateAccountInput struct {
DisplayName *string
Role *Role
OrganizationID *string
Status *Status
Password *string
ClearLoginLock bool
}
type UpdateOrganizationInput struct {
Name *string
Status *Status
}
func AuthorizeAccountTarget(actor Actor, target Account) error {
if actor.Role == RoleSuperAdmin {
return nil
}
if actor.Role == RoleOrganizationAdmin && target.Role == RoleUser && actor.OrganizationID != "" && actor.OrganizationID == target.OrganizationID {
return nil
}
return problem(ErrorForbidden, "组织管理员只能管理本组织普通用户。")
}
func requireSuper(actor Actor) error {
if actor.Role != RoleSuperAdmin {
return problem(ErrorForbidden, "需要超级管理员权限。")
}
return nil
}
func validRole(role Role) bool {
return role == RoleSuperAdmin || role == RoleOrganizationAdmin || role == RoleUser
}
func validStatus(status Status) bool { return status == StatusActive || status == StatusDisabled }
func ProjectAccount(a Account) AccountProjection {
return AccountProjection{ID: a.ID, Phone: a.Phone, DisplayName: a.DisplayName, Role: a.Role, OrganizationID: a.OrganizationID, Status: a.Status, CreatedAt: a.CreatedAt, LastLoginAt: a.LastLoginAt, LockedUntil: a.LockedUntil}
}
func ProjectOrganization(o Organization) OrganizationProjection {
return OrganizationProjection{ID: o.ID, Name: o.Name, Status: o.Status}
}