refactor(10.2-01): nest framework packages under modules
- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
This commit is contained in:
177
modules/cabana/contracts.go
Normal file
177
modules/cabana/contracts.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package cabana
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/bouncer"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// BackendUserRole is the Winter backend_user_roles row.
|
||||
type BackendUserRole struct {
|
||||
ID uint `gorm:"column:id;primaryKey"`
|
||||
Name string `gorm:"column:name"`
|
||||
Code string `gorm:"column:code"`
|
||||
Description string `gorm:"column:description"`
|
||||
Permissions string `gorm:"column:permissions"`
|
||||
IsSystem bool `gorm:"column:is_system"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||
}
|
||||
|
||||
func (BackendUserRole) TableName() string { return "backend_user_roles" }
|
||||
|
||||
// BackendUser is the Winter backend_users row. It is not a frontend user.
|
||||
// Nullable Winter columns are mapped so a copied row round-trips without a
|
||||
// schema transform. TokensValidAfter is the admin reset cutoff, not a Winter column.
|
||||
type BackendUser struct {
|
||||
ID uint `gorm:"column:id;primaryKey"`
|
||||
FirstName string `gorm:"column:first_name"`
|
||||
LastName string `gorm:"column:last_name"`
|
||||
Login string `gorm:"column:login"`
|
||||
Email string `gorm:"column:email"`
|
||||
Password string `gorm:"column:password"`
|
||||
ActivationCode string `gorm:"column:activation_code"`
|
||||
PersistCode string `gorm:"column:persist_code"`
|
||||
ResetPasswordCode string `gorm:"column:reset_password_code"`
|
||||
Permissions string `gorm:"column:permissions"`
|
||||
IsActivated bool `gorm:"column:is_activated"`
|
||||
IsSuperuser bool `gorm:"column:is_superuser"`
|
||||
RoleID *uint `gorm:"column:role_id"`
|
||||
ActivatedAt *time.Time `gorm:"column:activated_at"`
|
||||
LastLogin *time.Time `gorm:"column:last_login"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at"`
|
||||
TokensValidAfter *time.Time `gorm:"column:tokens_valid_after"`
|
||||
Role BackendUserRole
|
||||
}
|
||||
|
||||
func (BackendUser) TableName() string { return "backend_users" }
|
||||
|
||||
// Option is a dropdown choice shared with later schema plans.
|
||||
type Option = pact.Option
|
||||
|
||||
// WritableField is one schema field bound to a model fill key at activation.
|
||||
// FillKey comes from the model column, not from request JSON.
|
||||
type WritableField struct {
|
||||
Name string
|
||||
FillKey string
|
||||
}
|
||||
|
||||
// CompiledController is one admin controller after YAML compilation.
|
||||
type CompiledController struct {
|
||||
PluginID string
|
||||
Controller pact.AdminController
|
||||
List *ListSchema
|
||||
Form *FormSchema
|
||||
Relations map[string]*CompiledRelation
|
||||
Writable []WritableField
|
||||
// FieldRelations are the form's `type: relation` fields keyed by field name.
|
||||
FieldRelations map[string]*CompiledFieldRelation
|
||||
}
|
||||
|
||||
// Registry is the immutable controller map keyed by controller ID.
|
||||
type Registry struct {
|
||||
byID map[string]*CompiledController
|
||||
permissions map[string]pact.Permission
|
||||
roleGrants map[string]map[string]bool
|
||||
navigation []pact.NavigationItem
|
||||
settings map[string]*CompiledSetting
|
||||
}
|
||||
|
||||
// Get returns the compiled controller for a D-09 id (vendor.plugin.controller).
|
||||
func (r *Registry) Get(id string) (*CompiledController, bool) {
|
||||
if r == nil {
|
||||
return nil, false
|
||||
}
|
||||
cc, ok := r.byID[id]
|
||||
return cc, ok && cc != nil
|
||||
}
|
||||
|
||||
// Setting returns one compiled singleton setting by stable code.
|
||||
func (r *Registry) Setting(code string) (*CompiledSetting, bool) {
|
||||
if r == nil {
|
||||
return nil, false
|
||||
}
|
||||
setting, ok := r.settings[code]
|
||||
return setting, ok && setting != nil
|
||||
}
|
||||
|
||||
// Allows reports whether principal satisfies every required permission code.
|
||||
// A nil principal fails. Superusers pass. An empty requirement list allows
|
||||
// any authenticated principal. Grants ending in ".*" match by prefix.
|
||||
func Allows(principal *bouncer.Principal, required []string) bool {
|
||||
if principal == nil {
|
||||
return false
|
||||
}
|
||||
if principal.IsSuperuser || len(required) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, code := range required {
|
||||
if !granted(principal.PermissionGrants, code) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func granted(grants map[string]bool, code string) bool {
|
||||
if grants[code] {
|
||||
return true
|
||||
}
|
||||
for key, on := range grants {
|
||||
if !on || !strings.HasSuffix(key, ".*") {
|
||||
continue
|
||||
}
|
||||
prefix := strings.TrimSuffix(key, "*")
|
||||
if strings.HasPrefix(code, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func requiredOf(ctl pact.AdminController) []string {
|
||||
if p, ok := ctl.(pact.AdminPermissioned); ok && p != nil {
|
||||
return p.RequiredPermissions()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteData writes a D-10 success envelope.
|
||||
func WriteData(w http.ResponseWriter, status int, data, meta any) {
|
||||
if meta == nil {
|
||||
meta = map[string]any{}
|
||||
}
|
||||
writeJSON(w, status, map[string]any{"data": data, "meta": meta})
|
||||
}
|
||||
|
||||
// WriteError writes a D-10 error envelope. details is always an object.
|
||||
func WriteError(w http.ResponseWriter, status int, code, message string) {
|
||||
WriteErrorDetails(w, status, code, message, nil)
|
||||
}
|
||||
|
||||
// WriteErrorDetails writes a D-10 error envelope with field messages.
|
||||
func WriteErrorDetails(w http.ResponseWriter, status int, code, message string, details map[string]any) {
|
||||
if details == nil {
|
||||
details = map[string]any{}
|
||||
}
|
||||
writeJSON(w, status, map[string]any{
|
||||
"error": map[string]any{
|
||||
"code": code,
|
||||
"message": message,
|
||||
"details": details,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, body any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_ = json.NewEncoder(w).Encode(body)
|
||||
}
|
||||
Reference in New Issue
Block a user