Documentation
¶
Index ¶
- Constants
- func RedirectRankings(w http.ResponseWriter, r *http.Request)
- func RedirectStandings(w http.ResponseWriter, r *http.Request)
- type Admin
- type CompleteRegisterForm
- type EventForm
- type Events
- func (e *Events) Create(w http.ResponseWriter, r *http.Request)
- func (e *Events) Delete(w http.ResponseWriter, r *http.Request)
- func (e *Events) Edit(w http.ResponseWriter, r *http.Request)
- func (e *Events) Index(w http.ResponseWriter, r *http.Request)
- func (e *Events) Show(w http.ResponseWriter, r *http.Request)
- func (e *Events) Update(w http.ResponseWriter, r *http.Request)
- type FencerForm
- type Fencers
- func (f *Fencers) Activate(w http.ResponseWriter, r *http.Request)
- func (f *Fencers) Create(w http.ResponseWriter, r *http.Request)
- func (f *Fencers) Deactivate(w http.ResponseWriter, r *http.Request)
- func (f *Fencers) Delete(w http.ResponseWriter, r *http.Request)
- func (f *Fencers) Edit(w http.ResponseWriter, r *http.Request)
- func (f *Fencers) Index(w http.ResponseWriter, r *http.Request)
- func (f *Fencers) Merge(w http.ResponseWriter, r *http.Request)
- func (f *Fencers) New(w http.ResponseWriter, r *http.Request)
- func (f *Fencers) Show(w http.ResponseWriter, r *http.Request)
- func (f *Fencers) ShowMerge(w http.ResponseWriter, r *http.Request)
- func (f *Fencers) Update(w http.ResponseWriter, r *http.Request)
- type Imports
- func (im *Imports) Create(w http.ResponseWriter, r *http.Request)
- func (im *Imports) ExportEventResults(w http.ResponseWriter, r *http.Request)
- func (im *Imports) ExportFencers(w http.ResponseWriter, r *http.Request)
- func (im *Imports) ExportStandings(w http.ResponseWriter, r *http.Request)
- func (im *Imports) New(w http.ResponseWriter, r *http.Request)
- func (im *Imports) Template(w http.ResponseWriter, r *http.Request)
- type LoadMatchForm
- type LoginForm
- type LogoutForm
- type MatchForm
- type Matches
- func (m *Matches) Create(w http.ResponseWriter, r *http.Request)
- func (m *Matches) Delete(w http.ResponseWriter, r *http.Request)
- func (m *Matches) Edit(w http.ResponseWriter, r *http.Request)
- func (m *Matches) New(w http.ResponseWriter, r *http.Request)
- func (m *Matches) Show(w http.ResponseWriter, r *http.Request)
- func (m *Matches) Update(w http.ResponseWriter, r *http.Request)
- type MergeForm
- type NewMatchForm
- type RegisterForm
- type ResetPwForm
- type SchoolForm
- type Schools
- func (s *Schools) Create(w http.ResponseWriter, r *http.Request)
- func (s *Schools) Delete(w http.ResponseWriter, r *http.Request)
- func (s *Schools) Edit(w http.ResponseWriter, r *http.Request)
- func (s *Schools) Index(w http.ResponseWriter, r *http.Request)
- func (s *Schools) SetStandings(w http.ResponseWriter, r *http.Request)
- func (s *Schools) Show(w http.ResponseWriter, r *http.Request)
- func (s *Schools) Update(w http.ResponseWriter, r *http.Request)
- type Seeding
- type SeedingQuery
- type Squads
- type StandingsForm
- type Static
- type Users
- func (u *Users) CompleteRegistration(w http.ResponseWriter, r *http.Request)
- func (u *Users) CompleteReset(w http.ResponseWriter, r *http.Request)
- func (u *Users) Create(w http.ResponseWriter, r *http.Request)
- func (u *Users) Demote(w http.ResponseWriter, r *http.Request)
- func (u *Users) InitiateReset(w http.ResponseWriter, r *http.Request)
- func (u *Users) Login(w http.ResponseWriter, r *http.Request)
- func (u *Users) Logout(w http.ResponseWriter, r *http.Request)
- func (u *Users) New(w http.ResponseWriter, r *http.Request)
- func (u *Users) Promote(w http.ResponseWriter, r *http.Request)
- func (u *Users) ResetPw(w http.ResponseWriter, r *http.Request)
- func (u *Users) ShowComplete(w http.ResponseWriter, r *http.Request)
- func (u *Users) ShowLogin(w http.ResponseWriter, r *http.Request)
Constants ¶
const ( // IndexEvents is the name of the events index route. IndexEvents = "index_events" // ShowEvent is the name of the event show route. ShowEvent = "show_event" // EditEvent is the name of the event edit route. EditEvent = "edit_event" )
const ( // IndexFencers is the name of the index fencers route. IndexFencers = "index_fencers" // ShowFencer is the name of the show fencer route. ShowFencer = "show_fencer" // EditFencer is the name of the edit fencer route. EditFencer = "edit_fencer" )
const ( // ShowMatch is the name of the show match route. ShowMatch = "show_match" // EditMatch is the name of the edit match route. EditMatch = "edit_match" )
const ( // IndexSchools is the name of the schools index route. IndexSchools = "index_schools" // ShowSchool is the name of the school show route. ShowSchool = "show_school" // EditSchool is the name of the school edit route. EditSchool = "edit_school" )
const DefaultMailgunAPIKey = "your-api-key"
DefaultMailgunAPIKey is the placeholder the default config ships with. An instance still carrying it cannot send mail, which is worth saying plainly on the admin page rather than leaving to be discovered by a failed reset.
const (
// ShowSquad is the name of the squad show route.
ShowSquad = "show_squad"
)
Variables ¶
This section is empty.
Functions ¶
func RedirectRankings ¶
func RedirectRankings(w http.ResponseWriter, r *http.Request)
RedirectRankings sends /rankings to the same weapon's tab of the fencers page. The rankings listed this season's fencers unless ?span=all asked for everyone, and showed the first weapon when none was given.
GET /rankings
func RedirectStandings ¶
func RedirectStandings(w http.ResponseWriter, r *http.Request)
RedirectStandings sends /standings to the same season on the squads page when it names a weapon, and otherwise to the schools page, which shows every weapon at once, as the standings page did.
GET /standings
Types ¶
type Admin ¶
Admin holds the services behind the admin page and the archive download.
func NewAdmin ¶
func NewAdmin( cfg config.Config, us models.UserService, fs models.FencerService, ss models.SchoolService, sqs models.SquadService, es models.EventService, ms models.MatchService, bs models.BoutService, ) *Admin
NewAdmin returns an *Admin controller.
It takes the whole config but keeps only the settings that are safe to show, so the secrets in it cannot reach a template however the view is later changed. See instanceInfo.
func (*Admin) Archive ¶
func (a *Admin) Archive(w http.ResponseWriter, r *http.Request)
Archive streams the whole conference as a zip of CSV files.
This is the answer to "what happens to our data if bouts.app stops being maintained": an admin can take a complete, readable copy at any time, in a format any spreadsheet opens, without anyone needing shell access to the server. It deliberately omits the users table — that holds email addresses and password hashes, and none of it is conference data.
GET /admin/archive.zip
func (*Admin) Index ¶
func (a *Admin) Index(w http.ResponseWriter, r *http.Request)
Index renders one section of the admin page: the data-hygiene lists, the tools that are otherwise scattered across the app, the standings toggles, the invites that have not been used, or what this instance is running.
The page used to render all of them at once. With a full roster that is a 200KB wall of tables to scroll through, and every section's queries ran whether or not anyone read it. Only the section being shown is fetched now, which leaves three of the five costing no queries at all.
GET /admin
type CompleteRegisterForm ¶
type CompleteRegisterForm struct {
Name string `schema:"name"`
Password string `schema:"password"`
Token string `schema:"token"`
}
CompleteRegisterForm is the second step of signing up, where the invite's token is exchanged for an account. There is no email field: the address comes from the invite the token names, so it cannot be substituted here.
type EventForm ¶
type EventForm struct {
Name string `scheme:"name"`
Date string `scheme:"date"`
Rounds uint `scheme:"rounds"`
}
EventForm holds and maps data from the new event form.
type Events ¶
type Events struct {
IndexView *views.View
New *views.View
ShowView *views.View
EditView *views.View
// contains filtered or unexported fields
}
Events holds a models.EventService and a mux.Router.
func NewEvents ¶
func NewEvents(es models.EventService, r *mux.Router) *Events
NewEvents returns a *Events, for use as an Events controller.
func (*Events) Create ¶
func (e *Events) Create(w http.ResponseWriter, r *http.Request)
Create proceses the new event form and creates a new event.
POST /event
func (*Events) Delete ¶
func (e *Events) Delete(w http.ResponseWriter, r *http.Request)
Delete removes an event from the database.
POST /event/:id/delete
func (*Events) Edit ¶
func (e *Events) Edit(w http.ResponseWriter, r *http.Request)
Edit renders the details of an event in the event form for editing.
GET /event/:id/edit
func (*Events) Index ¶
func (e *Events) Index(w http.ResponseWriter, r *http.Request)
Index renders the events index page with all of the events.
GET /events
type FencerForm ¶
type FencerForm struct {
Name string `schema:"name"`
SchoolID uint `schema:"school"`
Category models.Category `schema:"category"`
SchoolOptions []models.School
CategoryOptions [6]models.Category
}
FencerForm holds and maps data from the new fencer form.
type Fencers ¶
type Fencers struct {
NewView *views.View
IndexView *views.View
ShowView *views.View
EditView *views.View
MergeView *views.View
// contains filtered or unexported fields
}
Fencers holds a models.FencerService, a mux.Router, and all of the fencer-related views.
func NewFencers ¶
func NewFencers(fs models.FencerService, sqs models.SquadService, ss models.SchoolService, es models.EventService, seeding models.SeedingService, minBouts int, r *mux.Router) *Fencers
NewFencers returns a *Fencers, for use as a FencersController (e.g., for routing). minBouts is the championship qualification minimum, used to compute fencer status.
func (*Fencers) Activate ¶
func (f *Fencers) Activate(w http.ResponseWriter, r *http.Request)
Activate marks a fencer active for the current season, then redirects to the fencer's edit page with a confirmation.
POST /fencers/:id/activate
func (*Fencers) Create ¶
func (f *Fencers) Create(w http.ResponseWriter, r *http.Request)
Create is used to process the new fencer form when a user tries to create a new fencer.
POST /fencers
func (*Fencers) Deactivate ¶
func (f *Fencers) Deactivate(w http.ResponseWriter, r *http.Request)
Deactivate clears a fencer's active mark for the current season, then redirects to the fencer's edit page with a confirmation.
POST /fencers/:id/deactivate
func (*Fencers) Delete ¶
func (f *Fencers) Delete(w http.ResponseWriter, r *http.Request)
Delete is used to trigger deletion of a particular fencer in the models package. Only admins reach it; the route requires them.
POST /fencers/:id/delete
func (*Fencers) Edit ¶
func (f *Fencers) Edit(w http.ResponseWriter, r *http.Request)
Edit is used to edit a particular fencer's details.
GET /fencers/:id/edit
func (*Fencers) Index ¶
func (f *Fencers) Index(w http.ResponseWriter, r *http.Request)
Index lists the fencers. The All tab lists every fencer by school, split into those active this season and the rest, each with a status. Each weapon's tab ranks that weapon's fencers instead, and adds their record; this was the rankings page.
The filter in the query narrows either list; see fencerFilter. Its category is the tab, and its season comes from the season list: this season (the fencers active this season), a past season (the fencers who fenced in it) or all time (everyone). A weapon's tab ranks this season and all time by rating, and a past season by its record, since the rating is today's.
GET /fencers
func (*Fencers) Merge ¶
func (f *Fencers) Merge(w http.ResponseWriter, r *http.Request)
Merge folds one fencer into another and sends the user to the record that survived.
POST /fencers/:id/merge
func (*Fencers) New ¶
func (f *Fencers) New(w http.ResponseWriter, r *http.Request)
New is used to render the new fencer form.
GET /fencers/new
func (*Fencers) Show ¶
func (f *Fencers) Show(w http.ResponseWriter, r *http.Request)
Show is used to show a particular fencer's details, including the bouts in which the fencer has participated.
GET /fencers/:id
func (*Fencers) ShowMerge ¶
func (f *Fencers) ShowMerge(w http.ResponseWriter, r *http.Request)
ShowMerge renders the merge page for the fencer to be kept.
Without a ?merge= it lists the fencers that could be folded in; with one it shows both records side by side so the choice can be checked before it is made. Merging cannot be undone from the app, so it is deliberately two steps.
GET /fencers/:id/merge
type Imports ¶
type Imports struct {
NewView *views.View
ResultView *views.View
// contains filtered or unexported fields
}
Imports holds the services and views for roster import and data export.
func NewImports ¶
func NewImports( ris models.RosterImportService, fs models.FencerService, sqs models.SquadService, es models.EventService, ms models.MatchService, ) *Imports
NewImports returns an *Imports controller wired to the services it needs to import a roster and to build the data exports.
func (*Imports) Create ¶
func (im *Imports) Create(w http.ResponseWriter, r *http.Request)
Create processes an uploaded roster CSV. The whole file is validated first: if any row is invalid, nothing is written and every bad row is reported; otherwise the roster is applied idempotently and a summary is shown.
POST /import
func (*Imports) ExportEventResults ¶
func (im *Imports) ExportEventResults(w http.ResponseWriter, r *http.Request)
ExportEventResults streams the bout-by-bout results for a single event as a downloadable CSV.
GET /events/{id}/export.csv
func (*Imports) ExportFencers ¶
func (im *Imports) ExportFencers(w http.ResponseWriter, r *http.Request)
ExportFencers streams the fencer roster with school, category, and W/L record as a downloadable CSV. It takes the fencers page's filter, so the page's export link downloads the list the page shows, and the school and squad pages offer their own rosters through it.
GET /export/fencers.csv
func (*Imports) ExportStandings ¶
func (im *Imports) ExportStandings(w http.ResponseWriter, r *http.Request)
ExportStandings streams per-squad (school × category) win/loss/draw records as a downloadable CSV. The ?season parameter picks the span the way the standings page does, so the page's export link downloads the season on the screen. Without it the export covers all time, as it always has.
It lists every squad whose school counts toward standings, in the order the squads are stored, including squads with no matches (at 0-0-0); the page leaves those out.
GET /export/standings.csv
type LoadMatchForm ¶
type LoadMatchForm struct {
ID uint
Event *models.Event
Round uint `schema:"round"`
Category models.Category
SquadLeft *models.Squad
SquadRight *models.Squad
// ScoreLeft and ScoreRight are the match's bout tallies, tallied and stored
// when the match is saved, so the show view can head the bout table with the
// result instead of leaving the reader to count the rows.
ScoreLeft uint
ScoreRight uint
Bouts []models.Bout
ShowAll bool `schema:"all"`
FencersLeft []models.Fencer
FencersRight []models.Fencer
FencersLeftActive []models.Fencer
FencersLeftOther []models.Fencer
FencersRightActive []models.Fencer
FencersRightOther []models.Fencer
}
LoadMatchForm contains the data needed to render the match form.
In the default (active-only) mode, FencersLeft/FencersRight hold the flat list of fencers offered in each side's dropdowns. When ShowAll is set, the dropdowns instead render two groups per side: the active fencers (…Active) and everyone else on the squad (…Other).
type LoginForm ¶
type LoginForm struct {
Email string `schema:"email"`
Password string `schema:"password"`
// Next is the page the user was trying to reach when they were asked to log
// in, carried through the form so signing in lands them there. It is only
// ever used via safeRedirect, which rejects anything off-site.
Next string `schema:"next"`
}
LoginForm contains the data used to log in a user via the LoginView.
type LogoutForm ¶
type LogoutForm struct {
Next string `schema:"next"`
}
Logout is used to delete a user's session cookie and invalidate their current remember token, which will sign-out the current user.
POST /logout LogoutForm carries the page the user was on when they logged out, so they can be put back there instead of on the home page. Like LoginForm.Next it is only ever used via safeRedirect, which rejects anything off-site.
type MatchForm ¶
MatchForm contains the data submitted by the match form, used by the Update action.
type Matches ¶
type Matches struct {
NewView *views.View
ShowView *views.View
EditView *views.View
// contains filtered or unexported fields
}
Matches holds a models.MatchService, a *mux.Router, and all of the match-related views.
func NewMatches ¶
func NewMatches( ms models.MatchService, ss models.SchoolService, sqs models.SquadService, es models.EventService, bs models.BoutService, fs models.FencerService, r *mux.Router, ) *Matches
NewMatches returns a *Matches, for use as a Matches controller.
func (*Matches) Create ¶
func (m *Matches) Create(w http.ResponseWriter, r *http.Request)
Create processes the new match form and saves the match.
POST /matches
func (*Matches) Delete ¶
func (m *Matches) Delete(w http.ResponseWriter, r *http.Request)
Delete is used to delete a particular match.
POST /matches/:id/delete
func (*Matches) Edit ¶
func (m *Matches) Edit(w http.ResponseWriter, r *http.Request)
Edit renders the match form, where match details and scores are entered.
GET /matches/:id/edit
func (*Matches) New ¶
func (m *Matches) New(w http.ResponseWriter, r *http.Request)
New renders the new match form.
GET /matches/new
type MergeForm ¶
type MergeForm struct {
MergeID uint `schema:"merge"`
}
MergeForm carries the fencer to be folded into the one named in the URL.
The fencer in the URL is the one kept, so a merge reads the way it is started: from the record you want to survive, you pick the duplicate to absorb.
type NewMatchForm ¶
type NewMatchForm struct {
Events []models.Event
EventID uint `schema:"event"`
Round uint `schema:"round"`
Categories [6]models.Category
Category models.Category `schema:"category"`
Schools []models.School
SchoolIDLeft uint `schema:"school_left"`
SchoolIDRight uint `schema:"school_right"`
}
NewMatchForm is used to load data for the New action and process for the Create action.
type RegisterForm ¶
type RegisterForm struct {
Email string `schema:"email"`
}
RegisterForm is the address someone wants to sign up with. Registration asks for nothing else: the name and password are collected on the second step, after the address has been shown to be readable by whoever is signing up.
type ResetPwForm ¶
type ResetPwForm struct {
Email string `schema:"email"`
Token string `schema:"token"`
Password string `schema:"password"`
}
ResetPwForm is used to process the forgot password form // and the reset password form.
type SchoolForm ¶
SchoolForm holds and maps data from the new school form.
type Schools ¶
type Schools struct {
IndexView *views.View
New *views.View
ShowView *views.View
EditView *views.View
// contains filtered or unexported fields
}
Schools holds a models.SchoolService, a models.SquadService (to manage squads), a mux.Router, and all of the school-related views.
func NewSchools ¶
func NewSchools(ss models.SchoolService, sqs models.SquadService, es models.EventService, r *mux.Router) *Schools
NewSchools returns a *Schools, for use as a SchoolsControllers.
func (*Schools) Create ¶
func (s *Schools) Create(w http.ResponseWriter, r *http.Request)
Create is used to process a new school form submission to create a new school and the associated squads.
POST /schools
func (*Schools) Delete ¶
func (s *Schools) Delete(w http.ResponseWriter, r *http.Request)
Delete is used to delete a school. Only admins reach it; the route requires them.
POST /schools/:id/delete
func (*Schools) Edit ¶
func (s *Schools) Edit(w http.ResponseWriter, r *http.Request)
Edit is used to edit a particular school's details using the edit form. Only admins reach it; the route requires them.
GET /schools/:id/edit
func (*Schools) Index ¶
func (s *Schools) Index(w http.ResponseWriter, r *http.Request)
Index lists every school with its squads' records in one season: a row per school and a column per weapon, each cell a squad's wins, losses and draws. A school with no matches in the season, or out of standings, still has its row. The ?season parameter selects the span, as on the squads page.
GET /schools
func (*Schools) SetStandings ¶
func (s *Schools) SetStandings(w http.ResponseWriter, r *http.Request)
SetStandings turns a school's participation in conference standings on or off.
It is its own route rather than a field on the edit form for two reasons: the edit form is open to admins while this is superadmin-only, and it follows the fencer activate/deactivate precedent, where a state change that reaches beyond the record itself gets an explicit action rather than riding along with a name change.
POST /schools/:id/standings
type Seeding ¶
type Seeding struct {
// contains filtered or unexported fields
}
Seeding holds the seeding service.
func NewSeeding ¶
func NewSeeding(ss models.SeedingService) *Seeding
NewSeeding returns a *Seeding, for use as a SeedingController (e.g., for routing).
type SeedingQuery ¶
type SeedingQuery struct {
Category models.Category `schema:"category"`
Plurality models.Plurality `schema:"plurality"`
}
SeedingQuery holds the query of a seeding export link.
type Squads ¶
type Squads struct {
IndexView *views.View
ShowView *views.View
// contains filtered or unexported fields
}
Squads holds the services the squad pages read, and their views.
func NewSquads ¶
func NewSquads(sqs models.SquadService, fs models.FencerService, es models.EventService, seeding models.SeedingService, minBouts int) *Squads
NewSquads returns a *Squads, for use as a Squads controller.
func (*Squads) Index ¶
func (s *Squads) Index(w http.ResponseWriter, r *http.Request)
Index shows one weapon's standings: its squads strongest first, and for the current season and all time each squad's rating, the sum of its three best qualified fencers' ratings, which the squad seeding uses too. The rating is always today's, so it is left off past seasons, where it would not describe the squad that fenced them. Squads with no matches in the span are left out. Every weapon at once is on the schools page.
The ?category parameter selects the weapon; nothing, or a value that does not parse, is the first. The ?season parameter selects the span: "all" for all time, or a starting year (e.g. 2025) for that season. It defaults to the current season.
GET /squads
type StandingsForm ¶
StandingsForm carries the new value for a school's standings participation.
type Static ¶
func (*Static) HomeHandler ¶
func (s *Static) HomeHandler(w http.ResponseWriter, r *http.Request)
HomeHandler serves the marketing page to visitors who aren't logged in, and sends logged-in users straight to the events page: someone who already has an account has no use for the pitch and pricing, and events is where their conference's season lives.
GET /
type Users ¶
type Users struct {
NewView *views.View
CompleteView *views.View
LoginView *views.View
ForgotPwView *views.View
ResetPwView *views.View
// contains filtered or unexported fields
}
Users holds a models.UserService and all of the user-related views.
func NewUsers ¶
func NewUsers(us models.UserService, secure bool) *Users
NewUsers returns *Users, for use as a Users controller. secure marks the session cookie Secure, so the browser only sends it over HTTPS; production serves nothing else.
func (*Users) CompleteRegistration ¶
func (u *Users) CompleteRegistration(w http.ResponseWriter, r *http.Request)
CompleteRegistration creates the account for a registration invite and signs the new user in.
POST /register/complete
func (*Users) CompleteReset ¶
func (u *Users) CompleteReset(w http.ResponseWriter, r *http.Request)
CompleteReset processed the reset password form
POST /reset
func (*Users) Create ¶
func (u *Users) Create(w http.ResponseWriter, r *http.Request)
Create processes the registration form. It emails either an invite or a "you already have an account" note, and answers identically either way.
POST /register
func (*Users) Demote ¶
func (u *Users) Demote(w http.ResponseWriter, r *http.Request)
Demote makes an admin a user.
POST /demote/{email}
func (*Users) InitiateReset ¶
func (u *Users) InitiateReset(w http.ResponseWriter, r *http.Request)
POST /forgot
func (*Users) Login ¶
func (u *Users) Login(w http.ResponseWriter, r *http.Request)
Login is used to process the login form when a user tries to log in as an existing user with email and password.
POST /login
func (*Users) New ¶
func (u *Users) New(w http.ResponseWriter, r *http.Request)
New renders the form where someone can ask to register an email address.
GET /register
func (*Users) Promote ¶
func (u *Users) Promote(w http.ResponseWriter, r *http.Request)
Promote makes a user an admin.
POST /promote/{email}
func (*Users) ResetPw ¶
func (u *Users) ResetPw(w http.ResponseWriter, r *http.Request)
ResetPw displays the reset password form and has a method so that we can prefill the form data with a token provided via the URL query params.
GET /reset
func (*Users) ShowComplete ¶
func (u *Users) ShowComplete(w http.ResponseWriter, r *http.Request)
ShowComplete renders the second step of signing up, where an invited address chooses a name and password.
GET /register/complete
func (*Users) ShowLogin ¶
func (u *Users) ShowLogin(w http.ResponseWriter, r *http.Request)
ShowLogin renders the login form, prefilling the "next" page from the URL query so the form can carry it through the POST. A user who is already logged in is sent straight on rather than shown a login form they don't need.
GET /login