Documentation
¶
Index ¶
- Constants
- Variables
- func GenerateTokenPreview(plainToken string) string
- type ActionType
- type ActivityAction
- type ActivityChange
- type ActivityData
- type ActivityDraftCounts
- type ActivityEvent
- type ActivityEventList
- type ActivityImport
- type ActivityImportError
- type ActivityInput
- type ActivityPublish
- type ActivityPublishCounts
- type ActivityResource
- type ActivityRollback
- type ActivityRollbackScope
- type ActivityTruncate
- type AdminPermission
- type Agent
- type AgentList
- type DraftChangeType
- type Namespace
- type NamespaceList
- type Page
- type PageDraft
- type PageDraftList
- type PageList
- type PageSnapshot
- type Project
- type ProjectList
- type Redirect
- type RedirectDraft
- type RedirectDraftList
- type RedirectList
- type RedirectSnapshot
- type ResourcePermission
- type ResourceType
- type Role
- type RoleList
- type RoleType
- type SectionType
- type SubjectPermissions
- type Token
- type TokenList
- type User
- type UserList
- type UserRole
Constants ¶
const ( ActivityResourceRedirect ActivityResource = "REDIRECT" ActivityResourcePage ActivityResource = "PAGE" ActivityResourceProject ActivityResource = "PROJECT" // ActivityResourceActivity is the journal itself, which can be cleared. ActivityResourceActivity ActivityResource = "ACTIVITY" ActivityActionCreate ActivityAction = "CREATE" ActivityActionUpdate ActivityAction = "UPDATE" ActivityActionDelete ActivityAction = "DELETE" ActivityActionImport ActivityAction = "IMPORT" ActivityActionRollback ActivityAction = "ROLLBACK" ActivityActionPublish ActivityAction = "PUBLISH" // ActivityActionTruncate wipes every entry of a resource in a project. ActivityActionTruncate ActivityAction = "TRUNCATE" )
const ( AdminSectionUsers SectionType = "users" AdminSectionRoles SectionType = "roles" AdminSectionProjects SectionType = "projects" AdminSectionNamespaces SectionType = "namespaces" AdminSectionTokens SectionType = "tokens" AdminSectionAll SectionType = "*" ActionRead ActionType = "read" ActionWrite ActionType = "write" ActionAll ActionType = "*" ResourceTypeRedirect ResourceType = "redirect" ResourceTypePage ResourceType = "page" ResourceTypeAgent ResourceType = "agent" ResourceTypeAll ResourceType = "*" ResourceTypeAny ResourceType = "any" )
const ( TokenPrefix = "flecto_" TokenNameMaxLength = 300 TokenPreviewChars = 4 // Number of characters to show at start and end of preview )
const ActivityActorSystem = "system"
ActivityActorSystem is recorded when no authenticated subject is in the context, for instance a CLI command.
const ActivityImportErrorSampleMax = 20
ActivityImportErrorSampleMax bounds the errors kept in an import payload. The payload keeps an exact ErrorCount, so a truncated sample is detectable with errorCount > len(errorSample).
const (
ColumnNamespaceCode = "namespace_code"
)
const (
ColumnProjectCode = "project_code"
)
Variables ¶
var ActivityEventSortableColumns = map[string]string{
"occurredAt": "occurred_at",
"resource": "resource",
"action": "action",
"actor": "actor",
}
var AgentSortableColumns = map[string]string{
"name": "name",
"status": "status",
"type": "type",
"createAt": "created_at",
"updatedAt": "updated_at",
"lastHitAt": "last_hit_at",
}
var ErrActivityDataReadOnly = errors.New("activity data is read-only")
ErrActivityDataReadOnly is returned when a client tries to supply an activity payload.
var NamespaceSortableColumns = map[string]string{ "id": "id", "namespace_code": ColumnNamespaceCode, "name": "name", "createdAt": "created_at", "updatedAt": "updated_at", }
var PageSortableColumns = map[string]string{
"path": "path",
"contentType": "content_type",
"type": "type",
"updatedAt": "updated_at",
}
var ProjectSortableColumns = map[string]string{ "id": "id", "namespace_code": ColumnNamespaceCode, "code": ColumnProjectCode, "name": "name", "createdAt": "created_at", "updatedAt": "updated_at", }
var RedirectSortableColumns = map[string]string{
"source": "source",
"target": "target",
"type": "type",
"status": "status",
"updatedAt": "updated_at",
}
var RoleSortableColumns = map[string]string{
"id": "id",
"code": "code",
"type": "type",
"createdAt": "created_at",
"updatedAt": "updated_at",
}
var TokenSortableColumns = map[string]string{
"id": "id",
"name": "name",
"createdAt": "created_at",
"updatedAt": "updated_at",
"expiresAt": "expires_at",
}
var UserSortableColumns = map[string]string{
"id": "id",
"username": "username",
"lastname": "lastname",
"firstname": "firstname",
"createdAt": "created_at",
"updatedAt": "updated_at",
}
var ValidRoleNameRegex = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
Functions ¶
func GenerateTokenPreview ¶
GenerateTokenPreview creates a preview string like "flecto_abcd...wxyz" from the full token
Types ¶
type ActionType ¶
type ActionType string
type ActivityAction ¶ added in v0.2.0
type ActivityAction string
ActivityAction is what was done to the resource. The pair (ActivityResource, ActivityAction) determines the shape of the event payload, see activity_payload.go.
type ActivityChange ¶ added in v0.2.0
type ActivityChange[T any] struct { Before *T `json:"before,omitempty"` After *T `json:"after,omitempty"` }
ActivityChange carries the before and after of a single-entry change. Before is nil on a creation, After is nil on a deletion.
type ActivityData ¶ added in v0.2.0
type ActivityData json.RawMessage
ActivityData is a raw JSON payload written by the server only. Its shape is given by the (Resource, Action) pair of the event carrying it, and the front-end picks a renderer from that same pair.
func (ActivityData) MarshalGQL ¶ added in v0.2.0
func (d ActivityData) MarshalGQL(w io.Writer)
MarshalGQL writes the payload straight into the response: it already holds JSON produced server-side.
func (ActivityData) MarshalJSON ¶ added in v0.2.0
func (d ActivityData) MarshalJSON() ([]byte, error)
MarshalJSON emits the payload as-is. Without it the underlying []byte would be base64 encoded.
func (*ActivityData) Scan ¶ added in v0.2.0
func (d *ActivityData) Scan(value any) error
func (*ActivityData) UnmarshalGQL ¶ added in v0.2.0
func (d *ActivityData) UnmarshalGQL(any) error
UnmarshalGQL always fails: activity payloads are never provided by clients.
func (*ActivityData) UnmarshalJSON ¶ added in v0.2.0
func (d *ActivityData) UnmarshalJSON(data []byte) error
type ActivityDraftCounts ¶ added in v0.2.0
type ActivityDraftCounts struct {
Create int64 `json:"create"`
Update int64 `json:"update"`
Delete int64 `json:"delete"`
}
ActivityDraftCounts breaks discarded drafts down by change type.
func (*ActivityDraftCounts) IsEmpty ¶ added in v0.2.0
func (c *ActivityDraftCounts) IsEmpty() bool
IsEmpty reports whether nothing was discarded, in which case the rollback is a no-op and deserves no journal entry.
type ActivityEvent ¶ added in v0.2.0
type ActivityEvent struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
NamespaceCode string `json:"-" gorm:"size:50;not null;index:idx_activity_events_ns_proj,priority:1"`
ProjectCode string `json:"-" gorm:"size:50;not null;index:idx_activity_events_ns_proj,priority:2"`
Resource ActivityResource `json:"resource" gorm:"size:20;not null"`
Action ActivityAction `json:"action" gorm:"size:20;not null"`
// Actor is a snapshot: the username for a user session, the token name for an
// API token. It is deliberately not a foreign key, so the trail stays readable
// after the user is deleted or renamed. UserID is set to NULL by the database
// when the user is deleted.
UserID *int64 `json:"userID" gorm:"index:idx_activity_events_user"`
Actor string `json:"actor" gorm:"size:300;not null"`
AuthType appTypes.AuthType `json:"authType" gorm:"size:20"`
// ResourceID is the redirect or page id when the event targets a single entry,
// nil for aggregated events (import, project rollback, publish).
ResourceID *int64 `json:"resourceID" gorm:"index:idx_activity_events_resource"`
Data ActivityData `json:"data" gorm:"type:longtext"`
OccurredAt time.Time `json:"occurredAt" gorm:"type:timestamp;not null"`
}
ActivityEvent records one user action on a project. Rows are immutable: they are never updated, only inserted and eventually purged.
type ActivityEventList ¶ added in v0.2.0
type ActivityEventList = commonTypes.PaginatedResult[ActivityEvent]
type ActivityImport ¶ added in v0.2.0
type ActivityImport struct {
Filename string `json:"filename,omitempty"`
Overwrite bool `json:"overwrite"`
TotalLines int `json:"totalLines"`
Imported int `json:"imported"`
Skipped int `json:"skipped"`
ErrorCount int `json:"errorCount"`
ErrorSample []ActivityImportError `json:"errorSample,omitempty"`
}
ActivityImport summarises a redirect import as a single event, whatever the size of the imported file.
type ActivityImportError ¶ added in v0.2.0
type ActivityInput ¶ added in v0.2.0
type ActivityInput struct {
NamespaceCode string
ProjectCode string
Resource ActivityResource
Action ActivityAction
ResourceID *int64
// Data is the payload, marshalled to JSON when recorded. Its type must match
// the (Resource, Action) pair, see the payload types below.
Data any
}
ActivityInput describes an event to record. The actor and the timestamp are filled in by the activity service from the request context.
It lives in model rather than service so that the generated service mocks stay free of any import back into service, which would make the service tests cycle.
type ActivityPublish ¶ added in v0.2.0
type ActivityPublish struct {
Version int `json:"version"`
Redirects ActivityPublishCounts `json:"redirects"`
Pages ActivityPublishCounts `json:"pages"`
}
ActivityPublish summarises a publish: the version it produced and what it moved.
type ActivityPublishCounts ¶ added in v0.2.0
type ActivityResource ¶ added in v0.2.0
type ActivityResource string
ActivityResource is what an activity event is about.
type ActivityRollback ¶ added in v0.2.0
type ActivityRollback[T any] struct { Scope ActivityRollbackScope `json:"scope"` ChangeType *DraftChangeType `json:"changeType,omitempty"` Entry *T `json:"entry,omitempty"` Discarded *ActivityDraftCounts `json:"discarded,omitempty"` }
ActivityRollback describes discarded pending changes. SINGLE fills ChangeType and Entry, PROJECT fills Discarded.
type ActivityRollbackScope ¶ added in v0.2.0
type ActivityRollbackScope string
ActivityRollbackScope tells a rollback of the whole project apart from the cancellation of one pending change.
const ( ActivityRollbackScopeSingle ActivityRollbackScope = "SINGLE" ActivityRollbackScopeProject ActivityRollbackScope = "PROJECT" )
type ActivityTruncate ¶ added in v0.2.0
type ActivityTruncate struct {
Published int64 `json:"published"`
Drafts int64 `json:"drafts"`
Version int `json:"version,omitempty"`
}
ActivityTruncate summarises wiping a resource of a project. Version is the project version the wipe produced, since removing every redirect or page has to be published for the agents to stop serving what was deleted.
type AdminPermission ¶
type AdminPermission struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
Section SectionType `json:"section" gorm:"size:100;not null;index:idx_admin_perm_section"`
Action ActionType `json:"action" gorm:"size:50;not null"`
RoleID int64
Role Role `json:"role,omitempty"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
}
func (AdminPermission) TableName ¶
func (AdminPermission) TableName() string
type Agent ¶
type Agent struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
NamespaceCode string `json:"-" gorm:"size:50;index:idx_agents_namespace_project"`
ProjectCode string `json:"-" gorm:"size:50;index:idx_agents_namespace_project"`
Project *Project `json:"project" gorm:"foreignKey:NamespaceCode,ProjectCode;references:NamespaceCode,ProjectCode;"`
commonTypes.Agent
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp"`
LastHitAt time.Time `json:"lastHitAt" gorm:"type:timestamp"`
}
type AgentList ¶
type AgentList = commonTypes.PaginatedResult[Agent]
type DraftChangeType ¶
type DraftChangeType string
const ( DraftChangeTypeCreate DraftChangeType = "CREATE" DraftChangeTypeUpdate DraftChangeType = "UPDATE" DraftChangeTypeDelete DraftChangeType = "DELETE" DraftChangeTypePublished DraftChangeType = "PUBLISHED" )
type Namespace ¶
type Namespace struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
NamespaceCode string `json:"namespace_code" gorm:"size:50;uniqueIndex:idx_namespace_namespace_code;" validate:"required,code"`
Name string `json:"name" validate:"required"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp"`
}
type NamespaceList ¶
type NamespaceList = types.PaginatedResult[Namespace]
type Page ¶
type Page struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
NamespaceCode string `` /* 154-byte string literal not displayed */
ProjectCode string `` /* 154-byte string literal not displayed */
Project *Project `json:"project" gorm:"foreignKey:NamespaceCode,ProjectCode;references:NamespaceCode,ProjectCode;"`
// Same reason as on Redirect: the agent endpoint filters on is_published.
IsPublished *bool `json:"is_published" gorm:"default:false;not null;index:idx_pages_namespace_project,priority:3"`
PublishedAt time.Time `json:"publishedAt" gorm:"type:timestamp"`
ContentSize int64 `json:"contentSize" gorm:"default:0;not null"`
*commonTypes.Page
PageDraft *PageDraft `json:"draft" gorm:"foreignKey:OldPageID;references:ID"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp;index:idx_pages_ns_proj_created,priority:3,sort:desc"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp;index:idx_pages_ns_proj_updated,priority:3,sort:desc"`
}
type PageDraft ¶
type PageDraft struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
NamespaceCode string `json:"-" gorm:"size:50;index:idx_page_drafts_namespace_project"`
ProjectCode string `json:"-" gorm:"size:50;index:idx_page_drafts_namespace_project"`
Project *Project `json:"project" gorm:"foreignKey:NamespaceCode,ProjectCode;references:NamespaceCode,ProjectCode;"`
ChangeType DraftChangeType `json:"changeType" gorm:"size:50;" validate:"required"`
OldPageID *int64 `json:"-" gorm:"index:idx_page_drafts_old_page_id"`
OldPage *Page `json:"oldPage" gorm:"foreignKey:OldPageID;"`
ContentSize int64 `json:"contentSize" gorm:"default:0;not null"`
NewPage *commonTypes.Page `gorm:"embedded;embeddedPrefix:new_"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp"`
}
type PageDraftList ¶
type PageDraftList = commonTypes.PaginatedResult[PageDraft]
type PageList ¶
type PageList = commonTypes.PaginatedResult[Page]
type PageSnapshot ¶ added in v0.2.0
type PageSnapshot struct {
Type commonTypes.PageType `json:"type"`
Path string `json:"path"`
ContentType commonTypes.PageContentType `json:"contentType"`
ContentSize int64 `json:"contentSize"`
}
PageSnapshot is the activity projection of a page. The page content is deliberately left out: it can reach page.size_limit (1MB by default), which has no place in a journal kept for months.
func NewPageSnapshot ¶ added in v0.2.0
func NewPageSnapshot(page *commonTypes.Page, contentSize int64) *PageSnapshot
NewPageSnapshot projects a page for the activity trail, nil in, nil out.
type Project ¶
type Project struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
ProjectCode string `json:"code" gorm:"size:50;uniqueIndex:idx_project_namespace" validate:"required,code"`
NamespaceCode string `json:"-" gorm:"size:50;uniqueIndex:idx_project_namespace;index:idx_namespace"`
Namespace *Namespace `json:"namespace" gorm:"foreignKey:NamespaceCode;references:NamespaceCode;"`
Name string `json:"name" validate:"required"`
Version int `json:"version" gorm:"default:1"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"UpdatedAt" gorm:"type:timestamp"`
PublishedAt time.Time `json:"publishedAt" gorm:"type:timestamp"`
}
type ProjectList ¶
type ProjectList = types.PaginatedResult[Project]
type Redirect ¶
type Redirect struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
NamespaceCode string `` /* 166-byte string literal not displayed */
ProjectCode string `` /* 166-byte string literal not displayed */
Project *Project `json:"project" gorm:"foreignKey:NamespaceCode,ProjectCode;references:NamespaceCode,ProjectCode;"`
// is_published is part of idx_redirects_namespace_project rather than sitting in
// its own index: the agent endpoint filters on (namespace, project, is_published),
// and without it every row of the project has to be read to test the flag.
IsPublished *bool `json:"is_published" gorm:"default:false;not null;index:idx_redirects_namespace_project,priority:3"`
PublishedAt time.Time `json:"publishedAt" gorm:"type:timestamp"`
*commonTypes.Redirect
RedirectDraft *RedirectDraft `json:"draft" gorm:"foreignKey:OldRedirectID;references:ID"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp;index:idx_redirects_ns_proj_created,priority:3,sort:desc"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp;index:idx_redirects_ns_proj_updated,priority:3,sort:desc"`
}
type RedirectDraft ¶
type RedirectDraft struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
NamespaceCode string `json:"-" gorm:"size:50;index:idx_redirect_drafts_namespace_project"`
ProjectCode string `json:"-" gorm:"size:50;index:idx_redirect_drafts_namespace_project"`
Project *Project `json:"project" gorm:"foreignKey:NamespaceCode,ProjectCode;references:NamespaceCode,ProjectCode;"`
ChangeType DraftChangeType `json:"changeType" gorm:"size:50;" validate:"required"`
OldRedirectID *int64 `json:"-" gorm:"index:idx_redirect_drafts_old_redirect_id"`
OldRedirect *Redirect `json:"oldRedirect" gorm:"foreignKey:OldRedirectID;"`
NewRedirect *commonTypes.Redirect `gorm:"embedded;embeddedPrefix:new_"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp"`
}
type RedirectDraftList ¶
type RedirectDraftList = commonTypes.PaginatedResult[RedirectDraft]
type RedirectList ¶
type RedirectList = commonTypes.PaginatedResult[Redirect]
type RedirectSnapshot ¶ added in v0.2.0
type RedirectSnapshot struct {
Type commonTypes.RedirectType `json:"type"`
Source string `json:"source"`
Target string `json:"target"`
Status commonTypes.RedirectStatus `json:"status"`
}
RedirectSnapshot is the activity projection of a redirect.
func NewRedirectSnapshot ¶ added in v0.2.0
func NewRedirectSnapshot(redirect *commonTypes.Redirect) *RedirectSnapshot
NewRedirectSnapshot projects a redirect for the activity trail, nil in, nil out.
type ResourcePermission ¶
type ResourcePermission struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
Namespace string `json:"namespace" gorm:"size:50;not null;index:idx_res_perm_namespace"`
Project string `json:"project" gorm:"size:50;index:idx_res_perm_project"`
Resource ResourceType `json:"resource" gorm:"size:50;not null"`
Action ActionType `json:"action" gorm:"size:50;not null"`
RoleID int64
Role Role `json:"role,omitempty"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
}
func (ResourcePermission) TableName ¶
func (ResourcePermission) TableName() string
type ResourceType ¶
type ResourceType string
type Role ¶
type Role struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
Code string `json:"code" gorm:"uniqueIndex:idx_role_code_type;size:100;not null" validate:"required,code"`
Type RoleType `json:"type" gorm:"uniqueIndex:idx_role_code_type;size:100;not null"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp"`
Users []User `json:"users,omitempty" gorm:"many2many:user_roles;"`
Resources []ResourcePermission `json:"resources,omitempty" gorm:"foreignKey:RoleID;constraint:OnDelete:CASCADE;"`
Admin []AdminPermission `json:"admin,omitempty" gorm:"foreignKey:RoleID;constraint:OnDelete:CASCADE;"`
}
type RoleList ¶
type RoleList = types.PaginatedResult[Role]
type SectionType ¶
type SectionType string
type SubjectPermissions ¶
type SubjectPermissions struct {
Resources []ResourcePermission `json:"resources,omitempty"`
Admin []AdminPermission `json:"admin,omitempty"`
}
func (*SubjectPermissions) Append ¶
func (s *SubjectPermissions) Append(permission *SubjectPermissions)
type Token ¶
type Token struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
Name string `json:"name" gorm:"uniqueIndex;size:300;not null" validate:"required,max=300"`
TokenHash string `json:"-" gorm:"uniqueIndex;size:64;not null"`
TokenPreview string `json:"tokenPreview" gorm:"size:30;not null"` // e.g., "flecto_abcd...wxyz"
ExpiresAt *time.Time `json:"expiresAt" gorm:"type:timestamp"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp"`
}
func (*Token) GetRoleCode ¶
GetRoleCode returns the role code for this token's personal role
type TokenList ¶
type TokenList = types.PaginatedResult[Token]
type User ¶
type User struct {
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
Username string `json:"username" gorm:"unique;size:100;not null" validate:"required,username"`
Password string `json:"-" gorm:"size:255"`
Lastname string `json:"lastname" validate:"required"`
Firstname string `json:"firstname" validate:"required"`
Active *bool `json:"active" gorm:"default:true;not null"`
RefreshTokenHash string `json:"-" gorm:"size:255"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp"`
}
func (*User) HasPassword ¶
HasPassword returns true if the user can use basic auth
type UserList ¶
type UserList = types.PaginatedResult[User]
type UserRole ¶
type UserRole struct {
UserID int64 `json:"userId" gorm:"primaryKey"`
RoleID int64 `json:"roleId" gorm:"primaryKey"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
User User `json:"user" gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE;"`
Role Role `json:"role" gorm:"foreignKey:RoleID;constraint:OnDelete:CASCADE;"`
}