Documentation ¶
Index ¶
- Constants
- Variables
- func CanDeleteDialMembership(ctx context.Context, membership *DialMembership) bool
- func CanEditDial(ctx context.Context, dial *Dial) bool
- func CanEditDialMembership(ctx context.Context, membership *DialMembership) bool
- func ErrorCode(err error) string
- func ErrorMessage(err error) string
- func FlashFromContext(ctx context.Context) string
- func NewContextWithFlash(ctx context.Context, v string) context.Context
- func NewContextWithUser(ctx context.Context, user *User) context.Context
- func UserIDFromContext(ctx context.Context) int
- type Auth
- type AuthFilter
- type AuthService
- type Dial
- type DialFilter
- type DialMembership
- type DialMembershipFilter
- type DialMembershipService
- type DialMembershipUpdate
- type DialMembershipValueChangedPayload
- type DialService
- type DialUpdate
- type DialValueChangedPayload
- type DialValueRecord
- type DialValueReport
- type Error
- type Event
- type EventService
- type Subscription
- type User
- type UserFilter
- type UserService
- type UserUpdate
Constants ¶
const ( ECONFLICT = "conflict" EINTERNAL = "internal" EINVALID = "invalid" ENOTFOUND = "not_found" ENOTIMPLEMENTED = "not_implemented" EUNAUTHORIZED = "unauthorized" )
Application error codes.
NOTE: These are meant to be generic and they map well to HTTP error codes. Different applications can have very different error code requirements so these should be expanded as needed (or introduce subcodes).
const ( EventTypeDialValueChanged = "dial:value_changed" EventTypeDialMembershipValueChanged = "dial_membership:value_changed" )
Event type constants.
const (
AuthSourceGitHub = "github"
)
Authentication providers. Currently we only support GitHub but any OAuth provider could be supported.
const (
DialMembershipSortByUpdatedAtDesc = "updated_at_desc"
)
Dial membership sort options. Only specific sorting options are supported.
const (
MaxDialNameLen = 100
)
Dial constants.
Variables ¶
var ( Version string Commit string )
Build version & commit SHA.
var ReportError = func(ctx context.Context, err error, args ...interface{}) {}
ReportError notifies an external service of errors. No-op by default.
var ReportPanic = func(err interface{}) {}
ReportPanic notifies an external service of panics. No-op by default.
Functions ¶
func CanDeleteDialMembership ¶
func CanDeleteDialMembership(ctx context.Context, membership *DialMembership) bool
CanDeleteDialMembership returns true if the current user can delete membership.
func CanEditDial ¶
CanEditDial returns true if the current user can edit the dial. Only the dial owner can edit the dial.
func CanEditDialMembership ¶
func CanEditDialMembership(ctx context.Context, membership *DialMembership) bool
CanEditDialMembership returns true if the current user can edit membership.
func ErrorCode ¶
ErrorCode unwraps an application error and returns its code. Non-application errors always return EINTERNAL.
func ErrorMessage ¶
ErrorMessage unwraps an application error and returns its message. Non-application errors always return "Internal error".
func FlashFromContext ¶
FlashFromContext returns the flash value for the current request.
func NewContextWithFlash ¶
NewContextWithFlash returns a new context with the given flash value.
func NewContextWithUser ¶
NewContextWithUser returns a new context with the given user.
func UserIDFromContext ¶
UserIDFromContext is a helper function that returns the ID of the current logged in user. Returns zero if no user is logged in.
Types ¶
type Auth ¶
type Auth struct { ID int `json:"id"` // User can have one or more methods of authentication. // However, only one per source is allowed per user. UserID int `json:"userID"` User *User `json:"user"` // The authentication source & the source provider's user ID. // Source can only be "github" currently. Source string `json:"source"` SourceID string `json:"sourceID"` // OAuth fields returned from the authentication provider. // GitHub does not use refresh tokens but the field exists for future providers. AccessToken string `json:"-"` RefreshToken string `json:"-"` Expiry *time.Time `json:"-"` // Timestamps of creation & last update. CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` }
Auth represents a set of OAuth credentials. These are linked to a User so a single user could authenticate through multiple providers.
The authentication system links users by email address, however, some GitHub users don't provide their email publicly so we may not be able to link them by email address. It's a moot point, however, as we only support GitHub as an OAuth provider.
type AuthFilter ¶
type AuthFilter struct { // Filtering fields. ID *int `json:"id"` UserID *int `json:"userID"` Source *string `json:"source"` SourceID *string `json:"sourceID"` // Restricts results to a subset of the total range. // Can be used for pagination. Offset int `json:"offset"` Limit int `json:"limit"` }
AuthFilter represents a filter accepted by FindAuths().
type AuthService ¶
type AuthService interface { // Looks up an authentication object by ID along with the associated user. // Returns ENOTFOUND if ID does not exist. FindAuthByID(ctx context.Context, id int) (*Auth, error) // Retrieves authentication objects based on a filter. Also returns the // total number of objects that match the filter. This may differ from the // returned object count if the Limit field is set. FindAuths(ctx context.Context, filter AuthFilter) ([]*Auth, int, error) // Creates a new authentication object If a User is attached to auth, then // the auth object is linked to an existing user. Otherwise a new user // object is created. // // On success, the auth.ID is set to the new authentication ID. CreateAuth(ctx context.Context, auth *Auth) error // Permanently deletes an authentication object from the system by ID. // The parent user object is not removed. DeleteAuth(ctx context.Context, id int) error }
AuthService represents a service for managing auths.
type Dial ¶
type Dial struct { ID int `json:"id"` // Owner of the dial. Only the owner may delete the dial. UserID int `json:"userID"` User *User `json:"user"` // Human-readable name of the dial. Name string `json:"name"` // Code used to share the dial with other users. // It allows the creation of a shareable link without explicitly inviting users. InviteCode string `json:"inviteCode,omitempty"` // Aggregate WTF level for the dial. This is a computed field based on the // average value of each member's WTF level. Value int `json:"value"` // Timestamps for dial creation & last update. CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` // List of associated members and their contributing WTF level. // This is only set when returning a single dial. Memberships []*DialMembership `json:"memberships,omitempty"` }
Dial represents an aggregate WTF level. They are used to roll up the WTF levels of multiple members and show an average WTF level.
A dial is created by a user and can only be edited & deleted by the user who created it. Members can be added by sharing an invite link and accepting the invitation.
The WTF level for the dial will immediately change when a member's WTF level changes and the change will be announced to all other members in real-time.
See the EventService for more information about notifications.
func (*Dial) MembershipByUserID ¶
func (d *Dial) MembershipByUserID(userID int) *DialMembership
MembershipByUserID returns the membership attached to the dial for a given user. Returns nil if user is not associated with the dial or if memberships is unset.
type DialFilter ¶
type DialFilter struct { // Filtering fields. ID *int `json:"id"` InviteCode *string `json:"inviteCode"` // Restrict to subset of range. Offset int `json:"offset"` Limit int `json:"limit"` }
DialFilter represents a filter used by FindDials().
type DialMembership ¶
type DialMembership struct { ID int `json:"id"` // Parent dial. This dial's WTF level updates when a membership updates. DialID int `json:"dialID"` Dial *Dial `json:"dial"` // Owner of the membership. Only this user can update the membership. UserID int `json:"userID"` User *User `json:"user"` // Current WTF level for the user for this dial. // Updating this value will cause the parent dial's WTF level to be recomputed. Value int `json:"value"` // Timestamps for membership creation & last update. CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` }
DialMembership represents a contributor to a Dial. Each membership is aggregated to determine the total WTF value of the parent dial.
All members can view all other member's values in the dial. However, only the membership owner can edit the membership value.
func (*DialMembership) Validate ¶
func (m *DialMembership) Validate() error
Validate returns an error if membership fields are invalid. Only performs basic validation.
type DialMembershipFilter ¶
type DialMembershipFilter struct { ID *int `json:"id"` DialID *int `json:"dialID"` UserID *int `json:"userID"` // Restricts to a subset of the results. Offset int `json:"offset"` Limit int `json:"limit"` // Sorting option for results. SortBy string `json:"sortBy"` }
DialMembershipFilter represents a filter used by FindDialMemberships().
type DialMembershipService ¶
type DialMembershipService interface { // Retrieves a membership by ID along with the associated dial & user. // Returns ENOTFOUND if membership does exist or user does not have // permission to view it. FindDialMembershipByID(ctx context.Context, id int) (*DialMembership, error) // Retrieves a list of matching memberships based on filter. Only returns // memberships that belong to dials that the current user is a member of. // Also returns a count of total matching memberships which may different if // "Limit" is specified on the filter. FindDialMemberships(ctx context.Context, filter DialMembershipFilter) ([]*DialMembership, int, error) // Creates a new membership on a dial for the current user. Returns // EUNAUTHORIZED if there is no current user logged in. CreateDialMembership(ctx context.Context, membership *DialMembership) error // Updates the value of a membership. Only the owner of the membership can // update the value. Returns EUNAUTHORIZED if user is not the owner. Returns // ENOTFOUND if the membership does not exist. UpdateDialMembership(ctx context.Context, id int, upd DialMembershipUpdate) (*DialMembership, error) // Permanently deletes a membership by ID. Only the membership owner and // the parent dial's owner can delete a membership. DeleteDialMembership(ctx context.Context, id int) error }
DialMembershipService represents a service for managing dial memberships.
type DialMembershipUpdate ¶
type DialMembershipUpdate struct {
Value *int `json:"value"`
}
DialMembershipUpdate represents a set of fields to update on a membership.
type DialMembershipValueChangedPayload ¶
DialMembershipValueChangedPayload represents the payload for an Event object with a type of EventTypeDialMembershipValueChanged.
type DialService ¶
type DialService interface { // Retrieves a single dial by ID along with associated memberships. Only // the dial owner & members can see a dial. Returns ENOTFOUND if dial does // not exist or user does not have permission to view it. FindDialByID(ctx context.Context, id int) (*Dial, error) // Retrieves a list of dials based on a filter. Only returns dials that // the user owns or is a member of. Also returns a count of total matching // dials which may different from the number of returned dials if the // "Limit" field is set. FindDials(ctx context.Context, filter DialFilter) ([]*Dial, int, error) // Creates a new dial and assigns the current user as the owner. // The owner will automatically be added as a member of the new dial. CreateDial(ctx context.Context, dial *Dial) error // Updates an existing dial by ID. Only the dial owner can update a dial. // Returns the new dial state even if there was an error during update. // // Returns ENOTFOUND if dial does not exist. Returns EUNAUTHORIZED if user // is not the dial owner. UpdateDial(ctx context.Context, id int, upd DialUpdate) (*Dial, error) // Permanently removes a dial by ID. Only the dial owner may delete a dial. // Returns ENOTFOUND if dial does not exist. Returns EUNAUTHORIZED if user // is not the dial owner. DeleteDial(ctx context.Context, id int) error // Sets the value of the user's membership in a dial. This works the same // as calling UpdateDialMembership() although it doesn't require that the // user know their membership ID. Only the dial ID. // // Returns ENOTFOUND if the membership does not exist. SetDialMembershipValue(ctx context.Context, dialID, value int) error // AverageDialValueReport returns a report of the average dial value across // all dials that the user is a member of. Average values are computed // between start & end time and are slotted into given intervals. The // minimum interval size is one minute. AverageDialValueReport(ctx context.Context, start, end time.Time, interval time.Duration) (*DialValueReport, error) }
DialService represents a service for managing dials.
type DialUpdate ¶
type DialUpdate struct {
Name *string `json:"name"`
}
DialUpdate represents a set of fields to update on a dial.
type DialValueChangedPayload ¶
DialValueChangedPayload represents the payload for an Event object with a type of EventTypeDialValueChanged.
type DialValueRecord ¶
DialValueRecord represents an average dial value at a given point in time for the DialValueReport.
func (*DialValueRecord) GoString ¶
func (r *DialValueRecord) GoString() string
GoString prints a more easily readable representation for debugging. The timestamp field is represented as an RFC 3339 string instead of a pointer.
type DialValueReport ¶
type DialValueReport struct {
Records []*DialValueRecord
}
DialValueReport represents a report generated by AverageDialValueReport(). Each record represents the average value within an interval of time.
type Error ¶
type Error struct { // Machine-readable error code. Code string // Human-readable error message. Message string }
Error represents an application-specific error. Application errors can be unwrapped by the caller to extract out the code & message.
Any non-application error (such as a disk error) should be reported as an EINTERNAL error and the human user should only see "Internal error" as the message. These low-level internal error details should only be logged and reported to the operator of the application (not the end user).
type Event ¶
type Event struct { // Specifies the type of event that is occurring. Type string `json:"type"` // The actual data from the event. See related payload types below. Payload interface{} `json:"payload"` }
Event represents an event that occurs in the system. Currently there are only events for changes to a dial value or membership value. These events are eventually propagated out to connected users via WebSockets whenever changes occur so that the UI can update in real-time.
type EventService ¶
type EventService interface { // Publishes an event to a user's event listeners. // If the user is not currently subscribed then this is a no-op. PublishEvent(userID int, event Event) // Creates a subscription for the current user's events. // Caller must call Subscription.Close() when done with the subscription. Subscribe(ctx context.Context) (Subscription, error) }
EventService represents a service for managing event dispatch and event listeners (aka subscriptions).
Events are user-centric in this implementation although a more generic implementation may use a topic-centic model (e.g. "dial_value_changed(id=1)"). The application has frequent reconnects so it's more efficient to subscribe for a single user instead of resubscribing to all their related topics.
func NopEventService ¶
func NopEventService() EventService
NopEventService returns an event service that does nothing.
type Subscription ¶
type Subscription interface { // Event stream for all user's event. C() <-chan Event // Closes the event stream channel and disconnects from the event service. Close() error }
Subscription represents a stream of events for a single user.
type User ¶
type User struct { ID int `json:"id"` // User's preferred name & email. Name string `json:"name"` Email string `json:"email"` // Randomly generated API key for use with the CLI. APIKey string `json:"-"` // Timestamps for user creation & last update. CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` // List of associated OAuth authentication objects. // Currently only GitHub is supported so there should only be a maximum of one. Auths []*Auth `json:"auths"` }
User represents a user in the system. Users are typically created via OAuth using the AuthService but users can also be created directly for testing.
func UserFromContext ¶
UserFromContext returns the current logged in user.
type UserFilter ¶
type UserFilter struct { // Filtering fields. ID *int `json:"id"` Email *string `json:"email"` APIKey *string `json:"apiKey"` // Restrict to subset of results. Offset int `json:"offset"` Limit int `json:"limit"` }
UserFilter represents a filter passed to FindUsers().
type UserService ¶
type UserService interface { // Retrieves a user by ID along with their associated auth objects. // Returns ENOTFOUND if user does not exist. FindUserByID(ctx context.Context, id int) (*User, error) // Retrieves a list of users by filter. Also returns total count of matching // users which may differ from returned results if filter.Limit is specified. FindUsers(ctx context.Context, filter UserFilter) ([]*User, int, error) // Creates a new user. This is only used for testing since users are typically // created during the OAuth creation process in AuthService.CreateAuth(). CreateUser(ctx context.Context, user *User) error // Updates a user object. Returns EUNAUTHORIZED if current user is not // the user that is being updated. Returns ENOTFOUND if user does not exist. UpdateUser(ctx context.Context, id int, upd UserUpdate) (*User, error) // Permanently deletes a user and all owned dials. Returns EUNAUTHORIZED // if current user is not the user being deleted. Returns ENOTFOUND if // user does not exist. DeleteUser(ctx context.Context, id int) error }
UserService represents a service for managing users.
type UserUpdate ¶
UserUpdate represents a set of fields to be updated via UpdateUser().