Documentation
¶
Overview ¶
Package ticket is an Arandu module: internal support tickets, for the customers of a multi-tenant application and for the platform team that answers them.
A ticket is a conversation between a user of a tenant -- a space -- and the team. The customer lists their own tickets, opens one, reads the conversation, replies, closes and reopens. The team lists every space's tickets, reads the conversation with its internal notes, answers, writes notes the customer never sees, and changes a ticket's status, priority and assignee. The two sides are separate: each has its own controllers, its own screens and its own prefix, and the application mounts each where its own session is valid.
The files are laid out by role rather than by layer, so the whole package reads top to bottom:
module.go -> registration, routes and the contract with the framework config.go -> what the application passes in, and the route table contracts.go -> what the application implements: accounts, directory, notifier model.go -> the entities, their own rules, and what they may answer with policy.go -> who may do what, for a customer and for the team service.go -> the customer's use cases, and Model access after authorization team.go -> the team's use cases contact.go -> opening a ticket from the application's contact form compose.go -> storing a ticket, a message and its files attachments.go -> checking and keeping files on the application's Disk grants.go -> the three places a Grant is issued without a signed-in customer activity.go -> what is recorded in arandu-activitylog, and what is told handlers.go -> the controllers of both sides pages.go -> what the screens are handed migrations.go -> the schema views.go -> the files the application takes ownership of
An application registers it explicitly. There is no service provider, no container and no discovery: the wiring is the lines somebody wrote, and reading them is how they learn what the application is made of.
Index ¶
- Constants
- Variables
- func Actions() []security.Action
- func AttachmentTypes() []string
- func Lines(locale string) translation.Lines
- func Locales() []string
- func PriorityVariant(key string) string
- func Prose(message MessageRow) template.HTML
- func PublishedPaths() []string
- func StatusVariant(key string) string
- func TeamActions() []security.Action
- func ViewNames() []string
- func ViewPackages() []string
- type Account
- type AccountResolver
- type Attachment
- type AttachmentCollection
- func (c AttachmentCollection) Contains(key any) bool
- func (c AttachmentCollection) Except(keys ...any) AttachmentCollection
- func (c AttachmentCollection) Find(key any) *Attachment
- func (c AttachmentCollection) FindOrFail(key any) (*Attachment, error)
- func (c AttachmentCollection) Fresh(ctx context.Context, g auth.Grant, with ...string) (AttachmentCollection, error)
- func (c AttachmentCollection) Load(ctx context.Context, g auth.Grant, relations ...string) error
- func (c AttachmentCollection) LoadCount(ctx context.Context, g auth.Grant, relations ...string) error
- func (c AttachmentCollection) LoadMissing(ctx context.Context, g auth.Grant, relations ...string) error
- func (c AttachmentCollection) ModelKeys() []any
- func (c AttachmentCollection) Only(keys ...any) AttachmentCollection
- func (c AttachmentCollection) Pluck(column string) []any
- func (c AttachmentCollection) Rows() model.Rows
- func (c AttachmentCollection) ToArray() []map[string]any
- func (c AttachmentCollection) Unique() AttachmentCollection
- type AttachmentQuery
- func (q *AttachmentQuery) AddSelect(columns ...any) *AttachmentQuery
- func (q *AttachmentQuery) Avg(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *AttachmentQuery) Base() *model.Builder
- func (q *AttachmentQuery) Chunk(ctx context.Context, g auth.Grant, count int, ...) error
- func (q *AttachmentQuery) Clone() *AttachmentQuery
- func (q *AttachmentQuery) Count(ctx context.Context, g auth.Grant, columns ...any) (int64, error)
- func (q *AttachmentQuery) Create(ctx context.Context, g auth.Grant, attributes map[string]any) (*Attachment, error)
- func (q *AttachmentQuery) Cursor(ctx context.Context, g auth.Grant) iter.Seq2[*Attachment, error]
- func (q *AttachmentQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, ...) (AttachmentCollection, *pagination.CursorPage, error)
- func (q *AttachmentQuery) Decrement(ctx context.Context, g auth.Grant, column string, amount any, ...) (int64, error)
- func (q *AttachmentQuery) Delete(ctx context.Context, g auth.Grant) (int64, error)
- func (q *AttachmentQuery) DoesntExist(ctx context.Context, g auth.Grant) (bool, error)
- func (q *AttachmentQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *AttachmentQuery
- func (q *AttachmentQuery) Each(ctx context.Context, g auth.Grant, count int, ...) error
- func (q *AttachmentQuery) Exists(ctx context.Context, g auth.Grant) (bool, error)
- func (q *AttachmentQuery) Find(ctx context.Context, g auth.Grant, id any, columns ...any) (*Attachment, error)
- func (q *AttachmentQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (AttachmentCollection, error)
- func (q *AttachmentQuery) FindOrFail(ctx context.Context, g auth.Grant, id any, columns ...any) (*Attachment, error)
- func (q *AttachmentQuery) FindOrNew(ctx context.Context, g auth.Grant, id any, columns ...any) (*Attachment, error)
- func (q *AttachmentQuery) First(ctx context.Context, g auth.Grant, columns ...any) (*Attachment, error)
- func (q *AttachmentQuery) FirstOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Attachment, error)
- func (q *AttachmentQuery) FirstOrFail(ctx context.Context, g auth.Grant, columns ...any) (*Attachment, error)
- func (q *AttachmentQuery) FirstOrNew(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Attachment, error)
- func (q *AttachmentQuery) FirstWhere(ctx context.Context, g auth.Grant, column any, args ...any) (*Attachment, error)
- func (q *AttachmentQuery) ForPage(page, perPage int) *AttachmentQuery
- func (q *AttachmentQuery) ForceCreate(ctx context.Context, g auth.Grant, attributes map[string]any) (*Attachment, error)
- func (q *AttachmentQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (AttachmentCollection, error)
- func (q *AttachmentQuery) GroupBy(groups ...any) *AttachmentQuery
- func (q *AttachmentQuery) Has(relation, operator string, count int, boolean string, ...) *AttachmentQuery
- func (q *AttachmentQuery) Increment(ctx context.Context, g auth.Grant, column string, amount any, ...) (int64, error)
- func (q *AttachmentQuery) Insert(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)
- func (q *AttachmentQuery) InsertOrIgnore(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)
- func (q *AttachmentQuery) Join(table any, first any, args ...any) *AttachmentQuery
- func (q *AttachmentQuery) Latest(column ...string) *AttachmentQuery
- func (q *AttachmentQuery) Limit(value int) *AttachmentQuery
- func (q *AttachmentQuery) Max(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *AttachmentQuery) Min(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *AttachmentQuery) New() (*Attachment, error)
- func (q *AttachmentQuery) Offset(value int) *AttachmentQuery
- func (q *AttachmentQuery) Oldest(column ...string) *AttachmentQuery
- func (q *AttachmentQuery) OrDoesntHave(relation string) *AttachmentQuery
- func (q *AttachmentQuery) OrHas(relation, operator string, count int) *AttachmentQuery
- func (q *AttachmentQuery) OrWhere(column any, args ...any) *AttachmentQuery
- func (q *AttachmentQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *AttachmentQuery
- func (q *AttachmentQuery) OrWhereHas(relation string, callback func(*query.Builder)) *AttachmentQuery
- func (q *AttachmentQuery) OrWhereNot(column any, args ...any) *AttachmentQuery
- func (q *AttachmentQuery) OrWhereRelation(relation string, column any, args ...any) *AttachmentQuery
- func (q *AttachmentQuery) OrderBy(column any, direction ...string) *AttachmentQuery
- func (q *AttachmentQuery) OrderByDesc(column any) *AttachmentQuery
- func (q *AttachmentQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, ...) (AttachmentCollection, *pagination.LengthAwarePage, error)
- func (q *AttachmentQuery) Pluck(ctx context.Context, g auth.Grant, column string) ([]any, error)
- func (q *AttachmentQuery) Select(columns ...any) *AttachmentQuery
- func (q *AttachmentQuery) SelectRaw(expression string, bindings ...any) *AttachmentQuery
- func (q *AttachmentQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, ...) (AttachmentCollection, *pagination.Page, error)
- func (q *AttachmentQuery) Sole(ctx context.Context, g auth.Grant, columns ...any) (*Attachment, error)
- func (q *AttachmentQuery) SoleValue(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *AttachmentQuery) Sum(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *AttachmentQuery) Touch(ctx context.Context, g auth.Grant, column ...string) (int64, error)
- func (q *AttachmentQuery) Update(ctx context.Context, g auth.Grant, values map[string]any) (int64, error)
- func (q *AttachmentQuery) UpdateOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Attachment, error)
- func (q *AttachmentQuery) Upsert(ctx context.Context, g auth.Grant, values []map[string]any, ...) (int64, error)
- func (q *AttachmentQuery) Value(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *AttachmentQuery) ValueOrFail(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *AttachmentQuery) Where(column any, args ...any) *AttachmentQuery
- func (q *AttachmentQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *AttachmentQuery
- func (q *AttachmentQuery) WhereBetween(column any, from, to any) *AttachmentQuery
- func (q *AttachmentQuery) WhereColumn(first any, args ...any) *AttachmentQuery
- func (q *AttachmentQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *AttachmentQuery
- func (q *AttachmentQuery) WhereExists(callback func(*query.Builder)) *AttachmentQuery
- func (q *AttachmentQuery) WhereHas(relation string, callback func(*query.Builder)) *AttachmentQuery
- func (q *AttachmentQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *AttachmentQuery
- func (q *AttachmentQuery) WhereIn(column any, values []any) *AttachmentQuery
- func (q *AttachmentQuery) WhereKey(id any) *AttachmentQuery
- func (q *AttachmentQuery) WhereNot(column any, args ...any) *AttachmentQuery
- func (q *AttachmentQuery) WhereNotExists(callback func(*query.Builder)) *AttachmentQuery
- func (q *AttachmentQuery) WhereNotIn(column any, values []any) *AttachmentQuery
- func (q *AttachmentQuery) WhereNotNull(columns ...any) *AttachmentQuery
- func (q *AttachmentQuery) WhereNull(columns ...any) *AttachmentQuery
- func (q *AttachmentQuery) WhereRelation(relation string, column any, args ...any) *AttachmentQuery
- func (q *AttachmentQuery) With(relations ...string) *AttachmentQuery
- func (q *AttachmentQuery) WithAvg(relation, column string) *AttachmentQuery
- func (q *AttachmentQuery) WithCount(relations ...string) *AttachmentQuery
- func (q *AttachmentQuery) WithExists(relation string) *AttachmentQuery
- func (q *AttachmentQuery) WithMax(relation, column string) *AttachmentQuery
- func (q *AttachmentQuery) WithMin(relation, column string) *AttachmentQuery
- func (q *AttachmentQuery) WithSum(relation, column string) *AttachmentQuery
- func (q *AttachmentQuery) Without(relations ...string) *AttachmentQuery
- func (q *AttachmentQuery) WithoutGlobalScope(identifier string) *AttachmentQuery
- func (q *AttachmentQuery) WithoutGlobalScopes(identifiers ...string) *AttachmentQuery
- type AttachmentView
- type ChromeRequest
- type Config
- type ContactInput
- type ContactResult
- type Conversation
- type Counter
- type CounterCollection
- func (c CounterCollection) Contains(key any) bool
- func (c CounterCollection) Except(keys ...any) CounterCollection
- func (c CounterCollection) Find(key any) *Counter
- func (c CounterCollection) FindOrFail(key any) (*Counter, error)
- func (c CounterCollection) Fresh(ctx context.Context, g auth.Grant, with ...string) (CounterCollection, error)
- func (c CounterCollection) Load(ctx context.Context, g auth.Grant, relations ...string) error
- func (c CounterCollection) LoadCount(ctx context.Context, g auth.Grant, relations ...string) error
- func (c CounterCollection) LoadMissing(ctx context.Context, g auth.Grant, relations ...string) error
- func (c CounterCollection) ModelKeys() []any
- func (c CounterCollection) Only(keys ...any) CounterCollection
- func (c CounterCollection) Pluck(column string) []any
- func (c CounterCollection) Rows() model.Rows
- func (c CounterCollection) ToArray() []map[string]any
- func (c CounterCollection) Unique() CounterCollection
- type CounterQuery
- func (q *CounterQuery) AddSelect(columns ...any) *CounterQuery
- func (q *CounterQuery) Avg(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *CounterQuery) Base() *model.Builder
- func (q *CounterQuery) Chunk(ctx context.Context, g auth.Grant, count int, ...) error
- func (q *CounterQuery) Clone() *CounterQuery
- func (q *CounterQuery) Count(ctx context.Context, g auth.Grant, columns ...any) (int64, error)
- func (q *CounterQuery) Create(ctx context.Context, g auth.Grant, attributes map[string]any) (*Counter, error)
- func (q *CounterQuery) Cursor(ctx context.Context, g auth.Grant) iter.Seq2[*Counter, error]
- func (q *CounterQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, ...) (CounterCollection, *pagination.CursorPage, error)
- func (q *CounterQuery) Decrement(ctx context.Context, g auth.Grant, column string, amount any, ...) (int64, error)
- func (q *CounterQuery) Delete(ctx context.Context, g auth.Grant) (int64, error)
- func (q *CounterQuery) DoesntExist(ctx context.Context, g auth.Grant) (bool, error)
- func (q *CounterQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *CounterQuery
- func (q *CounterQuery) Each(ctx context.Context, g auth.Grant, count int, ...) error
- func (q *CounterQuery) Exists(ctx context.Context, g auth.Grant) (bool, error)
- func (q *CounterQuery) Find(ctx context.Context, g auth.Grant, id any, columns ...any) (*Counter, error)
- func (q *CounterQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (CounterCollection, error)
- func (q *CounterQuery) FindOrFail(ctx context.Context, g auth.Grant, id any, columns ...any) (*Counter, error)
- func (q *CounterQuery) FindOrNew(ctx context.Context, g auth.Grant, id any, columns ...any) (*Counter, error)
- func (q *CounterQuery) First(ctx context.Context, g auth.Grant, columns ...any) (*Counter, error)
- func (q *CounterQuery) FirstOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Counter, error)
- func (q *CounterQuery) FirstOrFail(ctx context.Context, g auth.Grant, columns ...any) (*Counter, error)
- func (q *CounterQuery) FirstOrNew(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Counter, error)
- func (q *CounterQuery) FirstWhere(ctx context.Context, g auth.Grant, column any, args ...any) (*Counter, error)
- func (q *CounterQuery) ForPage(page, perPage int) *CounterQuery
- func (q *CounterQuery) ForceCreate(ctx context.Context, g auth.Grant, attributes map[string]any) (*Counter, error)
- func (q *CounterQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (CounterCollection, error)
- func (q *CounterQuery) GroupBy(groups ...any) *CounterQuery
- func (q *CounterQuery) Has(relation, operator string, count int, boolean string, ...) *CounterQuery
- func (q *CounterQuery) Increment(ctx context.Context, g auth.Grant, column string, amount any, ...) (int64, error)
- func (q *CounterQuery) Insert(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)
- func (q *CounterQuery) InsertOrIgnore(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)
- func (q *CounterQuery) Join(table any, first any, args ...any) *CounterQuery
- func (q *CounterQuery) Latest(column ...string) *CounterQuery
- func (q *CounterQuery) Limit(value int) *CounterQuery
- func (q *CounterQuery) Max(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *CounterQuery) Min(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *CounterQuery) New() (*Counter, error)
- func (q *CounterQuery) Offset(value int) *CounterQuery
- func (q *CounterQuery) Oldest(column ...string) *CounterQuery
- func (q *CounterQuery) OrDoesntHave(relation string) *CounterQuery
- func (q *CounterQuery) OrHas(relation, operator string, count int) *CounterQuery
- func (q *CounterQuery) OrWhere(column any, args ...any) *CounterQuery
- func (q *CounterQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *CounterQuery
- func (q *CounterQuery) OrWhereHas(relation string, callback func(*query.Builder)) *CounterQuery
- func (q *CounterQuery) OrWhereNot(column any, args ...any) *CounterQuery
- func (q *CounterQuery) OrWhereRelation(relation string, column any, args ...any) *CounterQuery
- func (q *CounterQuery) OrderBy(column any, direction ...string) *CounterQuery
- func (q *CounterQuery) OrderByDesc(column any) *CounterQuery
- func (q *CounterQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, ...) (CounterCollection, *pagination.LengthAwarePage, error)
- func (q *CounterQuery) Pluck(ctx context.Context, g auth.Grant, column string) ([]any, error)
- func (q *CounterQuery) Select(columns ...any) *CounterQuery
- func (q *CounterQuery) SelectRaw(expression string, bindings ...any) *CounterQuery
- func (q *CounterQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, ...) (CounterCollection, *pagination.Page, error)
- func (q *CounterQuery) Sole(ctx context.Context, g auth.Grant, columns ...any) (*Counter, error)
- func (q *CounterQuery) SoleValue(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *CounterQuery) Sum(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *CounterQuery) Touch(ctx context.Context, g auth.Grant, column ...string) (int64, error)
- func (q *CounterQuery) Update(ctx context.Context, g auth.Grant, values map[string]any) (int64, error)
- func (q *CounterQuery) UpdateOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Counter, error)
- func (q *CounterQuery) Upsert(ctx context.Context, g auth.Grant, values []map[string]any, ...) (int64, error)
- func (q *CounterQuery) Value(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *CounterQuery) ValueOrFail(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *CounterQuery) Where(column any, args ...any) *CounterQuery
- func (q *CounterQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *CounterQuery
- func (q *CounterQuery) WhereBetween(column any, from, to any) *CounterQuery
- func (q *CounterQuery) WhereColumn(first any, args ...any) *CounterQuery
- func (q *CounterQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *CounterQuery
- func (q *CounterQuery) WhereExists(callback func(*query.Builder)) *CounterQuery
- func (q *CounterQuery) WhereHas(relation string, callback func(*query.Builder)) *CounterQuery
- func (q *CounterQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *CounterQuery
- func (q *CounterQuery) WhereIn(column any, values []any) *CounterQuery
- func (q *CounterQuery) WhereKey(id any) *CounterQuery
- func (q *CounterQuery) WhereNot(column any, args ...any) *CounterQuery
- func (q *CounterQuery) WhereNotExists(callback func(*query.Builder)) *CounterQuery
- func (q *CounterQuery) WhereNotIn(column any, values []any) *CounterQuery
- func (q *CounterQuery) WhereNotNull(columns ...any) *CounterQuery
- func (q *CounterQuery) WhereNull(columns ...any) *CounterQuery
- func (q *CounterQuery) WhereRelation(relation string, column any, args ...any) *CounterQuery
- func (q *CounterQuery) With(relations ...string) *CounterQuery
- func (q *CounterQuery) WithAvg(relation, column string) *CounterQuery
- func (q *CounterQuery) WithCount(relations ...string) *CounterQuery
- func (q *CounterQuery) WithExists(relation string) *CounterQuery
- func (q *CounterQuery) WithMax(relation, column string) *CounterQuery
- func (q *CounterQuery) WithMin(relation, column string) *CounterQuery
- func (q *CounterQuery) WithSum(relation, column string) *CounterQuery
- func (q *CounterQuery) Without(relations ...string) *CounterQuery
- func (q *CounterQuery) WithoutGlobalScope(identifier string) *CounterQuery
- func (q *CounterQuery) WithoutGlobalScopes(identifiers ...string) *CounterQuery
- type Directory
- type Event
- type EventKind
- type FileRow
- type Flag
- type FormState
- type IndexPageData
- type Labels
- type ListQuery
- type Message
- type MessageCollection
- func (c MessageCollection) Contains(key any) bool
- func (c MessageCollection) Except(keys ...any) MessageCollection
- func (c MessageCollection) Find(key any) *Message
- func (c MessageCollection) FindOrFail(key any) (*Message, error)
- func (c MessageCollection) Fresh(ctx context.Context, g auth.Grant, with ...string) (MessageCollection, error)
- func (c MessageCollection) Load(ctx context.Context, g auth.Grant, relations ...string) error
- func (c MessageCollection) LoadCount(ctx context.Context, g auth.Grant, relations ...string) error
- func (c MessageCollection) LoadMissing(ctx context.Context, g auth.Grant, relations ...string) error
- func (c MessageCollection) ModelKeys() []any
- func (c MessageCollection) Only(keys ...any) MessageCollection
- func (c MessageCollection) Pluck(column string) []any
- func (c MessageCollection) Rows() model.Rows
- func (c MessageCollection) ToArray() []map[string]any
- func (c MessageCollection) Unique() MessageCollection
- type MessageQuery
- func (q *MessageQuery) AddSelect(columns ...any) *MessageQuery
- func (q *MessageQuery) Avg(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *MessageQuery) Base() *model.Builder
- func (q *MessageQuery) Chunk(ctx context.Context, g auth.Grant, count int, ...) error
- func (q *MessageQuery) Clone() *MessageQuery
- func (q *MessageQuery) Count(ctx context.Context, g auth.Grant, columns ...any) (int64, error)
- func (q *MessageQuery) Create(ctx context.Context, g auth.Grant, attributes map[string]any) (*Message, error)
- func (q *MessageQuery) Cursor(ctx context.Context, g auth.Grant) iter.Seq2[*Message, error]
- func (q *MessageQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, ...) (MessageCollection, *pagination.CursorPage, error)
- func (q *MessageQuery) Decrement(ctx context.Context, g auth.Grant, column string, amount any, ...) (int64, error)
- func (q *MessageQuery) Delete(ctx context.Context, g auth.Grant) (int64, error)
- func (q *MessageQuery) DoesntExist(ctx context.Context, g auth.Grant) (bool, error)
- func (q *MessageQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *MessageQuery
- func (q *MessageQuery) Each(ctx context.Context, g auth.Grant, count int, ...) error
- func (q *MessageQuery) Exists(ctx context.Context, g auth.Grant) (bool, error)
- func (q *MessageQuery) Find(ctx context.Context, g auth.Grant, id any, columns ...any) (*Message, error)
- func (q *MessageQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (MessageCollection, error)
- func (q *MessageQuery) FindOrFail(ctx context.Context, g auth.Grant, id any, columns ...any) (*Message, error)
- func (q *MessageQuery) FindOrNew(ctx context.Context, g auth.Grant, id any, columns ...any) (*Message, error)
- func (q *MessageQuery) First(ctx context.Context, g auth.Grant, columns ...any) (*Message, error)
- func (q *MessageQuery) FirstOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Message, error)
- func (q *MessageQuery) FirstOrFail(ctx context.Context, g auth.Grant, columns ...any) (*Message, error)
- func (q *MessageQuery) FirstOrNew(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Message, error)
- func (q *MessageQuery) FirstWhere(ctx context.Context, g auth.Grant, column any, args ...any) (*Message, error)
- func (q *MessageQuery) ForPage(page, perPage int) *MessageQuery
- func (q *MessageQuery) ForceCreate(ctx context.Context, g auth.Grant, attributes map[string]any) (*Message, error)
- func (q *MessageQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (MessageCollection, error)
- func (q *MessageQuery) GroupBy(groups ...any) *MessageQuery
- func (q *MessageQuery) Has(relation, operator string, count int, boolean string, ...) *MessageQuery
- func (q *MessageQuery) Increment(ctx context.Context, g auth.Grant, column string, amount any, ...) (int64, error)
- func (q *MessageQuery) Insert(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)
- func (q *MessageQuery) InsertOrIgnore(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)
- func (q *MessageQuery) Join(table any, first any, args ...any) *MessageQuery
- func (q *MessageQuery) Latest(column ...string) *MessageQuery
- func (q *MessageQuery) Limit(value int) *MessageQuery
- func (q *MessageQuery) Max(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *MessageQuery) Min(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *MessageQuery) New() (*Message, error)
- func (q *MessageQuery) Offset(value int) *MessageQuery
- func (q *MessageQuery) Oldest(column ...string) *MessageQuery
- func (q *MessageQuery) OrDoesntHave(relation string) *MessageQuery
- func (q *MessageQuery) OrHas(relation, operator string, count int) *MessageQuery
- func (q *MessageQuery) OrWhere(column any, args ...any) *MessageQuery
- func (q *MessageQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *MessageQuery
- func (q *MessageQuery) OrWhereHas(relation string, callback func(*query.Builder)) *MessageQuery
- func (q *MessageQuery) OrWhereNot(column any, args ...any) *MessageQuery
- func (q *MessageQuery) OrWhereRelation(relation string, column any, args ...any) *MessageQuery
- func (q *MessageQuery) OrderBy(column any, direction ...string) *MessageQuery
- func (q *MessageQuery) OrderByDesc(column any) *MessageQuery
- func (q *MessageQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, ...) (MessageCollection, *pagination.LengthAwarePage, error)
- func (q *MessageQuery) Pluck(ctx context.Context, g auth.Grant, column string) ([]any, error)
- func (q *MessageQuery) Select(columns ...any) *MessageQuery
- func (q *MessageQuery) SelectRaw(expression string, bindings ...any) *MessageQuery
- func (q *MessageQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, ...) (MessageCollection, *pagination.Page, error)
- func (q *MessageQuery) Sole(ctx context.Context, g auth.Grant, columns ...any) (*Message, error)
- func (q *MessageQuery) SoleValue(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *MessageQuery) Sum(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *MessageQuery) Touch(ctx context.Context, g auth.Grant, column ...string) (int64, error)
- func (q *MessageQuery) Update(ctx context.Context, g auth.Grant, values map[string]any) (int64, error)
- func (q *MessageQuery) UpdateOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Message, error)
- func (q *MessageQuery) Upsert(ctx context.Context, g auth.Grant, values []map[string]any, ...) (int64, error)
- func (q *MessageQuery) Value(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *MessageQuery) ValueOrFail(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *MessageQuery) Where(column any, args ...any) *MessageQuery
- func (q *MessageQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *MessageQuery
- func (q *MessageQuery) WhereBetween(column any, from, to any) *MessageQuery
- func (q *MessageQuery) WhereColumn(first any, args ...any) *MessageQuery
- func (q *MessageQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *MessageQuery
- func (q *MessageQuery) WhereExists(callback func(*query.Builder)) *MessageQuery
- func (q *MessageQuery) WhereHas(relation string, callback func(*query.Builder)) *MessageQuery
- func (q *MessageQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *MessageQuery
- func (q *MessageQuery) WhereIn(column any, values []any) *MessageQuery
- func (q *MessageQuery) WhereKey(id any) *MessageQuery
- func (q *MessageQuery) WhereNot(column any, args ...any) *MessageQuery
- func (q *MessageQuery) WhereNotExists(callback func(*query.Builder)) *MessageQuery
- func (q *MessageQuery) WhereNotIn(column any, values []any) *MessageQuery
- func (q *MessageQuery) WhereNotNull(columns ...any) *MessageQuery
- func (q *MessageQuery) WhereNull(columns ...any) *MessageQuery
- func (q *MessageQuery) WhereRelation(relation string, column any, args ...any) *MessageQuery
- func (q *MessageQuery) With(relations ...string) *MessageQuery
- func (q *MessageQuery) WithAvg(relation, column string) *MessageQuery
- func (q *MessageQuery) WithCount(relations ...string) *MessageQuery
- func (q *MessageQuery) WithExists(relation string) *MessageQuery
- func (q *MessageQuery) WithMax(relation, column string) *MessageQuery
- func (q *MessageQuery) WithMin(relation, column string) *MessageQuery
- func (q *MessageQuery) WithSum(relation, column string) *MessageQuery
- func (q *MessageQuery) Without(relations ...string) *MessageQuery
- func (q *MessageQuery) WithoutGlobalScope(identifier string) *MessageQuery
- func (q *MessageQuery) WithoutGlobalScopes(identifiers ...string) *MessageQuery
- type MessageRow
- type MessageView
- type Module
- func (m *Module) Boot(context.Context) error
- func (m *Module) CustomerRoutes(r *fhttp.Router)
- func (m *Module) Labels() Labels
- func (m *Module) Migrations() []foundation.Migration
- func (m *Module) Name() string
- func (m *Module) Publishes() []foundation.Publication
- func (m *Module) Routes(r *fhttp.Router)
- func (m *Module) Team() *TeamService
- func (m *Module) TeamRoutes(r *fhttp.Router)
- func (m *Module) Tickets() *TicketService
- func (m *Module) URLs() URLs
- type NewPageData
- type Notifier
- type OpenRequest
- type Origin
- type Page
- type Paths
- type Person
- type Priority
- type ReplyRequest
- type Role
- type Screen
- func (s Screen) All() []string
- func (s Screen) Chrome() any
- func (s Screen) ErrorSummary() []string
- func (s Screen) FieldErrors(name string) []string
- func (s Screen) FilesHint(max int, mb string) string
- func (s Screen) Form() FormState
- func (s Screen) HasErrors() bool
- func (s Screen) LogValue() slog.Value
- func (s Screen) MarshalJSON() ([]byte, error)
- func (s Screen) PriorityOptions() []components.SelectOption
- func (s Screen) SearchLabel(table string) string
- func (s Screen) StatusOptions() []components.SelectOption
- func (s Screen) TableLines() components.DataTableLines
- func (s Screen) When(t time.Time) string
- type ShowPageData
- type Status
- type Summary
- type TeamIndexPageData
- type TeamPolicy
- type TeamQuery
- type TeamService
- func (s *TeamService) Assign(ctx context.Context, actor security.Subject, id, assigneeID string) (*Ticket, error)
- func (s *TeamService) Attachment(ctx context.Context, actor security.Subject, ticketID, attachmentID string) (*Attachment, filesystem.File, error)
- func (s *TeamService) Find(ctx context.Context, actor security.Subject, id string) (Conversation, error)
- func (s *TeamService) List(ctx context.Context, actor security.Subject, q TeamQuery) (Page, error)
- func (s *TeamService) Note(ctx context.Context, actor security.Subject, id string, in ReplyRequest, ...) (*Message, error)
- func (s *TeamService) Reply(ctx context.Context, actor security.Subject, id string, in ReplyRequest, ...) (*Message, error)
- func (s *TeamService) SetPriority(ctx context.Context, actor security.Subject, id string, next Priority) (*Ticket, error)
- func (s *TeamService) SetStatus(ctx context.Context, actor security.Subject, id string, next Status) (*Ticket, error)
- func (s *TeamService) Team(ctx context.Context, actor security.Subject) ([]Person, error)
- type TeamShowPageData
- type Ticket
- func (t *Ticket) AddNote(now time.Time) error
- func (t *Ticket) Assign(id, name string, now time.Time) error
- func (t Ticket) CanClose() bool
- func (t Ticket) CanReopen() bool
- func (t Ticket) CanReply() bool
- func (t *Ticket) Close(now time.Time) error
- func (t Ticket) Code() string
- func (row *Ticket) Fresh(ctx context.Context, g auth.Grant, with ...string) (*Ticket, error)
- func (t Ticket) IsClosed() bool
- func (t *Ticket) ReceiveCustomerReply(now time.Time) error
- func (t *Ticket) ReceiveTeamReply(now time.Time) error
- func (t *Ticket) Reopen(now time.Time) error
- func (row *Ticket) Replicate(except ...string) (*Ticket, error)
- func (t *Ticket) SetPriority(next Priority, now time.Time) error
- func (t *Ticket) SetStatus(next Status, now time.Time) error
- type TicketCollection
- func (c TicketCollection) Contains(key any) bool
- func (c TicketCollection) Except(keys ...any) TicketCollection
- func (c TicketCollection) Find(key any) *Ticket
- func (c TicketCollection) FindOrFail(key any) (*Ticket, error)
- func (c TicketCollection) Fresh(ctx context.Context, g auth.Grant, with ...string) (TicketCollection, error)
- func (c TicketCollection) Load(ctx context.Context, g auth.Grant, relations ...string) error
- func (c TicketCollection) LoadCount(ctx context.Context, g auth.Grant, relations ...string) error
- func (c TicketCollection) LoadMissing(ctx context.Context, g auth.Grant, relations ...string) error
- func (c TicketCollection) ModelKeys() []any
- func (c TicketCollection) Only(keys ...any) TicketCollection
- func (c TicketCollection) Pluck(column string) []any
- func (c TicketCollection) Rows() model.Rows
- func (c TicketCollection) ToArray() []map[string]any
- func (c TicketCollection) Unique() TicketCollection
- type TicketPolicy
- type TicketQuery
- func (q *TicketQuery) AddSelect(columns ...any) *TicketQuery
- func (q *TicketQuery) Avg(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *TicketQuery) Base() *model.Builder
- func (q *TicketQuery) Chunk(ctx context.Context, g auth.Grant, count int, ...) error
- func (q *TicketQuery) Clone() *TicketQuery
- func (q *TicketQuery) Count(ctx context.Context, g auth.Grant, columns ...any) (int64, error)
- func (q *TicketQuery) Create(ctx context.Context, g auth.Grant, attributes map[string]any) (*Ticket, error)
- func (q *TicketQuery) Cursor(ctx context.Context, g auth.Grant) iter.Seq2[*Ticket, error]
- func (q *TicketQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, ...) (TicketCollection, *pagination.CursorPage, error)
- func (q *TicketQuery) Decrement(ctx context.Context, g auth.Grant, column string, amount any, ...) (int64, error)
- func (q *TicketQuery) Delete(ctx context.Context, g auth.Grant) (int64, error)
- func (q *TicketQuery) DoesntExist(ctx context.Context, g auth.Grant) (bool, error)
- func (q *TicketQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *TicketQuery
- func (q *TicketQuery) Each(ctx context.Context, g auth.Grant, count int, ...) error
- func (q *TicketQuery) Exists(ctx context.Context, g auth.Grant) (bool, error)
- func (q *TicketQuery) Find(ctx context.Context, g auth.Grant, id any, columns ...any) (*Ticket, error)
- func (q *TicketQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (TicketCollection, error)
- func (q *TicketQuery) FindOrFail(ctx context.Context, g auth.Grant, id any, columns ...any) (*Ticket, error)
- func (q *TicketQuery) FindOrNew(ctx context.Context, g auth.Grant, id any, columns ...any) (*Ticket, error)
- func (q *TicketQuery) First(ctx context.Context, g auth.Grant, columns ...any) (*Ticket, error)
- func (q *TicketQuery) FirstOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Ticket, error)
- func (q *TicketQuery) FirstOrFail(ctx context.Context, g auth.Grant, columns ...any) (*Ticket, error)
- func (q *TicketQuery) FirstOrNew(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Ticket, error)
- func (q *TicketQuery) FirstWhere(ctx context.Context, g auth.Grant, column any, args ...any) (*Ticket, error)
- func (q *TicketQuery) ForPage(page, perPage int) *TicketQuery
- func (q *TicketQuery) ForceCreate(ctx context.Context, g auth.Grant, attributes map[string]any) (*Ticket, error)
- func (q *TicketQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (TicketCollection, error)
- func (q *TicketQuery) GroupBy(groups ...any) *TicketQuery
- func (q *TicketQuery) Has(relation, operator string, count int, boolean string, ...) *TicketQuery
- func (q *TicketQuery) Increment(ctx context.Context, g auth.Grant, column string, amount any, ...) (int64, error)
- func (q *TicketQuery) Insert(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)
- func (q *TicketQuery) InsertOrIgnore(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)
- func (q *TicketQuery) Join(table any, first any, args ...any) *TicketQuery
- func (q *TicketQuery) Latest(column ...string) *TicketQuery
- func (q *TicketQuery) Limit(value int) *TicketQuery
- func (q *TicketQuery) Max(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *TicketQuery) Min(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *TicketQuery) New() (*Ticket, error)
- func (q *TicketQuery) Offset(value int) *TicketQuery
- func (q *TicketQuery) Oldest(column ...string) *TicketQuery
- func (q *TicketQuery) OrDoesntHave(relation string) *TicketQuery
- func (q *TicketQuery) OrHas(relation, operator string, count int) *TicketQuery
- func (q *TicketQuery) OrWhere(column any, args ...any) *TicketQuery
- func (q *TicketQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *TicketQuery
- func (q *TicketQuery) OrWhereHas(relation string, callback func(*query.Builder)) *TicketQuery
- func (q *TicketQuery) OrWhereNot(column any, args ...any) *TicketQuery
- func (q *TicketQuery) OrWhereRelation(relation string, column any, args ...any) *TicketQuery
- func (q *TicketQuery) OrderBy(column any, direction ...string) *TicketQuery
- func (q *TicketQuery) OrderByDesc(column any) *TicketQuery
- func (q *TicketQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, ...) (TicketCollection, *pagination.LengthAwarePage, error)
- func (q *TicketQuery) Pluck(ctx context.Context, g auth.Grant, column string) ([]any, error)
- func (q *TicketQuery) Select(columns ...any) *TicketQuery
- func (q *TicketQuery) SelectRaw(expression string, bindings ...any) *TicketQuery
- func (q *TicketQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, ...) (TicketCollection, *pagination.Page, error)
- func (q *TicketQuery) Sole(ctx context.Context, g auth.Grant, columns ...any) (*Ticket, error)
- func (q *TicketQuery) SoleValue(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *TicketQuery) Sum(ctx context.Context, g auth.Grant, column any) (any, error)
- func (q *TicketQuery) Touch(ctx context.Context, g auth.Grant, column ...string) (int64, error)
- func (q *TicketQuery) Update(ctx context.Context, g auth.Grant, values map[string]any) (int64, error)
- func (q *TicketQuery) UpdateOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Ticket, error)
- func (q *TicketQuery) Upsert(ctx context.Context, g auth.Grant, values []map[string]any, ...) (int64, error)
- func (q *TicketQuery) Value(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *TicketQuery) ValueOrFail(ctx context.Context, g auth.Grant, column string) (any, error)
- func (q *TicketQuery) Where(column any, args ...any) *TicketQuery
- func (q *TicketQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *TicketQuery
- func (q *TicketQuery) WhereBetween(column any, from, to any) *TicketQuery
- func (q *TicketQuery) WhereColumn(first any, args ...any) *TicketQuery
- func (q *TicketQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *TicketQuery
- func (q *TicketQuery) WhereExists(callback func(*query.Builder)) *TicketQuery
- func (q *TicketQuery) WhereHas(relation string, callback func(*query.Builder)) *TicketQuery
- func (q *TicketQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *TicketQuery
- func (q *TicketQuery) WhereIn(column any, values []any) *TicketQuery
- func (q *TicketQuery) WhereKey(id any) *TicketQuery
- func (q *TicketQuery) WhereNot(column any, args ...any) *TicketQuery
- func (q *TicketQuery) WhereNotExists(callback func(*query.Builder)) *TicketQuery
- func (q *TicketQuery) WhereNotIn(column any, values []any) *TicketQuery
- func (q *TicketQuery) WhereNotNull(columns ...any) *TicketQuery
- func (q *TicketQuery) WhereNull(columns ...any) *TicketQuery
- func (q *TicketQuery) WhereRelation(relation string, column any, args ...any) *TicketQuery
- func (q *TicketQuery) With(relations ...string) *TicketQuery
- func (q *TicketQuery) WithAvg(relation, column string) *TicketQuery
- func (q *TicketQuery) WithCount(relations ...string) *TicketQuery
- func (q *TicketQuery) WithExists(relation string) *TicketQuery
- func (q *TicketQuery) WithMax(relation, column string) *TicketQuery
- func (q *TicketQuery) WithMin(relation, column string) *TicketQuery
- func (q *TicketQuery) WithSum(relation, column string) *TicketQuery
- func (q *TicketQuery) Without(relations ...string) *TicketQuery
- func (q *TicketQuery) WithoutGlobalScope(identifier string) *TicketQuery
- func (q *TicketQuery) WithoutGlobalScopes(identifiers ...string) *TicketQuery
- type TicketRow
- type TicketService
- func (s *TicketService) Attachment(ctx context.Context, actor security.Subject, ticketID, attachmentID string) (*Attachment, filesystem.File, error)
- func (s *TicketService) CanOpen(ctx context.Context, actor security.Subject) error
- func (s *TicketService) Close(ctx context.Context, actor security.Subject, id string) (*Ticket, error)
- func (s *TicketService) Find(ctx context.Context, actor security.Subject, id string) (Conversation, error)
- func (s *TicketService) List(ctx context.Context, actor security.Subject, q ListQuery) (Page, error)
- func (s *TicketService) Open(ctx context.Context, actor security.Subject, in OpenRequest, uploads []Upload) (*Ticket, error)
- func (s *TicketService) OpenFromContact(ctx context.Context, in ContactInput) (ContactResult, error)
- func (s *TicketService) Reopen(ctx context.Context, actor security.Subject, id string) (*Ticket, error)
- func (s *TicketService) Reply(ctx context.Context, actor security.Subject, id string, in ReplyRequest, ...) (*Message, error)
- type URLs
- func (u URLs) Attachment(id, attachment string) string
- func (u URLs) Close(id string) string
- func (u URLs) Index() string
- func (u URLs) New() string
- func (u URLs) Reopen(id string) string
- func (u URLs) Reply(id string) string
- func (u URLs) Show(id string) string
- func (u URLs) TeamAssign(id string) string
- func (u URLs) TeamAttachment(id, attachment string) string
- func (u URLs) TeamIndex() string
- func (u URLs) TeamNote(id string) string
- func (u URLs) TeamPriority(id string) string
- func (u URLs) TeamReply(id string) string
- func (u URLs) TeamShow(id string) string
- func (u URLs) TeamStatus(id string) string
- type Upload
Constants ¶
const ( ActivityTicketOpened = "ticket.opened" ActivityTicketReplied = "ticket.replied" ActivityTicketStatusChanged = "ticket.status_changed" ActivityTicketPriorityChanged = "ticket.priority_changed" ActivityTicketAssigned = "ticket.assigned" ActivityTicketClosed = "ticket.closed" ActivityTicketReopened = "ticket.reopened" ActivityTicketNoteAdded = "ticket.note_added" ActivityAttachmentRead = "ticket.attachment_read" )
The events this package records, one per act that changes something, and one for a file being read. The subject of each entry is the ticket.
No entry carries the text of a message, and the entry of an internal note carries nothing of the note: that an act happened is what the log keeps, and what was said stays in the conversation, where the side that may read it reads it.
const ( // DefaultPrefix is where the customer's routes are mounted when Config // leaves Prefix empty. DefaultPrefix = "/tickets" // DefaultTeamPrefix is where the team's routes are mounted when Config // leaves TeamPrefix empty. DefaultTeamPrefix = "/admin/tickets" // DefaultPageSize is how many rows one page of a list answers with when // Config leaves PageSize at zero. DefaultPageSize = 25 // MaxPageSize is the ceiling PageSize is refused above. A page nobody // bounded is a page that reads the whole table on the day the table is // large. MaxPageSize = 100 // DefaultLocale is the language of the screens, the messages and the // activity descriptions when Config.Locale names none. DefaultLocale = "pt-BR" // FallbackLocale answers every line the locale in use does not have. FallbackLocale = "en" // DefaultContactWindow is how long the same e-mail, subject and body open // one ticket and not two. DefaultContactWindow = 10 * time.Minute // MaxContactWindow is the ceiling ContactWindow is refused above: past a // day the same words are a new request. MaxContactWindow = 24 * time.Hour // DefaultMaxAttachmentBytes is the size of the largest file one message // carries when Config leaves MaxAttachmentBytes at zero. DefaultMaxAttachmentBytes = 5 << 20 // MaxAttachmentBytesLimit is the ceiling MaxAttachmentBytes is refused // above: the file is read into memory to be looked at before it is kept. MaxAttachmentBytesLimit = 25 << 20 // DefaultMaxAttachments is how many files one message carries when Config // leaves MaxAttachments at zero. DefaultMaxAttachments = 5 // MaxAttachmentsLimit is the ceiling MaxAttachments is refused above. MaxAttachmentsLimit = 10 // MaxBodyChars is the longest message body, in characters. Markdown is // rendered in a pass as long as the text, so a field that takes text from // people the application does not trust caps its length. MaxBodyChars = 10000 )
The defaults for the optional settings. They are constants rather than literals inside Config.withDefaults, so the value a reader finds here is the value the package uses.
const ( ViewIndex = "modules.ticket.index" ViewNew = "modules.ticket.form" ViewShow = "modules.ticket.show" ViewTeamIndex = "modules.ticket.admin.index" ViewTeamShow = "modules.ticket.admin.show" )
The screens this package renders, by the name their path gives them.
const ( // TicketCreate is opening a ticket in the subject's own space. TicketCreate security.Action = "ticket.create" // TicketViewOwn is reading the tickets the subject asked for themselves. TicketViewOwn security.Action = "ticket.view_own" // TicketViewSpace is reading every ticket of the subject's space, whoever // asked: for the people who run the space, beside those they run it for. TicketViewSpace security.Action = "ticket.view_space" // TicketReply is adding a message to a ticket the subject can read. TicketReply security.Action = "ticket.reply" // TicketClose is closing a ticket the subject can read, and reopening it. TicketClose security.Action = "ticket.close" // TicketViewAll is the team reading every ticket of every space, internal // notes included. TicketViewAll security.Action = "ticket.view_all" // TicketManage is the team answering a customer and changing a ticket's // status and priority. TicketManage security.Action = "ticket.manage" // TicketAssign is the team giving a ticket to a member of the team. TicketAssign security.Action = "ticket.assign" // TicketNote is the team writing a note only the team reads. TicketNote security.Action = "ticket.note" // TicketOpenFromContact is the application's contact form opening a ticket // for the account an e-mail belongs to. It is not a subject's action: no // person holds it, and OpenFromContact is the only place that issues it. TicketOpenFromContact security.Action = "ticket.open_from_contact" )
The actions of this package. Constants rather than strings at the call site: a typo in an action name would silently authorize nothing, or worse, everything.
They carry the entity in the name because an application registers many packages, and the name of an action shows up in logs and in audit trails where "view" on its own says nothing about what was viewed.
The actions are what an arandu-permission group carries: the application declares them in the catalogue (Actions and TeamActions), an organization or the platform grants them to whom it decides, and arandu-permission fills the subject's actions on every request. Nothing here is decided by a role.
const ActivityLogName = "ticket"
ActivityLogName is the log every entry of this package is recorded in.
const ActivityRefType = "ticket"
ActivityRefType is the kind of subject an entry about a ticket names.
const AssigneeNone = "none"
AssigneeNone is TeamQuery.Assignee for the tickets nobody took.
const DefaultLocation = "America/Sao_Paulo"
DefaultLocation is the timezone the screens write instants in when Config leaves Location nil. The package is Brazilian first; an application elsewhere sets its own.
const PublishCommand = "aru vendor:publish --apply"
PublishCommand is what an application runs to take ownership of the views this package offers.
It is spelled out as a constant so that whatever says it -- the refusal below, or a message an application writes for its own operators -- says one thing. A person who is told two different commands for one job tries both.
The command reads the modules the application registered and writes what each one declares, which is why it is the application's command and not this package's: nothing outside the application knows which modules it holds.
const TranslationGroup = "ticket"
TranslationGroup is the file every key of this package lives in, and the prefix of every key: "ticket.screen.index_title".
Variables ¶
var DefaultPaths = Paths{
New: "/new", Replies: "/replies", Closure: "/closure", Reopening: "/reopening", Attachments: "/attachments",
Notes: "/notes", Status: "/status", Priority: "/priority", Assignee: "/assignee",
}
DefaultPaths are the English segments.
var ErrNoAccount = errors.New("ticket: no account has that e-mail")
ErrNoAccount is what an AccountResolver answers when no account has the e-mail, and what OpenFromContact returns for it: the contact form opens a ticket only for somebody who has an account, and for anybody else the application keeps doing what it did before.
var ErrNotFound = fmt.Errorf("ticket: record not found: %w", model.ErrModelNotFound)
ErrNotFound is returned when no row matches, including when the row exists in another tenant. The two cases are deliberately indistinguishable.
It wraps model.ErrModelNotFound, so the router answers it with 404 and a caller can still ask for it by name with errors.Is.
var PortuguesePaths = Paths{
New: "/novo", Replies: "/respostas", Closure: "/encerramento", Reopening: "/reabertura", Attachments: "/anexos",
Notes: "/notas", Status: "/situacao", Priority: "/prioridade", Assignee: "/responsavel",
}
PortuguesePaths are the segments in Brazilian Portuguese, for an application whose addresses are written in it. Mounted at "/painel/chamados" and "/admin/chamados" they read /painel/chamados/novo, /painel/chamados/{id}/respostas and /admin/chamados/{id}/responsavel.
var Priorities = []Priority{PriorityLow, PriorityNormal, PriorityHigh, PriorityUrgent}
Priorities are every priority, lowest first.
var Statuses = []Status{StatusOpen, StatusWaitingCustomer, StatusWaitingTeam, StatusResolved, StatusClosed}
Statuses are every status, in the order the screens list them.
Functions ¶
func Actions ¶
Actions are the customer's actions, for an application to declare in arandu-permission's catalogue beside its own, so an organization grants them to whom it decides.
func AttachmentTypes ¶
func AttachmentTypes() []string
AttachmentTypes are the content types a message may carry, sorted.
func Lines ¶
func Lines(locale string) translation.Lines
Lines are the sentences this package ships for one locale, keyed the way a translator asks for them ("ticket.validation.required"), and nil for a locale it does not ship. An application reads them to see what there is to override, or to check a catalogue of its own against them.
func PriorityVariant ¶
PriorityVariant is the badge variant a priority wears: the ordinary ones are quiet, the ones that ask for haste are not.
func Prose ¶
func Prose(message MessageRow) template.HTML
Prose is a message's text as markup a page may write unescaped. It is a component in the sense the view compiler's audit means: a function returning template.HTML whose input was made safe when the row was built -- escaped, or rendered and sanitized by arandu-markdown -- and so is the one door through which text a person typed reaches a page as markup.
func PublishedPaths ¶
func PublishedPaths() []string
PublishedPaths are the files in the archive, each relative to the root of the application, sorted.
func StatusVariant ¶
StatusVariant is the badge variant a status wears: the one that waits on the customer stands out, the ones that are over fade.
func TeamActions ¶
TeamActions are the platform team's actions. They are declared apart from Actions so a catalogue an organization grants from never holds them: an application gives them to the groups of its own team only, and TeamPolicy refuses them to any subject outside Config.TeamTenant besides.
func ViewNames ¶
func ViewNames() []string
ViewNames are the names the published views are rendered by, sorted.
The name is the path under the view root with its separators turned into dots, which is what the view compiler writes into the registration call. It is derived from the same archive the publication carries, so a view that was renamed cannot keep an old name here.
func ViewPackages ¶
func ViewPackages() []string
ViewPackages are the directories the compiled views land in, each relative to the root of the application, sorted and without repeats.
Importing one is what puts its views in the binary: a compiled view calls the registry from init(), and a package nothing imports is not linked at all. The import is named rather than written into bootstrap/app.go, because one line somebody reads beats a file that changed while they were not looking.
Types ¶
type Account ¶
type Account struct {
// UserID is the user. It becomes the requester of the ticket.
UserID string
// Tenant is the space the user's tickets live in. It is the application's
// answer and nothing a request names, and it has to be a valid tenant.
Tenant string
// Name and Email are how the user is called and reached. Email is the one
// the account has, which can differ in case from the one the form carried.
Name string
Email string
}
Account is the user an e-mail belongs to, and the tenant they are a member of.
type AccountResolver ¶
type AccountResolver interface {
// Resolve returns the account that has the e-mail, or ErrNoAccount.
Resolve(ctx context.Context, email string) (Account, error)
}
AccountResolver finds the account an e-mail belongs to.
It is the application's: it reads its own users. OpenFromContact asks it and trusts the answer, so it answers only for an address that is an account's own, and ErrNoAccount for any other.
type Attachment ¶
type Attachment struct {
model.Model
// ID is the identifier.
ID string `db:"id"`
// TenantID is the tenant of the ticket the file belongs to.
TenantID string `db:"tenant_id"`
// TicketID and MessageID say where the file was attached.
TicketID string `db:"ticket_id"`
MessageID string `db:"message_id"`
// Key is where the Disk keeps the bytes. It is generated by the server and
// never shown.
Key string `db:"file_key"`
// Name is the name the customer's file had, cut to a bare file name.
Name string `db:"file_name"`
// ContentType is what the bytes are, as detected and not as announced.
ContentType string `db:"content_type"`
// Size is the number of bytes.
Size int64 `db:"size"`
// SHA256 is the digest of the bytes, in hex.
SHA256 string `db:"sha256"`
// CreatedAt is when it was stored, in UTC.
CreatedAt time.Time `db:"created_at"`
}
Attachment is a file kept for a message. The bytes live on the application's Disk, under a key the server generated; the row says which and what they are.
func (*Attachment) Fresh ¶
func (row *Attachment) Fresh(ctx context.Context, g auth.Grant, with ...string) (*Attachment, error)
Fresh reads the row again, as a new Attachment; it shadows the untyped one the embedded model promotes.
func (*Attachment) Replicate ¶
func (row *Attachment) Replicate(except ...string) (*Attachment, error)
Replicate returns an unsaved copy of the row without its key; it shadows the untyped one the embedded model promotes.
type AttachmentCollection ¶
type AttachmentCollection []*Attachment
AttachmentCollection is rows of Attachment, as a query returns them.
func AttachmentCollectionOf ¶
func AttachmentCollectionOf(rows model.Rows) AttachmentCollection
AttachmentCollectionOf converts rows of the table of Attachment. A nil input is a nil collection.
func (AttachmentCollection) Contains ¶
func (c AttachmentCollection) Contains(key any) bool
func (AttachmentCollection) Except ¶
func (c AttachmentCollection) Except(keys ...any) AttachmentCollection
func (AttachmentCollection) Find ¶
func (c AttachmentCollection) Find(key any) *Attachment
func (AttachmentCollection) FindOrFail ¶
func (c AttachmentCollection) FindOrFail(key any) (*Attachment, error)
func (AttachmentCollection) Fresh ¶
func (c AttachmentCollection) Fresh(ctx context.Context, g auth.Grant, with ...string) (AttachmentCollection, error)
func (AttachmentCollection) LoadMissing ¶
func (AttachmentCollection) ModelKeys ¶
func (c AttachmentCollection) ModelKeys() []any
func (AttachmentCollection) Only ¶
func (c AttachmentCollection) Only(keys ...any) AttachmentCollection
func (AttachmentCollection) Pluck ¶
func (c AttachmentCollection) Pluck(column string) []any
func (AttachmentCollection) Rows ¶
func (c AttachmentCollection) Rows() model.Rows
Rows converts the collection back to the core's rows.
func (AttachmentCollection) ToArray ¶
func (c AttachmentCollection) ToArray() []map[string]any
func (AttachmentCollection) Unique ¶
func (c AttachmentCollection) Unique() AttachmentCollection
type AttachmentQuery ¶
type AttachmentQuery struct {
// contains filtered or unexported fields
}
AttachmentQuery is a query on the table of Attachment.
The methods that build return the query, so they chain. The methods that run take a context and an auth.Grant, and return Attachment rows or a value; there is no way to run one without a Grant.
func Attachments ¶
func Attachments(db model.DB) *AttachmentQuery
Attachments starts a query on the table of Attachment, through db.
Global scopes are on and, unless the table is Global, every statement is filtered by the tenant of the Grant its terminal takes.
func (*AttachmentQuery) AddSelect ¶
func (q *AttachmentQuery) AddSelect(columns ...any) *AttachmentQuery
func (*AttachmentQuery) Base ¶
func (q *AttachmentQuery) Base() *model.Builder
Base returns the core builder the query forwards to. It is for code in this package -- a local scope in the custom block of model.go -- and for the factory, which builds rows through it.
func (*AttachmentQuery) Clone ¶
func (q *AttachmentQuery) Clone() *AttachmentQuery
Clone returns an independent copy of the query.
func (*AttachmentQuery) Create ¶
func (q *AttachmentQuery) Create(ctx context.Context, g auth.Grant, attributes map[string]any) (*Attachment, error)
func (*AttachmentQuery) Cursor ¶
func (q *AttachmentQuery) Cursor(ctx context.Context, g auth.Grant) iter.Seq2[*Attachment, error]
func (*AttachmentQuery) CursorPaginate ¶
func (q *AttachmentQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, opts pagination.Options, columns ...any) (AttachmentCollection, *pagination.CursorPage, error)
func (*AttachmentQuery) DoesntExist ¶
func (*AttachmentQuery) DoesntHave ¶
func (q *AttachmentQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *AttachmentQuery
func (*AttachmentQuery) Find ¶
func (q *AttachmentQuery) Find(ctx context.Context, g auth.Grant, id any, columns ...any) (*Attachment, error)
func (*AttachmentQuery) FindMany ¶
func (q *AttachmentQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (AttachmentCollection, error)
func (*AttachmentQuery) FindOrFail ¶
func (q *AttachmentQuery) FindOrFail(ctx context.Context, g auth.Grant, id any, columns ...any) (*Attachment, error)
func (*AttachmentQuery) FindOrNew ¶
func (q *AttachmentQuery) FindOrNew(ctx context.Context, g auth.Grant, id any, columns ...any) (*Attachment, error)
func (*AttachmentQuery) First ¶
func (q *AttachmentQuery) First(ctx context.Context, g auth.Grant, columns ...any) (*Attachment, error)
First returns the first row, or nil and no error when there is none.
func (*AttachmentQuery) FirstOrCreate ¶
func (q *AttachmentQuery) FirstOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Attachment, error)
func (*AttachmentQuery) FirstOrFail ¶
func (q *AttachmentQuery) FirstOrFail(ctx context.Context, g auth.Grant, columns ...any) (*Attachment, error)
func (*AttachmentQuery) FirstOrNew ¶
func (q *AttachmentQuery) FirstOrNew(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Attachment, error)
func (*AttachmentQuery) FirstWhere ¶
func (q *AttachmentQuery) FirstWhere(ctx context.Context, g auth.Grant, column any, args ...any) (*Attachment, error)
func (*AttachmentQuery) ForPage ¶
func (q *AttachmentQuery) ForPage(page, perPage int) *AttachmentQuery
func (*AttachmentQuery) ForceCreate ¶
func (q *AttachmentQuery) ForceCreate(ctx context.Context, g auth.Grant, attributes map[string]any) (*Attachment, error)
func (*AttachmentQuery) Get ¶
func (q *AttachmentQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (AttachmentCollection, error)
Get runs the query and returns the rows.
func (*AttachmentQuery) GroupBy ¶
func (q *AttachmentQuery) GroupBy(groups ...any) *AttachmentQuery
func (*AttachmentQuery) Has ¶
func (q *AttachmentQuery) Has(relation, operator string, count int, boolean string, callback func(*query.Builder)) *AttachmentQuery
func (*AttachmentQuery) InsertOrIgnore ¶
func (*AttachmentQuery) Join ¶
func (q *AttachmentQuery) Join(table any, first any, args ...any) *AttachmentQuery
func (*AttachmentQuery) Latest ¶
func (q *AttachmentQuery) Latest(column ...string) *AttachmentQuery
func (*AttachmentQuery) Limit ¶
func (q *AttachmentQuery) Limit(value int) *AttachmentQuery
func (*AttachmentQuery) New ¶
func (q *AttachmentQuery) New() (*Attachment, error)
New returns an empty Attachment on the query's connection, not yet saved, filled with what WithAttributes recorded on the query.
func (*AttachmentQuery) Offset ¶
func (q *AttachmentQuery) Offset(value int) *AttachmentQuery
func (*AttachmentQuery) Oldest ¶
func (q *AttachmentQuery) Oldest(column ...string) *AttachmentQuery
func (*AttachmentQuery) OrDoesntHave ¶
func (q *AttachmentQuery) OrDoesntHave(relation string) *AttachmentQuery
func (*AttachmentQuery) OrHas ¶
func (q *AttachmentQuery) OrHas(relation, operator string, count int) *AttachmentQuery
func (*AttachmentQuery) OrWhere ¶
func (q *AttachmentQuery) OrWhere(column any, args ...any) *AttachmentQuery
OrWhere adds an or-where clause; a func(*AttachmentQuery) in place of the column adds a group.
func (*AttachmentQuery) OrWhereDoesntHave ¶
func (q *AttachmentQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *AttachmentQuery
func (*AttachmentQuery) OrWhereHas ¶
func (q *AttachmentQuery) OrWhereHas(relation string, callback func(*query.Builder)) *AttachmentQuery
func (*AttachmentQuery) OrWhereNot ¶
func (q *AttachmentQuery) OrWhereNot(column any, args ...any) *AttachmentQuery
OrWhereNot adds an or-joined negated where clause, or a negated group.
func (*AttachmentQuery) OrWhereRelation ¶
func (q *AttachmentQuery) OrWhereRelation(relation string, column any, args ...any) *AttachmentQuery
func (*AttachmentQuery) OrderBy ¶
func (q *AttachmentQuery) OrderBy(column any, direction ...string) *AttachmentQuery
func (*AttachmentQuery) OrderByDesc ¶
func (q *AttachmentQuery) OrderByDesc(column any) *AttachmentQuery
func (*AttachmentQuery) Paginate ¶
func (q *AttachmentQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (AttachmentCollection, *pagination.LengthAwarePage, error)
func (*AttachmentQuery) Select ¶
func (q *AttachmentQuery) Select(columns ...any) *AttachmentQuery
func (*AttachmentQuery) SelectRaw ¶
func (q *AttachmentQuery) SelectRaw(expression string, bindings ...any) *AttachmentQuery
func (*AttachmentQuery) SimplePaginate ¶
func (q *AttachmentQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (AttachmentCollection, *pagination.Page, error)
func (*AttachmentQuery) Sole ¶
func (q *AttachmentQuery) Sole(ctx context.Context, g auth.Grant, columns ...any) (*Attachment, error)
func (*AttachmentQuery) UpdateOrCreate ¶
func (q *AttachmentQuery) UpdateOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Attachment, error)
func (*AttachmentQuery) ValueOrFail ¶
func (*AttachmentQuery) Where ¶
func (q *AttachmentQuery) Where(column any, args ...any) *AttachmentQuery
Where adds a where clause; a func(*AttachmentQuery) in place of the column adds a group.
func (*AttachmentQuery) WhereBelongsTo ¶
func (q *AttachmentQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *AttachmentQuery
func (*AttachmentQuery) WhereBetween ¶
func (q *AttachmentQuery) WhereBetween(column any, from, to any) *AttachmentQuery
func (*AttachmentQuery) WhereColumn ¶
func (q *AttachmentQuery) WhereColumn(first any, args ...any) *AttachmentQuery
func (*AttachmentQuery) WhereDoesntHave ¶
func (q *AttachmentQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *AttachmentQuery
func (*AttachmentQuery) WhereExists ¶
func (q *AttachmentQuery) WhereExists(callback func(*query.Builder)) *AttachmentQuery
func (*AttachmentQuery) WhereHas ¶
func (q *AttachmentQuery) WhereHas(relation string, callback func(*query.Builder)) *AttachmentQuery
func (*AttachmentQuery) WhereHasCount ¶
func (q *AttachmentQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *AttachmentQuery
func (*AttachmentQuery) WhereIn ¶
func (q *AttachmentQuery) WhereIn(column any, values []any) *AttachmentQuery
func (*AttachmentQuery) WhereKey ¶
func (q *AttachmentQuery) WhereKey(id any) *AttachmentQuery
func (*AttachmentQuery) WhereNot ¶
func (q *AttachmentQuery) WhereNot(column any, args ...any) *AttachmentQuery
WhereNot adds a negated where clause, or a negated group.
func (*AttachmentQuery) WhereNotExists ¶
func (q *AttachmentQuery) WhereNotExists(callback func(*query.Builder)) *AttachmentQuery
func (*AttachmentQuery) WhereNotIn ¶
func (q *AttachmentQuery) WhereNotIn(column any, values []any) *AttachmentQuery
func (*AttachmentQuery) WhereNotNull ¶
func (q *AttachmentQuery) WhereNotNull(columns ...any) *AttachmentQuery
func (*AttachmentQuery) WhereNull ¶
func (q *AttachmentQuery) WhereNull(columns ...any) *AttachmentQuery
func (*AttachmentQuery) WhereRelation ¶
func (q *AttachmentQuery) WhereRelation(relation string, column any, args ...any) *AttachmentQuery
func (*AttachmentQuery) With ¶
func (q *AttachmentQuery) With(relations ...string) *AttachmentQuery
func (*AttachmentQuery) WithAvg ¶
func (q *AttachmentQuery) WithAvg(relation, column string) *AttachmentQuery
func (*AttachmentQuery) WithCount ¶
func (q *AttachmentQuery) WithCount(relations ...string) *AttachmentQuery
func (*AttachmentQuery) WithExists ¶
func (q *AttachmentQuery) WithExists(relation string) *AttachmentQuery
func (*AttachmentQuery) WithMax ¶
func (q *AttachmentQuery) WithMax(relation, column string) *AttachmentQuery
func (*AttachmentQuery) WithMin ¶
func (q *AttachmentQuery) WithMin(relation, column string) *AttachmentQuery
func (*AttachmentQuery) WithSum ¶
func (q *AttachmentQuery) WithSum(relation, column string) *AttachmentQuery
func (*AttachmentQuery) Without ¶
func (q *AttachmentQuery) Without(relations ...string) *AttachmentQuery
func (*AttachmentQuery) WithoutGlobalScope ¶
func (q *AttachmentQuery) WithoutGlobalScope(identifier string) *AttachmentQuery
func (*AttachmentQuery) WithoutGlobalScopes ¶
func (q *AttachmentQuery) WithoutGlobalScopes(identifiers ...string) *AttachmentQuery
type AttachmentView ¶
AttachmentView is a file as a screen or a response describes it. The key it is kept under is not part of it: the bytes are read through the package's download route, which asks the policy first.
type ChromeRequest ¶
type ChromeRequest struct {
// Title is the screen's title, already in the screen's language, without
// the application's name Config.TitleSeparator adds to the document title.
Title string
// Team says the screen is one of the team's, mounted under TeamPrefix.
Team bool
// Subject is who is asking, as RequireAuth and Config.Middleware left it.
Subject security.Subject
}
ChromeRequest is what Config.Chrome is asked about one screen.
type Config ¶
type Config struct {
// TeamTenant is the tenant the platform's own team signs in to: the realm of
// the operator console. A team member is a subject of this tenant who holds
// the team's actions, and the team policy refuses any other tenant's
// subject whatever actions they carry. It comes from the application's own
// configuration, never from a request. Required.
TeamTenant string
// Prefix is the path the customer's routes are mounted under. Empty means
// DefaultPrefix.
Prefix string
// TeamPrefix is the path the team's routes are mounted under. Empty means
// DefaultTeamPrefix. The two sides are separate: the team's session is
// valid on this side only, and the customer's on the other.
TeamPrefix string
// Paths are the segments every route is built from, under Prefix and
// TeamPrefix. A field left empty keeps its DefaultPaths value;
// PortuguesePaths is the whole set in Brazilian Portuguese.
Paths Paths
// PageSize is how many rows one page of a list answers with. Zero means
// DefaultPageSize, and anything above MaxPageSize is refused rather than
// clamped: a number somebody wrote and did not get is worse than a number
// somebody wrote and was told about.
PageSize int
// Middleware runs on every route of this package after RequireAuth, in
// order, and TeamMiddleware after it on the team's routes only. An
// application whose permissions arandu-permission administers mounts its
// Roles() in Middleware: RequireAuth puts the subject as the session
// resolved it, without the actions arandu-permission resolves on each
// request. A guard of the application's own side -- the operator console's
// -- goes in TeamMiddleware. A guard answers "is this request on the right
// side" and stops; the policy still decides.
Middleware []func(http.Handler) http.Handler
TeamMiddleware []func(http.Handler) http.Handler
// Accounts resolves an e-mail to an account, for OpenFromContact. Nil makes
// OpenFromContact refuse: there is no account to open for.
Accounts AccountResolver
// Directory names people and lists the team. Optional; see Directory.
Directory Directory
// Notifier is told when a ticket is opened, when the customer replies and
// when the team answers. Nil tells nobody. See Notifier.
Notifier Notifier
// Activity is arandu-activitylog's logger, the application's one instance
// (activityModule.Logger()). Every act that changes something is recorded
// under the Grant it was authorized with, in the log named ActivityLogName,
// and so is every download of a file. Nil records nothing.
Activity *activitylog.Logger
// Markdown renders the text of a message as sanitized HTML. It is the
// application's one arandu-markdown instance (markdownModule): configure it
// with Images set to markdown.ImagesNone and LinkRel "noopener nofollow
// ugc", as a body people wrote deserves. Nil shows the text escaped, with
// its line breaks, which is safe and plain.
Markdown *markdown.Module
// Files is the Disk the attachments are kept on: the application's, which
// in production is a private bucket. Every key is generated by the server
// and every read is authorized and recorded; nothing is served by a public
// address. Nil turns attachments off, and a message that carries a file is
// refused.
Files *filesystem.Disk
// MaxAttachmentBytes is the size of the largest file one message carries.
// Zero means DefaultMaxAttachmentBytes, and anything above
// MaxAttachmentBytesLimit is refused.
MaxAttachmentBytes int64
// MaxAttachments is how many files one message carries. Zero means
// DefaultMaxAttachments, and anything above MaxAttachmentsLimit is refused.
MaxAttachments int
// ContactWindow is how long the same e-mail, subject and body open one
// ticket and not two. Zero means DefaultContactWindow.
ContactWindow time.Duration
// Translator is the application's own catalogue, asked before the one this
// package ships, key by key: "ticket.screen.index_title". Optional.
Translator *translation.Translator
// Locale is the language every screen, validation message and activity
// description of this package is written in. Empty means DefaultLocale. The
// package ships the locales Locales() lists; any other reads the
// application's Translator first and English after it.
Locale string
// TitleSeparator joins a screen's title to the application's name in the
// document title: with " — ", the list is titled "Chamados — Alpr.sh". Empty
// titles a screen with its own title alone.
TitleSeparator string
// Chrome is what the application's layout draws around a screen of this
// package: its sidebar, its navigation, the account menu. It is called once
// per screen, after RequireAuth and before the page is rendered, and what it
// answers is carried on the page as Screen.Chrome, where a layout asserts
// the interface it draws from. An error is returned as the handler's. Nil
// draws the screens with the page hesape's view.New builds and nothing
// more.
Chrome func(ctx *fhttp.Context, req ChromeRequest) (any, error)
// Location is the timezone an instant is written in on the screens: a ticket
// written at 22:00 in Sao Paulo is shown at 22:00, though it is already the next
// day in UTC. Nil means DefaultLocation.
Location *time.Location
// Now is the clock: every timestamp a row carries is read from it. Nil
// means time.Now. A test holds it still; an application leaves it nil.
Now func() time.Time
// Logger receives what the package cannot return to anybody: a Notifier
// that failed after the change was stored. Nil means slog.Default().
Logger *slog.Logger
}
Config is what the application passes when it wires this package.
A typed struct rather than a map: a misspelled key in a map is a setting that silently keeps its default, and the failure shows up as behaviour nobody asked for rather than as an error. Here a field that does not exist does not compile.
It names no tenant for the customers, and that is deliberate. Every route sits behind RequireAuth, so the tenant of every customer statement is the one the Grant carries, from the session of whoever is asking; a tenant written here would be a second place for one to come from. The one tenant it does name is the team's own, TeamTenant, because the team is not a customer.
type ContactInput ¶
type ContactInput struct {
// Email is the address the sender wrote. The ticket opens only if it is an
// account's.
Email string
// Name is what the sender called themselves; the account's own name wins
// when it has one.
Name string
Subject string
Body string
}
ContactInput is what the application's public contact form carries.
type ContactResult ¶
type ContactResult struct {
// Ticket is the ticket the form opened, or the one it already had opened
// when this is a repeat.
Ticket *Ticket
// Duplicate says the same e-mail, subject and body had already opened a
// ticket inside Config.ContactWindow, and nothing new was written.
Duplicate bool
}
ContactResult is what OpenFromContact answers.
type Conversation ¶
type Conversation struct {
Ticket Summary
Messages []MessageView
}
Conversation is a ticket with what was said on it, as the side asking may read it. The customer's never holds an internal note.
type Counter ¶
type Counter struct {
model.Model
ID string `db:"id"`
TenantID string `db:"tenant_id"`
LastNumber int64 `db:"last_number"`
UpdatedAt time.Time `db:"updated_at"`
}
Counter is the last number a tenant's tickets were given. The row's key is the tenant, so there is one row to take a lock on and no way to hold two. It is bookkeeping: an application never reads or writes it.
type CounterCollection ¶
type CounterCollection []*Counter
CounterCollection is rows of Counter, as a query returns them.
func CounterCollectionOf ¶
func CounterCollectionOf(rows model.Rows) CounterCollection
CounterCollectionOf converts rows of the table of Counter. A nil input is a nil collection.
func (CounterCollection) Contains ¶
func (c CounterCollection) Contains(key any) bool
func (CounterCollection) Except ¶
func (c CounterCollection) Except(keys ...any) CounterCollection
func (CounterCollection) Find ¶
func (c CounterCollection) Find(key any) *Counter
func (CounterCollection) FindOrFail ¶
func (c CounterCollection) FindOrFail(key any) (*Counter, error)
func (CounterCollection) Fresh ¶
func (c CounterCollection) Fresh(ctx context.Context, g auth.Grant, with ...string) (CounterCollection, error)
func (CounterCollection) LoadMissing ¶
func (CounterCollection) ModelKeys ¶
func (c CounterCollection) ModelKeys() []any
func (CounterCollection) Only ¶
func (c CounterCollection) Only(keys ...any) CounterCollection
func (CounterCollection) Pluck ¶
func (c CounterCollection) Pluck(column string) []any
func (CounterCollection) Rows ¶
func (c CounterCollection) Rows() model.Rows
Rows converts the collection back to the core's rows.
func (CounterCollection) ToArray ¶
func (c CounterCollection) ToArray() []map[string]any
func (CounterCollection) Unique ¶
func (c CounterCollection) Unique() CounterCollection
type CounterQuery ¶
type CounterQuery struct {
// contains filtered or unexported fields
}
CounterQuery is a query on the table of Counter.
The methods that build return the query, so they chain. The methods that run take a context and an auth.Grant, and return Counter rows or a value; there is no way to run one without a Grant.
func Counters ¶
func Counters(db model.DB) *CounterQuery
Counters starts a query on the table of Counter, through db.
Global scopes are on and, unless the table is Global, every statement is filtered by the tenant of the Grant its terminal takes.
func (*CounterQuery) AddSelect ¶
func (q *CounterQuery) AddSelect(columns ...any) *CounterQuery
func (*CounterQuery) Base ¶
func (q *CounterQuery) Base() *model.Builder
Base returns the core builder the query forwards to. It is for code in this package -- a local scope in the custom block of model.go -- and for the factory, which builds rows through it.
func (*CounterQuery) Clone ¶
func (q *CounterQuery) Clone() *CounterQuery
Clone returns an independent copy of the query.
func (*CounterQuery) CursorPaginate ¶
func (q *CounterQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, opts pagination.Options, columns ...any) (CounterCollection, *pagination.CursorPage, error)
func (*CounterQuery) DoesntExist ¶
func (*CounterQuery) DoesntHave ¶
func (q *CounterQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *CounterQuery
func (*CounterQuery) FindMany ¶
func (q *CounterQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (CounterCollection, error)
func (*CounterQuery) FindOrFail ¶
func (*CounterQuery) FirstOrCreate ¶
func (*CounterQuery) FirstOrFail ¶
func (*CounterQuery) FirstOrNew ¶
func (*CounterQuery) FirstWhere ¶
func (*CounterQuery) ForPage ¶
func (q *CounterQuery) ForPage(page, perPage int) *CounterQuery
func (*CounterQuery) ForceCreate ¶
func (*CounterQuery) Get ¶
func (q *CounterQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (CounterCollection, error)
Get runs the query and returns the rows.
func (*CounterQuery) GroupBy ¶
func (q *CounterQuery) GroupBy(groups ...any) *CounterQuery
func (*CounterQuery) Has ¶
func (q *CounterQuery) Has(relation, operator string, count int, boolean string, callback func(*query.Builder)) *CounterQuery
func (*CounterQuery) InsertOrIgnore ¶
func (*CounterQuery) Join ¶
func (q *CounterQuery) Join(table any, first any, args ...any) *CounterQuery
func (*CounterQuery) Latest ¶
func (q *CounterQuery) Latest(column ...string) *CounterQuery
func (*CounterQuery) Limit ¶
func (q *CounterQuery) Limit(value int) *CounterQuery
func (*CounterQuery) New ¶
func (q *CounterQuery) New() (*Counter, error)
New returns an empty Counter on the query's connection, not yet saved, filled with what WithAttributes recorded on the query.
func (*CounterQuery) Offset ¶
func (q *CounterQuery) Offset(value int) *CounterQuery
func (*CounterQuery) Oldest ¶
func (q *CounterQuery) Oldest(column ...string) *CounterQuery
func (*CounterQuery) OrDoesntHave ¶
func (q *CounterQuery) OrDoesntHave(relation string) *CounterQuery
func (*CounterQuery) OrHas ¶
func (q *CounterQuery) OrHas(relation, operator string, count int) *CounterQuery
func (*CounterQuery) OrWhere ¶
func (q *CounterQuery) OrWhere(column any, args ...any) *CounterQuery
OrWhere adds an or-where clause; a func(*CounterQuery) in place of the column adds a group.
func (*CounterQuery) OrWhereDoesntHave ¶
func (q *CounterQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *CounterQuery
func (*CounterQuery) OrWhereHas ¶
func (q *CounterQuery) OrWhereHas(relation string, callback func(*query.Builder)) *CounterQuery
func (*CounterQuery) OrWhereNot ¶
func (q *CounterQuery) OrWhereNot(column any, args ...any) *CounterQuery
OrWhereNot adds an or-joined negated where clause, or a negated group.
func (*CounterQuery) OrWhereRelation ¶
func (q *CounterQuery) OrWhereRelation(relation string, column any, args ...any) *CounterQuery
func (*CounterQuery) OrderBy ¶
func (q *CounterQuery) OrderBy(column any, direction ...string) *CounterQuery
func (*CounterQuery) OrderByDesc ¶
func (q *CounterQuery) OrderByDesc(column any) *CounterQuery
func (*CounterQuery) Paginate ¶
func (q *CounterQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (CounterCollection, *pagination.LengthAwarePage, error)
func (*CounterQuery) Select ¶
func (q *CounterQuery) Select(columns ...any) *CounterQuery
func (*CounterQuery) SelectRaw ¶
func (q *CounterQuery) SelectRaw(expression string, bindings ...any) *CounterQuery
func (*CounterQuery) SimplePaginate ¶
func (q *CounterQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (CounterCollection, *pagination.Page, error)
func (*CounterQuery) UpdateOrCreate ¶
func (*CounterQuery) ValueOrFail ¶
func (*CounterQuery) Where ¶
func (q *CounterQuery) Where(column any, args ...any) *CounterQuery
Where adds a where clause; a func(*CounterQuery) in place of the column adds a group.
func (*CounterQuery) WhereBelongsTo ¶
func (q *CounterQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *CounterQuery
func (*CounterQuery) WhereBetween ¶
func (q *CounterQuery) WhereBetween(column any, from, to any) *CounterQuery
func (*CounterQuery) WhereColumn ¶
func (q *CounterQuery) WhereColumn(first any, args ...any) *CounterQuery
func (*CounterQuery) WhereDoesntHave ¶
func (q *CounterQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *CounterQuery
func (*CounterQuery) WhereExists ¶
func (q *CounterQuery) WhereExists(callback func(*query.Builder)) *CounterQuery
func (*CounterQuery) WhereHas ¶
func (q *CounterQuery) WhereHas(relation string, callback func(*query.Builder)) *CounterQuery
func (*CounterQuery) WhereHasCount ¶
func (q *CounterQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *CounterQuery
func (*CounterQuery) WhereIn ¶
func (q *CounterQuery) WhereIn(column any, values []any) *CounterQuery
func (*CounterQuery) WhereKey ¶
func (q *CounterQuery) WhereKey(id any) *CounterQuery
func (*CounterQuery) WhereNot ¶
func (q *CounterQuery) WhereNot(column any, args ...any) *CounterQuery
WhereNot adds a negated where clause, or a negated group.
func (*CounterQuery) WhereNotExists ¶
func (q *CounterQuery) WhereNotExists(callback func(*query.Builder)) *CounterQuery
func (*CounterQuery) WhereNotIn ¶
func (q *CounterQuery) WhereNotIn(column any, values []any) *CounterQuery
func (*CounterQuery) WhereNotNull ¶
func (q *CounterQuery) WhereNotNull(columns ...any) *CounterQuery
func (*CounterQuery) WhereNull ¶
func (q *CounterQuery) WhereNull(columns ...any) *CounterQuery
func (*CounterQuery) WhereRelation ¶
func (q *CounterQuery) WhereRelation(relation string, column any, args ...any) *CounterQuery
func (*CounterQuery) With ¶
func (q *CounterQuery) With(relations ...string) *CounterQuery
func (*CounterQuery) WithAvg ¶
func (q *CounterQuery) WithAvg(relation, column string) *CounterQuery
func (*CounterQuery) WithCount ¶
func (q *CounterQuery) WithCount(relations ...string) *CounterQuery
func (*CounterQuery) WithExists ¶
func (q *CounterQuery) WithExists(relation string) *CounterQuery
func (*CounterQuery) WithMax ¶
func (q *CounterQuery) WithMax(relation, column string) *CounterQuery
func (*CounterQuery) WithMin ¶
func (q *CounterQuery) WithMin(relation, column string) *CounterQuery
func (*CounterQuery) WithSum ¶
func (q *CounterQuery) WithSum(relation, column string) *CounterQuery
func (*CounterQuery) Without ¶
func (q *CounterQuery) Without(relations ...string) *CounterQuery
func (*CounterQuery) WithoutGlobalScope ¶
func (q *CounterQuery) WithoutGlobalScope(identifier string) *CounterQuery
func (*CounterQuery) WithoutGlobalScopes ¶
func (q *CounterQuery) WithoutGlobalScopes(identifiers ...string) *CounterQuery
type Directory ¶
type Directory interface {
// Person returns a user of a tenant, or ErrNoAccount when there is none.
Person(ctx context.Context, tenant, id string) (Person, error)
// Team returns the members of the platform team a ticket can be assigned
// to, in the order a list offers them.
Team(ctx context.Context) ([]Person, error)
}
Directory names people. It is optional: without one a ticket keeps the name and the e-mail of its requester empty when the application did not supply them, a message shows its author's role instead of a name, and a ticket can be assigned only to the member of the team who is asking.
type Event ¶
type Event struct {
Kind EventKind
// Ticket is the ticket after the change.
Ticket Summary
// Message is the message the event is about. It is never an internal one.
Message MessageView
}
Event is what a Notifier is handed.
An internal note is never one: a note changes nothing the customer can read, and the package tells nobody about it.
type EventKind ¶
type EventKind string
EventKind says what happened on a ticket that somebody should be told about.
const ( // EventOpened is a ticket a customer opened, on the screens or through the // contact form. The team is the one to tell. EventOpened EventKind = "ticket.opened" // EventCustomerReplied is a reply from the customer. The team is the one to // tell. EventCustomerReplied EventKind = "ticket.customer_replied" // EventTeamReplied is an answer from the team. The customer is the one to // tell: the requester of the ticket. EventTeamReplied EventKind = "ticket.team_replied" )
The events a Notifier is told about.
type FileRow ¶
type FileRow struct{ Name, Type, Size, URL string }
FileRow is one file of a message.
type Flag ¶
type Flag bool
Flag is a yes-or-no a column holds as an integer, read back from whatever an engine answers with: the engines disagree about booleans and one place has to flatten them.
type FormState ¶
FormState is what a form field reads from the page it is drawn on: what was typed before a refusal, and the message for each field.
func (FormState) ErrorSummary ¶
ErrorSummary is one line per rejected field.
func (FormState) FieldError ¶
FieldError is the first message for one field, empty when it was accepted.
func (FormState) FieldErrors ¶
FieldErrors is every message for one field.
func (FormState) LogValue ¶
LogValue is the form state as a log line carries it, the token redacted.
func (FormState) MarshalJSON ¶
MarshalJSON writes the form state with the CSRF token redacted.
type IndexPageData ¶
type IndexPageData struct {
Screen
Rows []TicketRow
Query string
Statuses []string
SortKey string
SortDir string
URL, NewURL string
CanCreate bool
Pager components.Paginator
CurrentPage int
Pages, Total int
PerPage int
StatusOptions []components.SelectOption
}
IndexPageData is the customer's list, a page at a time, filtered, searched and ordered on the server.
type Labels ¶
type Labels struct {
Locale string
// contains filtered or unexported fields
}
Labels are the sentences of this package in one locale.
func (Labels) Field ¶
Field is what a person reads in place of an input's name, in a summary of the rejected fields: "subject" reads "Assunto". A field with no line reads the way the input was named.
type ListQuery ¶
type ListQuery struct {
Search string
Statuses []Status
Sort string
Dir string
Page int
PerPage int
}
ListQuery narrows the customer's list. Page counts from one.
type Message ¶
type Message struct {
model.Model
// ID is the identifier. It is an ordered identifier, so messages written in
// the same instant keep the order they were written in.
ID string `db:"id"`
// TenantID is the tenant of the ticket the message belongs to.
TenantID string `db:"tenant_id"`
// TicketID is the ticket.
TicketID string `db:"ticket_id"`
// AuthorID is who wrote it, empty for a line the system wrote.
AuthorID string `db:"author_id"`
// AuthorName is how the author was called when they wrote it.
AuthorName string `db:"author_name"`
// Role is what the author is to the ticket.
Role Role `db:"role"`
// Body is the text, plain with optional Markdown. The screens render it
// through arandu-markdown, which sanitizes it.
Body string `db:"body"`
// Internal marks a note only the team reads. The customer is never shown a
// message with this set, nor told one exists.
Internal Flag `db:"internal"`
// CreatedAt is when it was written, in UTC.
CreatedAt time.Time `db:"created_at"`
}
Message is one entry of a ticket's conversation.
type MessageCollection ¶
type MessageCollection []*Message
MessageCollection is rows of Message, as a query returns them.
func MessageCollectionOf ¶
func MessageCollectionOf(rows model.Rows) MessageCollection
MessageCollectionOf converts rows of the table of Message. A nil input is a nil collection.
func (MessageCollection) Contains ¶
func (c MessageCollection) Contains(key any) bool
func (MessageCollection) Except ¶
func (c MessageCollection) Except(keys ...any) MessageCollection
func (MessageCollection) Find ¶
func (c MessageCollection) Find(key any) *Message
func (MessageCollection) FindOrFail ¶
func (c MessageCollection) FindOrFail(key any) (*Message, error)
func (MessageCollection) Fresh ¶
func (c MessageCollection) Fresh(ctx context.Context, g auth.Grant, with ...string) (MessageCollection, error)
func (MessageCollection) LoadMissing ¶
func (MessageCollection) ModelKeys ¶
func (c MessageCollection) ModelKeys() []any
func (MessageCollection) Only ¶
func (c MessageCollection) Only(keys ...any) MessageCollection
func (MessageCollection) Pluck ¶
func (c MessageCollection) Pluck(column string) []any
func (MessageCollection) Rows ¶
func (c MessageCollection) Rows() model.Rows
Rows converts the collection back to the core's rows.
func (MessageCollection) ToArray ¶
func (c MessageCollection) ToArray() []map[string]any
func (MessageCollection) Unique ¶
func (c MessageCollection) Unique() MessageCollection
type MessageQuery ¶
type MessageQuery struct {
// contains filtered or unexported fields
}
MessageQuery is a query on the table of Message.
The methods that build return the query, so they chain. The methods that run take a context and an auth.Grant, and return Message rows or a value; there is no way to run one without a Grant.
func Messages ¶
func Messages(db model.DB) *MessageQuery
Messages starts a query on the table of Message, through db.
Global scopes are on and, unless the table is Global, every statement is filtered by the tenant of the Grant its terminal takes.
func (*MessageQuery) AddSelect ¶
func (q *MessageQuery) AddSelect(columns ...any) *MessageQuery
func (*MessageQuery) Base ¶
func (q *MessageQuery) Base() *model.Builder
Base returns the core builder the query forwards to. It is for code in this package -- a local scope in the custom block of model.go -- and for the factory, which builds rows through it.
func (*MessageQuery) Clone ¶
func (q *MessageQuery) Clone() *MessageQuery
Clone returns an independent copy of the query.
func (*MessageQuery) CursorPaginate ¶
func (q *MessageQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, opts pagination.Options, columns ...any) (MessageCollection, *pagination.CursorPage, error)
func (*MessageQuery) DoesntExist ¶
func (*MessageQuery) DoesntHave ¶
func (q *MessageQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *MessageQuery
func (*MessageQuery) FindMany ¶
func (q *MessageQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (MessageCollection, error)
func (*MessageQuery) FindOrFail ¶
func (*MessageQuery) FirstOrCreate ¶
func (*MessageQuery) FirstOrFail ¶
func (*MessageQuery) FirstOrNew ¶
func (*MessageQuery) FirstWhere ¶
func (*MessageQuery) ForPage ¶
func (q *MessageQuery) ForPage(page, perPage int) *MessageQuery
func (*MessageQuery) ForceCreate ¶
func (*MessageQuery) Get ¶
func (q *MessageQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (MessageCollection, error)
Get runs the query and returns the rows.
func (*MessageQuery) GroupBy ¶
func (q *MessageQuery) GroupBy(groups ...any) *MessageQuery
func (*MessageQuery) Has ¶
func (q *MessageQuery) Has(relation, operator string, count int, boolean string, callback func(*query.Builder)) *MessageQuery
func (*MessageQuery) InsertOrIgnore ¶
func (*MessageQuery) Join ¶
func (q *MessageQuery) Join(table any, first any, args ...any) *MessageQuery
func (*MessageQuery) Latest ¶
func (q *MessageQuery) Latest(column ...string) *MessageQuery
func (*MessageQuery) Limit ¶
func (q *MessageQuery) Limit(value int) *MessageQuery
func (*MessageQuery) New ¶
func (q *MessageQuery) New() (*Message, error)
New returns an empty Message on the query's connection, not yet saved, filled with what WithAttributes recorded on the query.
func (*MessageQuery) Offset ¶
func (q *MessageQuery) Offset(value int) *MessageQuery
func (*MessageQuery) Oldest ¶
func (q *MessageQuery) Oldest(column ...string) *MessageQuery
func (*MessageQuery) OrDoesntHave ¶
func (q *MessageQuery) OrDoesntHave(relation string) *MessageQuery
func (*MessageQuery) OrHas ¶
func (q *MessageQuery) OrHas(relation, operator string, count int) *MessageQuery
func (*MessageQuery) OrWhere ¶
func (q *MessageQuery) OrWhere(column any, args ...any) *MessageQuery
OrWhere adds an or-where clause; a func(*MessageQuery) in place of the column adds a group.
func (*MessageQuery) OrWhereDoesntHave ¶
func (q *MessageQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *MessageQuery
func (*MessageQuery) OrWhereHas ¶
func (q *MessageQuery) OrWhereHas(relation string, callback func(*query.Builder)) *MessageQuery
func (*MessageQuery) OrWhereNot ¶
func (q *MessageQuery) OrWhereNot(column any, args ...any) *MessageQuery
OrWhereNot adds an or-joined negated where clause, or a negated group.
func (*MessageQuery) OrWhereRelation ¶
func (q *MessageQuery) OrWhereRelation(relation string, column any, args ...any) *MessageQuery
func (*MessageQuery) OrderBy ¶
func (q *MessageQuery) OrderBy(column any, direction ...string) *MessageQuery
func (*MessageQuery) OrderByDesc ¶
func (q *MessageQuery) OrderByDesc(column any) *MessageQuery
func (*MessageQuery) Paginate ¶
func (q *MessageQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (MessageCollection, *pagination.LengthAwarePage, error)
func (*MessageQuery) Select ¶
func (q *MessageQuery) Select(columns ...any) *MessageQuery
func (*MessageQuery) SelectRaw ¶
func (q *MessageQuery) SelectRaw(expression string, bindings ...any) *MessageQuery
func (*MessageQuery) SimplePaginate ¶
func (q *MessageQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (MessageCollection, *pagination.Page, error)
func (*MessageQuery) UpdateOrCreate ¶
func (*MessageQuery) ValueOrFail ¶
func (*MessageQuery) Where ¶
func (q *MessageQuery) Where(column any, args ...any) *MessageQuery
Where adds a where clause; a func(*MessageQuery) in place of the column adds a group.
func (*MessageQuery) WhereBelongsTo ¶
func (q *MessageQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *MessageQuery
func (*MessageQuery) WhereBetween ¶
func (q *MessageQuery) WhereBetween(column any, from, to any) *MessageQuery
func (*MessageQuery) WhereColumn ¶
func (q *MessageQuery) WhereColumn(first any, args ...any) *MessageQuery
func (*MessageQuery) WhereDoesntHave ¶
func (q *MessageQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *MessageQuery
func (*MessageQuery) WhereExists ¶
func (q *MessageQuery) WhereExists(callback func(*query.Builder)) *MessageQuery
func (*MessageQuery) WhereHas ¶
func (q *MessageQuery) WhereHas(relation string, callback func(*query.Builder)) *MessageQuery
func (*MessageQuery) WhereHasCount ¶
func (q *MessageQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *MessageQuery
func (*MessageQuery) WhereIn ¶
func (q *MessageQuery) WhereIn(column any, values []any) *MessageQuery
func (*MessageQuery) WhereKey ¶
func (q *MessageQuery) WhereKey(id any) *MessageQuery
func (*MessageQuery) WhereNot ¶
func (q *MessageQuery) WhereNot(column any, args ...any) *MessageQuery
WhereNot adds a negated where clause, or a negated group.
func (*MessageQuery) WhereNotExists ¶
func (q *MessageQuery) WhereNotExists(callback func(*query.Builder)) *MessageQuery
func (*MessageQuery) WhereNotIn ¶
func (q *MessageQuery) WhereNotIn(column any, values []any) *MessageQuery
func (*MessageQuery) WhereNotNull ¶
func (q *MessageQuery) WhereNotNull(columns ...any) *MessageQuery
func (*MessageQuery) WhereNull ¶
func (q *MessageQuery) WhereNull(columns ...any) *MessageQuery
func (*MessageQuery) WhereRelation ¶
func (q *MessageQuery) WhereRelation(relation string, column any, args ...any) *MessageQuery
func (*MessageQuery) With ¶
func (q *MessageQuery) With(relations ...string) *MessageQuery
func (*MessageQuery) WithAvg ¶
func (q *MessageQuery) WithAvg(relation, column string) *MessageQuery
func (*MessageQuery) WithCount ¶
func (q *MessageQuery) WithCount(relations ...string) *MessageQuery
func (*MessageQuery) WithExists ¶
func (q *MessageQuery) WithExists(relation string) *MessageQuery
func (*MessageQuery) WithMax ¶
func (q *MessageQuery) WithMax(relation, column string) *MessageQuery
func (*MessageQuery) WithMin ¶
func (q *MessageQuery) WithMin(relation, column string) *MessageQuery
func (*MessageQuery) WithSum ¶
func (q *MessageQuery) WithSum(relation, column string) *MessageQuery
func (*MessageQuery) Without ¶
func (q *MessageQuery) Without(relations ...string) *MessageQuery
func (*MessageQuery) WithoutGlobalScope ¶
func (q *MessageQuery) WithoutGlobalScope(identifier string) *MessageQuery
func (*MessageQuery) WithoutGlobalScopes ¶
func (q *MessageQuery) WithoutGlobalScopes(identifiers ...string) *MessageQuery
type MessageRow ¶
type MessageRow struct {
ID, Author, Role, RoleKey string
When, WhenISO string
// Internal marks a note only the team reads; the customer's rows hold none.
Internal bool
// Body is the text as sanitized HTML: Config.Markdown's output, or the text
// escaped. It is the one value on a page written without escaping, and
// nothing reaches it that did not pass through one of those two.
Body template.HTML
Files []FileRow
}
MessageRow is one message of a conversation.
type MessageView ¶
type MessageView struct {
ID string
AuthorID string
AuthorName string
Role Role
Body string
Internal bool
CreatedAt time.Time
Attachments []AttachmentView
}
MessageView is a message as a screen, a response or a notification carries it.
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
Module is what the application registers.
It implements foundation.Module, which is Name and Routes and nothing else -- that pair is the whole public contract between a package and the framework.
It also implements foundation.Migratable, because it owns tables, and foundation.Publishable, because it hands view sources to the project. The other optional interfaces are declared beside Module in the framework and are opted into the same way, by implementing them: Bootable to prepare state at boot, Background to run a loop of its own, Schedulable to declare work for the scheduler, Health to report on the storage it depends on, Closable to give resources back at shutdown.
func New ¶
New returns the module, or the reason it cannot be built.
The collaborators are parameters and not fields somebody fills in afterwards: a module that could be registered half-wired is a module whose first request is the thing that reports the missing half.
It returns an error rather than panicking or carrying on, because everything it refuses is a wiring mistake, and a wiring mistake found at boot costs one restart. The same mistake found later is a request that reached a nil handle.
func (*Module) Boot ¶
Boot refuses to serve when a view this package renders is not in the binary.
A compiled view registers itself from init(), so by the time anything boots the question has one answer already: either the application published the files, compiled them and imported the package they became, or it did not. Asking here turns "did anybody run the install command" into one refusal at start-up that names the views and the command, instead of a 500 on the first request that reached one of them -- which is where it used to be answered, once per page, to whoever happened to open it.
It also holds the destination. Every file the archive offers has to land under the module directory named after this module: an archive that reached resources/views/home.kyse.go would land on a page the application wrote, and what publishes the files writes what the archive says.
func (*Module) CustomerRoutes ¶
CustomerRoutes registers the customer's routes alone, under Prefix, for an application that mounts them inside its own side -- a customer's panel -- with that side's guards on r.
func (*Module) Migrations ¶
func (m *Module) Migrations() []foundation.Migration
Migrations declares the schema this module owns.
They are returned in the order their names sort in, which is the order they apply in: the name carries the order, and nothing else decides it.
func (*Module) Name ¶
Name is the module identifier: a lowercase slug, stable, no spaces.
It is what `aru route:list` groups by and what the route names are prefixed with, so changing it changes addresses that other code has already written down.
func (*Module) Publishes ¶
func (m *Module) Publishes() []foundation.Publication
Publishes declares the files this package offers, each at the path it takes relative to the root of the project.
One tree, under one tag. A publication may carry a page, a component, a configuration file, a migration, a catalogue of sentences or an asset, and this package offers the first of those and nothing else. Each absence is a decision rather than an omission:
- configuration is the Config struct New is handed, checked by the compiler and validated before the module exists. A file copied into the project beside it would be a second place to say the same thing, and only one of the two could be the one the code reads.
- a stylesheet, a script or any other asset is registered with the view layer and served from an address derived from its own bytes. Copying one into the project would put a second copy of those bytes under a second address, and a page can only reference one of them.
- translations are overridden by writing the lines the application wants into its own override tree, which the catalogue loader already reads. A copy of every line this package ships is a copy that goes stale, and it goes stale without saying so.
- migrations are declared and collected, never copied. A copy in the project's own migration directory is found by the runner as well, so one schema change applies twice under two names.
What is left is the markup, and it is here for the reason the others are not: it is the one thing the project is expected to edit. A package cannot know what a screen should say in a product it has never seen.
func (*Module) Routes ¶
Routes registers every route of the module: the customer's and the team's.
An application that keeps the two sides apart -- and the team's session is valid on its own side only -- registers them separately with CustomerRoutes and TeamRoutes, each on the router group its own guards are on.
Every route needs somebody signed in, so the guard is mounted once per side, on the router the routes are registered on, rather than route by route: r is replaced by the guarded group before the first registration, and no unguarded router is left in scope for the next route to be added to. RequireAuth sends a request with no session to the sign-in screen, and puts the subject of one that has a session on its context, where a handler reads it with ctx.User; Config.Middleware runs after it, and Config.TeamMiddleware after that on the team's routes.
func (*Module) Team ¶
func (m *Module) Team() *TeamService
Team is the team's use cases, for an application that calls them from its own code. Every method asks TeamPolicy, so handing it out opens nothing.
func (*Module) TeamRoutes ¶
TeamRoutes registers the team's routes alone, under TeamPrefix, for an application that mounts them inside its operator console with that console's guards on r.
func (*Module) Tickets ¶
func (m *Module) Tickets() *TicketService
Tickets is the customer's use cases, for an application that calls them from its own code: the contact form, a command. Every method asks the policy, so handing it out opens nothing.
type NewPageData ¶
type NewPageData struct {
Screen
Action, BackURL string
// Files says the form takes attachments, and the rest says which.
Files bool
MaxFiles int
MaxMB string
Accept string
}
NewPageData is the form that opens a ticket.
type Notifier ¶
Notifier tells people that something happened on a ticket.
The application implements it with its own mailer: this package sends no e-mail. Notify runs after the change is stored, and its error does not undo the change: the ticket is already open and the reply already written, and a mail server that is down is no reason for a customer to write it twice. The error is logged and the request goes on. An application that must not lose a notification enqueues it here and delivers it from its queue.
type OpenRequest ¶
OpenRequest is what a customer asks for when opening a ticket on the screens. There is no tenant, no requester and no priority here and there must never be one: the tenant and the requester come from the Grant and the subject, and the priority is the team's to set.
type Origin ¶
type Origin string
Origin is where a ticket came from.
const ( // OriginWeb is a ticket the customer opened on the screens of this package. OriginWeb Origin = "web" // OriginContactForm is a ticket OpenFromContact opened from the // application's public contact form. OriginContactForm Origin = "contact_form" // OriginEmail is a ticket the application opened from an incoming e-mail. OriginEmail Origin = "email" )
The origins of a ticket.
type Paths ¶
type Paths struct {
New, Replies, Closure, Reopening, Attachments string
Notes, Status, Priority, Assignee string
}
Paths are the segments the routes of this package are built from. Each one starts with "/" and holds no wildcard.
GET Prefix the customer's tickets
GET Prefix + New the form that opens one
POST Prefix open one
GET Prefix + /{id} one conversation
POST Prefix + /{id} + Replies reply
POST Prefix + /{id} + Closure close
POST Prefix + /{id} + Reopening reopen
GET Prefix + /{id} + Attachments + /{attachment} a file
and under TeamPrefix the list, one conversation, Replies, Notes, Status, Priority, Assignee and Attachments.
type Priority ¶
type Priority string
Priority is how soon the team looks at a ticket.
type ReplyRequest ¶
type ReplyRequest struct {
Body string
}
ReplyRequest is a message added to a ticket.
type Screen ¶
type Screen struct {
hview.Page
Labels Labels
// URLs are the addresses of the package's routes.
URLs URLs
// Location is the timezone instants are written in.
Location *time.Location
// contains filtered or unexported fields
}
Screen is what every page of this package carries: the page hesape's view.New builds, the application's chrome, and the sentences.
It embeds hesape's view.Page, so every page of this package is a hesape/view.Layout -- what the layouts of an application on Arandu framework v0.62 and newer assert -- and it answers HasErrors and ErrorSummary as well, so it is also the framework/view.Layout an older layout asks for.
func (Screen) All ¶
All is one line per rejected message, for the banner above a form, in the screen's language: the field is named by its label in the catalogue ("Assunto: Preencha este campo.").
func (Screen) Chrome ¶
Chrome is what Config.Chrome answered for this screen: the application's sidebar, navigation and account menu, in whatever type its layout draws from. A layout asserts that type on it; nil when Config.Chrome is nil.
func (Screen) ErrorSummary ¶
ErrorSummary is one line per rejected field: framework/view.Layout's name for All.
func (Screen) FieldErrors ¶
FieldErrors is every message for one field.
func (Screen) FilesHint ¶
FilesHint is the sentence under a file input: what may be sent, and how much.
func (Screen) HasErrors ¶
HasErrors says whether the attempt that landed here was rejected: framework/view.Layout's name for hesape's Any.
func (Screen) MarshalJSON ¶
MarshalJSON writes the page with the CSRF token redacted, as framework/view.Page does: a page dumped for debugging never publishes the token. It answers for every page data that embeds Screen.
func (Screen) PriorityOptions ¶
func (s Screen) PriorityOptions() []components.SelectOption
PriorityOptions are the priorities as options of a filter or a select.
func (Screen) SearchLabel ¶
SearchLabel is the name a screen reader gives a table's search box: "Buscar em Chamados".
func (Screen) StatusOptions ¶
func (s Screen) StatusOptions() []components.SelectOption
StatusOptions are the statuses as options of a filter or a select.
func (Screen) TableLines ¶
func (s Screen) TableLines() components.DataTableLines
TableLines are the sentences a data table on these screens says.
type ShowPageData ¶
type ShowPageData struct {
Screen
Ticket TicketRow
Messages []MessageRow
BackURL string
ReplyURL, CloseURL, ReopenURL string
CanReply, CanClose bool
CanReopen bool
Files bool
MaxFiles int
MaxMB string
Accept string
}
ShowPageData is one conversation, for the customer.
type Status ¶
type Status string
Status is where a ticket stands.
const ( // StatusOpen is a ticket nobody on the team has answered yet. StatusOpen Status = "open" // StatusWaitingCustomer is a ticket the team answered and the customer owes // a reply to. StatusWaitingCustomer Status = "waiting_customer" // StatusWaitingTeam is a ticket the customer answered and the team owes a // reply to. StatusWaitingTeam Status = "waiting_team" // StatusResolved is a ticket the team considers answered. A reply from the // customer brings it back. StatusResolved Status = "resolved" // StatusClosed is a ticket nobody replies to any more, until it is // reopened. StatusClosed Status = "closed" )
The statuses of a ticket.
type Summary ¶
type Summary struct {
ID string
Tenant string
Number int64
Subject string
Status Status
Priority Priority
Category string
RequesterID string
RequesterName string
RequesterEmail string
AssigneeID string
AssigneeName string
Origin Origin
LastMessageAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
ClosedAt *time.Time
}
Summary is a ticket as a list, a response or a notification carries it: plain values, with no connection to the database and nothing the customer must not read.
type TeamIndexPageData ¶
type TeamIndexPageData struct {
Screen
Rows []TicketRow
Query string
Statuses []string
Priorities []string
Assignee string
Space string
SortKey string
SortDir string
URL string
Pager components.Paginator
CurrentPage int
Pages, Total int
StatusOptions []components.SelectOption
PriorityOptions []components.SelectOption
AssigneeOptions []components.SelectOption
}
TeamIndexPageData is the team's list, a page at a time, filtered, searched and ordered on the server.
type TeamPolicy ¶
type TeamPolicy struct {
// Realm is the tenant the team signs in to. Empty refuses everybody.
Realm string
}
TeamPolicy is the only authority over what the platform team does with a Ticket of any space.
It denies unless the subject is a member of the team -- a subject of Realm, the tenant the team signs in to -- who was granted the action. A subject of any other tenant is refused whatever actions it carries, and so is a subject somebody else is viewing as: an operator inside a customer's account acts as the customer and reads no other space.
It is the one policy that does not compare a record's tenant with the subject's, because crossing tenants is what the team does. The tenant of the work itself is not the subject's: the service reads the ticket to find it, and then acts in that tenant under a Grant scoped to it, issued only after this policy allowed the action.
type TeamQuery ¶
type TeamQuery struct {
// Search matches the number, the subject and the requester's name and
// e-mail.
Search string
Statuses []Status
Priorities []Priority
// Assignee is a team member's identifier, AssigneeNone for the tickets
// nobody took, or empty for every ticket.
Assignee string
// Tenant narrows the list to one space.
Tenant string
Sort string
Dir string
Page int
PerPage int
}
TeamQuery narrows the team's list. Page counts from one.
type TeamService ¶
type TeamService struct {
// contains filtered or unexported fields
}
TeamService orchestrates what the platform team does with the tickets of every space.
A member of the team is a subject of Config.TeamTenant, and TeamPolicy decides what they may do. The tickets are in the customers' tenants, so a use case here goes in two steps: it asks the policy for a Grant in the team's own realm, reads the list or finds the ticket's tenant with it, and then acts with a Grant scoped to that one tenant, which exists only because the first one was issued for the same action. Nothing a request names chooses the tenant: it is the one the ticket's row says.
func NewTeamService ¶
func NewTeamService(db *data.DB, cfg Config) *TeamService
NewTeamService wires the service over the application's database handle.
func (*TeamService) Assign ¶
func (s *TeamService) Assign(ctx context.Context, actor security.Subject, id, assigneeID string) (*Ticket, error)
Assign gives a ticket to a member of the team, and an empty identifier takes it back. With a Directory the member has to be on its list; without one a member can only take a ticket themselves.
func (*TeamService) Attachment ¶
func (s *TeamService) Attachment(ctx context.Context, actor security.Subject, ticketID, attachmentID string) (*Attachment, filesystem.File, error)
Attachment opens a file of any message of a ticket, the team's notes included. The caller closes the body. Reading it is recorded.
func (*TeamService) Find ¶
func (s *TeamService) Find(ctx context.Context, actor security.Subject, id string) (Conversation, error)
Find returns one conversation with its internal notes: the team reads it all.
func (*TeamService) List ¶
List returns a page of the tickets of every space: searched by number, subject and requester, filtered by status, priority, assignee and space, and ordered by the server.
func (*TeamService) Note ¶
func (s *TeamService) Note(ctx context.Context, actor security.Subject, id string, in ReplyRequest, uploads []Upload) (*Message, error)
Note writes a note only the team reads. It changes neither the status nor the moment of the last message, tells nobody, and its entry in the log says that a note was written and not what it says.
func (*TeamService) Reply ¶
func (s *TeamService) Reply(ctx context.Context, actor security.Subject, id string, in ReplyRequest, uploads []Upload) (*Message, error)
Reply answers the customer. The ticket waits for them, and they are told. A closed ticket takes no reply.
func (*TeamService) SetPriority ¶
func (s *TeamService) SetPriority(ctx context.Context, actor security.Subject, id string, next Priority) (*Ticket, error)
SetPriority changes how soon the team looks at a ticket.
type TeamShowPageData ¶
type TeamShowPageData struct {
Screen
Ticket TicketRow
Messages []MessageRow
BackURL string
ReplyURL string
NoteURL string
StatusURL string
PriorityURL string
AssignURL string
CanReply bool
CanNote bool
CanManage bool
CanAssign bool
StatusOptions []components.SelectOption
PriorityOptions []components.SelectOption
AssigneeOptions []components.SelectOption
Files bool
MaxFiles int
MaxMB string
Accept string
}
TeamShowPageData is one conversation, for the team, with its internal notes and the controls that change the ticket.
type Ticket ¶
type Ticket struct {
model.Model
// ID is the identifier. It is generated by the application rather than by
// the database, because a DEFAULT that produces a uuid is spelled
// differently in every engine.
ID string `db:"id"`
// TenantID is the space the ticket belongs to. It is written from the Grant
// and read back so the policy can compare it against the subject's tenant.
TenantID string `db:"tenant_id"`
// Number is the ticket's sequence in its tenant, from one. People say it as
// Code: "#000123".
Number int64 `db:"number"`
// Subject is the one line the customer gave the request.
Subject string `db:"subject"`
// Status is where the ticket stands.
Status Status `db:"status"`
// Priority is how soon the team looks at it. Only the team changes it.
Priority Priority `db:"priority"`
// Category is a short free text, or empty.
Category string `db:"category"`
// RequesterID is the user of the tenant who asked. The application owns
// what a user is; this package only compares the identifier with the
// subject's.
RequesterID string `db:"requester_id"`
// RequesterName and RequesterEmail are who the requester was when the
// ticket was opened, as the application's Directory or AccountResolver
// answered. They are a copy: the team searches by them and reads them
// without asking the application again.
RequesterName string `db:"requester_name"`
RequesterEmail string `db:"requester_email"`
// AssigneeID is the team member answering, or empty. The team lives in the
// platform's own tenant, so the identifier is a user of that tenant and the
// name is a copy of how the team knew them when they took the ticket.
AssigneeID string `db:"assignee_id"`
AssigneeName string `db:"assignee_name"`
// Origin is where the ticket came from.
Origin Origin `db:"origin"`
// OriginKey identifies a ticket opened from a contact form by what it said,
// so the same form sent twice opens it once. Empty for every other origin.
OriginKey string `db:"origin_key"`
// LastMessageAt is when the last message the customer can read was written.
LastMessageAt time.Time `db:"last_message_at"`
// CreatedAt is when the row was written, in UTC.
CreatedAt time.Time `db:"created_at"`
// UpdatedAt is when the row last changed, in UTC.
UpdatedAt time.Time `db:"updated_at"`
// ClosedAt is when the ticket was closed, nil while it is not.
ClosedAt *time.Time `db:"closed_at"`
}
Ticket is one request for support: a conversation between a customer of a tenant and the platform team.
It embeds the model, so a row returned by a query carries the connection and can be saved again. Build a new row with Tickets(db).New(): a struct literal has no connection and its write methods return model.ErrUnwired.
Tickets, TicketQuery and TicketCollection are generated beside this file, in TicketQuery.go, by aru model:build.
func (*Ticket) AddNote ¶
AddNote stamps a note only the team reads. A note moves nothing the customer can read: the status stays, and so does the moment of the last message.
func (Ticket) CanReopen ¶
CanReopen reports whether the ticket can be reopened: it is resolved or closed.
func (Ticket) CanReply ¶
CanReply reports whether a message can still be added: a closed ticket is reopened first.
func (*Ticket) Fresh ¶
Fresh reads the row again, as a new Ticket; it shadows the untyped one the embedded model promotes.
func (*Ticket) ReceiveCustomerReply ¶
ReceiveCustomerReply moves the ticket the way a reply from the customer does: a ticket nobody has answered stays open, any other becomes the team's to answer. A closed ticket refuses it.
func (*Ticket) ReceiveTeamReply ¶
ReceiveTeamReply moves the ticket the way an answer from the team does: it becomes the customer's to answer. A closed ticket refuses it.
func (*Ticket) Replicate ¶
Replicate returns an unsaved copy of the row without its key; it shadows the untyped one the embedded model promotes.
func (*Ticket) SetPriority ¶
SetPriority changes how soon the team looks at the ticket.
type TicketCollection ¶
type TicketCollection []*Ticket
TicketCollection is rows of Ticket, as a query returns them.
func TicketCollectionOf ¶
func TicketCollectionOf(rows model.Rows) TicketCollection
TicketCollectionOf converts rows of the table of Ticket. A nil input is a nil collection.
func (TicketCollection) Contains ¶
func (c TicketCollection) Contains(key any) bool
func (TicketCollection) Except ¶
func (c TicketCollection) Except(keys ...any) TicketCollection
func (TicketCollection) Find ¶
func (c TicketCollection) Find(key any) *Ticket
func (TicketCollection) FindOrFail ¶
func (c TicketCollection) FindOrFail(key any) (*Ticket, error)
func (TicketCollection) Fresh ¶
func (c TicketCollection) Fresh(ctx context.Context, g auth.Grant, with ...string) (TicketCollection, error)
func (TicketCollection) LoadMissing ¶
func (TicketCollection) ModelKeys ¶
func (c TicketCollection) ModelKeys() []any
func (TicketCollection) Only ¶
func (c TicketCollection) Only(keys ...any) TicketCollection
func (TicketCollection) Pluck ¶
func (c TicketCollection) Pluck(column string) []any
func (TicketCollection) Rows ¶
func (c TicketCollection) Rows() model.Rows
Rows converts the collection back to the core's rows.
func (TicketCollection) ToArray ¶
func (c TicketCollection) ToArray() []map[string]any
func (TicketCollection) Unique ¶
func (c TicketCollection) Unique() TicketCollection
type TicketPolicy ¶
type TicketPolicy struct{}
TicketPolicy is the only authority over what a customer does with a Ticket.
It denies unless a rule below says otherwise, and every rule reads what the subject was granted: a subject with no actions, an administrator included, is refused everything. Ownership decides the rest -- a customer reads and answers the tickets they asked for, and the whole space's only with TicketViewSpace -- and the tenant of the record is checked first, before any rule, so a rule that opens an action opens it inside one space only.
func (TicketPolicy) Can ¶
func (TicketPolicy) Can(_ context.Context, s security.Subject, a security.Action, record Ticket) error
Can decides whether the subject may perform the action on the record.
It is the only place that decides. The service reaches the Model only after Authorize turns this method's nil result into a Grant.
The record is the empty Ticket where the question is "may this subject do this kind of thing at all", and the loaded row where it is "may they do it to this ticket". Both are asked, in that order, and the second is what a rule about ownership answers.
type TicketQuery ¶
type TicketQuery struct {
// contains filtered or unexported fields
}
TicketQuery is a query on the table of Ticket.
The methods that build return the query, so they chain. The methods that run take a context and an auth.Grant, and return Ticket rows or a value; there is no way to run one without a Grant.
func Tickets ¶
func Tickets(db model.DB) *TicketQuery
Tickets starts a query on the table of Ticket, through db.
Global scopes are on and, unless the table is Global, every statement is filtered by the tenant of the Grant its terminal takes.
func (*TicketQuery) AddSelect ¶
func (q *TicketQuery) AddSelect(columns ...any) *TicketQuery
func (*TicketQuery) Base ¶
func (q *TicketQuery) Base() *model.Builder
Base returns the core builder the query forwards to. It is for code in this package -- a local scope in the custom block of model.go -- and for the factory, which builds rows through it.
func (*TicketQuery) Clone ¶
func (q *TicketQuery) Clone() *TicketQuery
Clone returns an independent copy of the query.
func (*TicketQuery) CursorPaginate ¶
func (q *TicketQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, opts pagination.Options, columns ...any) (TicketCollection, *pagination.CursorPage, error)
func (*TicketQuery) DoesntExist ¶
func (*TicketQuery) DoesntHave ¶
func (q *TicketQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *TicketQuery
func (*TicketQuery) FindMany ¶
func (q *TicketQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (TicketCollection, error)
func (*TicketQuery) FindOrFail ¶
func (*TicketQuery) FirstOrCreate ¶
func (*TicketQuery) FirstOrFail ¶
func (*TicketQuery) FirstOrNew ¶
func (*TicketQuery) FirstWhere ¶
func (*TicketQuery) ForPage ¶
func (q *TicketQuery) ForPage(page, perPage int) *TicketQuery
func (*TicketQuery) ForceCreate ¶
func (*TicketQuery) Get ¶
func (q *TicketQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (TicketCollection, error)
Get runs the query and returns the rows.
func (*TicketQuery) GroupBy ¶
func (q *TicketQuery) GroupBy(groups ...any) *TicketQuery
func (*TicketQuery) Has ¶
func (q *TicketQuery) Has(relation, operator string, count int, boolean string, callback func(*query.Builder)) *TicketQuery
func (*TicketQuery) InsertOrIgnore ¶
func (*TicketQuery) Join ¶
func (q *TicketQuery) Join(table any, first any, args ...any) *TicketQuery
func (*TicketQuery) Latest ¶
func (q *TicketQuery) Latest(column ...string) *TicketQuery
func (*TicketQuery) Limit ¶
func (q *TicketQuery) Limit(value int) *TicketQuery
func (*TicketQuery) New ¶
func (q *TicketQuery) New() (*Ticket, error)
New returns an empty Ticket on the query's connection, not yet saved, filled with what WithAttributes recorded on the query.
func (*TicketQuery) Offset ¶
func (q *TicketQuery) Offset(value int) *TicketQuery
func (*TicketQuery) Oldest ¶
func (q *TicketQuery) Oldest(column ...string) *TicketQuery
func (*TicketQuery) OrDoesntHave ¶
func (q *TicketQuery) OrDoesntHave(relation string) *TicketQuery
func (*TicketQuery) OrHas ¶
func (q *TicketQuery) OrHas(relation, operator string, count int) *TicketQuery
func (*TicketQuery) OrWhere ¶
func (q *TicketQuery) OrWhere(column any, args ...any) *TicketQuery
OrWhere adds an or-where clause; a func(*TicketQuery) in place of the column adds a group.
func (*TicketQuery) OrWhereDoesntHave ¶
func (q *TicketQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *TicketQuery
func (*TicketQuery) OrWhereHas ¶
func (q *TicketQuery) OrWhereHas(relation string, callback func(*query.Builder)) *TicketQuery
func (*TicketQuery) OrWhereNot ¶
func (q *TicketQuery) OrWhereNot(column any, args ...any) *TicketQuery
OrWhereNot adds an or-joined negated where clause, or a negated group.
func (*TicketQuery) OrWhereRelation ¶
func (q *TicketQuery) OrWhereRelation(relation string, column any, args ...any) *TicketQuery
func (*TicketQuery) OrderBy ¶
func (q *TicketQuery) OrderBy(column any, direction ...string) *TicketQuery
func (*TicketQuery) OrderByDesc ¶
func (q *TicketQuery) OrderByDesc(column any) *TicketQuery
func (*TicketQuery) Paginate ¶
func (q *TicketQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (TicketCollection, *pagination.LengthAwarePage, error)
func (*TicketQuery) Select ¶
func (q *TicketQuery) Select(columns ...any) *TicketQuery
func (*TicketQuery) SelectRaw ¶
func (q *TicketQuery) SelectRaw(expression string, bindings ...any) *TicketQuery
func (*TicketQuery) SimplePaginate ¶
func (q *TicketQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (TicketCollection, *pagination.Page, error)
func (*TicketQuery) UpdateOrCreate ¶
func (*TicketQuery) ValueOrFail ¶
func (*TicketQuery) Where ¶
func (q *TicketQuery) Where(column any, args ...any) *TicketQuery
Where adds a where clause; a func(*TicketQuery) in place of the column adds a group.
func (*TicketQuery) WhereBelongsTo ¶
func (q *TicketQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *TicketQuery
func (*TicketQuery) WhereBetween ¶
func (q *TicketQuery) WhereBetween(column any, from, to any) *TicketQuery
func (*TicketQuery) WhereColumn ¶
func (q *TicketQuery) WhereColumn(first any, args ...any) *TicketQuery
func (*TicketQuery) WhereDoesntHave ¶
func (q *TicketQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *TicketQuery
func (*TicketQuery) WhereExists ¶
func (q *TicketQuery) WhereExists(callback func(*query.Builder)) *TicketQuery
func (*TicketQuery) WhereHas ¶
func (q *TicketQuery) WhereHas(relation string, callback func(*query.Builder)) *TicketQuery
func (*TicketQuery) WhereHasCount ¶
func (q *TicketQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *TicketQuery
func (*TicketQuery) WhereIn ¶
func (q *TicketQuery) WhereIn(column any, values []any) *TicketQuery
func (*TicketQuery) WhereKey ¶
func (q *TicketQuery) WhereKey(id any) *TicketQuery
func (*TicketQuery) WhereNot ¶
func (q *TicketQuery) WhereNot(column any, args ...any) *TicketQuery
WhereNot adds a negated where clause, or a negated group.
func (*TicketQuery) WhereNotExists ¶
func (q *TicketQuery) WhereNotExists(callback func(*query.Builder)) *TicketQuery
func (*TicketQuery) WhereNotIn ¶
func (q *TicketQuery) WhereNotIn(column any, values []any) *TicketQuery
func (*TicketQuery) WhereNotNull ¶
func (q *TicketQuery) WhereNotNull(columns ...any) *TicketQuery
func (*TicketQuery) WhereNull ¶
func (q *TicketQuery) WhereNull(columns ...any) *TicketQuery
func (*TicketQuery) WhereRelation ¶
func (q *TicketQuery) WhereRelation(relation string, column any, args ...any) *TicketQuery
func (*TicketQuery) With ¶
func (q *TicketQuery) With(relations ...string) *TicketQuery
func (*TicketQuery) WithAvg ¶
func (q *TicketQuery) WithAvg(relation, column string) *TicketQuery
func (*TicketQuery) WithCount ¶
func (q *TicketQuery) WithCount(relations ...string) *TicketQuery
func (*TicketQuery) WithExists ¶
func (q *TicketQuery) WithExists(relation string) *TicketQuery
func (*TicketQuery) WithMax ¶
func (q *TicketQuery) WithMax(relation, column string) *TicketQuery
func (*TicketQuery) WithMin ¶
func (q *TicketQuery) WithMin(relation, column string) *TicketQuery
func (*TicketQuery) WithSum ¶
func (q *TicketQuery) WithSum(relation, column string) *TicketQuery
func (*TicketQuery) Without ¶
func (q *TicketQuery) Without(relations ...string) *TicketQuery
func (*TicketQuery) WithoutGlobalScope ¶
func (q *TicketQuery) WithoutGlobalScope(identifier string) *TicketQuery
func (*TicketQuery) WithoutGlobalScopes ¶
func (q *TicketQuery) WithoutGlobalScopes(identifiers ...string) *TicketQuery
type TicketRow ¶
type TicketRow struct {
ID, Code, Subject, Category string
Status, StatusKey string
Priority, PriorityKey string
Requester, RequesterEmail string
Assignee, AssigneeID string
Space, Origin string
Created, Updated string
// CreatedSort and UpdatedSort order like the instants they stand for.
CreatedSort, UpdatedSort string
URL string
Closed, Resolved bool
}
TicketRow is one ticket as a list or a conversation shows it.
type TicketService ¶
type TicketService struct {
// contains filtered or unexported fields
}
TicketService orchestrates what a customer does with tickets: it validates the request, asks the policy for a Grant, applies the rules the entity holds for itself, and persists with that Grant.
It receives its collaborators through the constructor. There is no container and no resolution by reflection: what this service is made of is written at the one place that builds it, and reading that place is how somebody learns what the package touches.
Everything a handler is allowed to do for a customer goes through here. The service is the only owner of the database handle on this side, so the request layer cannot reach a Model before the policy has answered.
func NewTicketService ¶
func NewTicketService(db *data.DB, cfg Config) *TicketService
NewTicketService wires the service over the application's database handle.
func (*TicketService) Attachment ¶
func (s *TicketService) Attachment(ctx context.Context, actor security.Subject, ticketID, attachmentID string) (*Attachment, filesystem.File, error)
Attachment opens a file of a message the customer may read. The caller closes the body. Reading it is recorded.
func (*TicketService) CanOpen ¶
CanOpen answers whether the subject may open a ticket, for the screen that draws the form. It asks the policy exactly as Open does, and the refusal it returns is the one Open would.
func (*TicketService) Close ¶
func (s *TicketService) Close(ctx context.Context, actor security.Subject, id string) (*Ticket, error)
Close closes a ticket the customer may read.
func (*TicketService) Find ¶
func (s *TicketService) Find(ctx context.Context, actor security.Subject, id string) (Conversation, error)
Find returns one conversation: the ticket and the messages the customer may read. An internal note is not among them, and nothing says one exists. A ticket of another space, and one of the same space the subject may not read, are not found.
func (*TicketService) List ¶
func (s *TicketService) List(ctx context.Context, actor security.Subject, q ListQuery) (Page, error)
List returns a page of the customer's tickets: their own, or the whole space's when the policy allows it, searched, filtered by status and ordered by the server.
func (*TicketService) Open ¶
func (s *TicketService) Open(ctx context.Context, actor security.Subject, in OpenRequest, uploads []Upload) (*Ticket, error)
Open opens a ticket for the signed-in customer, in their own space, with the first message and the files that came with it.
func (*TicketService) OpenFromContact ¶
func (s *TicketService) OpenFromContact(ctx context.Context, in ContactInput) (ContactResult, error)
OpenFromContact opens a ticket from the application's public contact form, and only for somebody who has an account.
The e-mail is asked of Config.Accounts. When it belongs to an account, the ticket opens in that account's space, with the user as its requester, the subject and the body as its first message and the contact form as its origin. When it does not, nothing is created and the answer is ErrNoAccount: the application keeps doing what it did with the form before, which is sending the e-mail.
The same e-mail, subject and body sent twice inside Config.ContactWindow open one ticket: the second answer is the first ticket, with Duplicate set. Two requests at the same instant are told apart by the tenant's counter row, so neither can slip past the other.
There is no session behind the form, so the Grant is the system's, scoped to the tenant the application's AccountResolver answered, and the sender is who the form says they are: anybody can type an address. The ticket therefore carries its origin, and a ticket the customer did not write is a ticket the customer can close. An application that must not accept a form from just anybody puts its own check -- a captcha, a rate limit -- in front of the call.
func (*TicketService) Reopen ¶
func (s *TicketService) Reopen(ctx context.Context, actor security.Subject, id string) (*Ticket, error)
Reopen brings a resolved or closed ticket back to the team.
func (*TicketService) Reply ¶
func (s *TicketService) Reply(ctx context.Context, actor security.Subject, id string, in ReplyRequest, uploads []Upload) (*Message, error)
Reply adds the customer's message to a ticket. A ticket nobody answered yet stays open; any other waits for the team. A closed ticket takes no reply: it is reopened first.
type URLs ¶
type URLs struct {
// contains filtered or unexported fields
}
URLs are the addresses of the routes under the module's configuration. A link is built from here and never written out a second time: under another Prefix the paths move and the names do not.
func NewURLs ¶
NewURLs are the addresses of the routes under a pair of prefixes and the segments, for a screen built outside a request: a test that draws a page, an e-mail that links to one. Empty values are the defaults.
func (URLs) Attachment ¶
Attachment is a file of a message, for the customer.
func (URLs) TeamAssign ¶
TeamAssign is where the team posts a new assignee.
func (URLs) TeamAttachment ¶
TeamAttachment is a file of a message, for the team.
func (URLs) TeamPriority ¶
TeamPriority is where the team posts a new priority.
func (URLs) TeamStatus ¶
TeamStatus is where the team posts a new status.
type Upload ¶
type Upload struct {
// Name is the file's name as the sender had it. Only its last element is
// kept.
Name string
// Body is the file's bytes. The package reads at most Config.MaxAttachmentBytes
// of it, and decides what it is by looking, not by what it is called.
Body io.Reader
}
Upload is a file sent with a message.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
platform
Package platform is the tickets table read without the tenant filter: the one read of arandu-ticket that crosses tenants, the team's list and the lookup that finds which tenant a ticket is in, after the team's policy allowed it.
|
Package platform is the tickets table read without the tenant filter: the one read of arandu-ticket that crosses tenants, the team's list and the lookup that finds which tenant a ticket is in, after the team's policy allowed it. |