feat: add administration domain core
This commit is contained in:
1 parent
065dcd955a
commit
cea2751dc5
9 files changed
+1135
No files matched your search
@@ -0,0 +1,173 @@
|
||||
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}
|
||||
}
|
||||
Reference in new issue
Block a user