controllers

package
v0.0.0-...-bf23570 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 25, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
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"
)
View Source
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"
)
View Source
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"
)
View Source
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"
)
View Source
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.

View Source
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

type Admin struct {
	IndexView *views.View
	// contains filtered or unexported fields
}

Admin holds the services behind the admin page and the archive download.

func NewAdmin

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

func (*Events) Show

func (e *Events) Show(w http.ResponseWriter, r *http.Request)

Show renders the details of an event.

GET /event/:id

func (*Events) Update

func (e *Events) Update(w http.ResponseWriter, r *http.Request)

Update processes the submission of an event edit form and saves the event.

POST /event/:id/update

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

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

func (*Fencers) Update

func (f *Fencers) Update(w http.ResponseWriter, r *http.Request)

Update is used to process the edit form and send updates to the models package. Only admins reach it; the route requires them.

POST /fencers/:id/update

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

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

func (*Imports) New

func (im *Imports) New(w http.ResponseWriter, r *http.Request)

New renders the roster upload form.

GET /import

func (*Imports) Template

func (im *Imports) Template(w http.ResponseWriter, r *http.Request)

Template serves the blank roster CSV template: the header row plus one example line, so an admin can fill it in and re-upload.

GET /import/template.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

type MatchForm struct {
	Bouts []models.Bout
}

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

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

func (*Matches) Show

func (m *Matches) Show(w http.ResponseWriter, r *http.Request)

Show renders the match show view.

GET /matches/:id

func (*Matches) Update

func (m *Matches) Update(w http.ResponseWriter, r *http.Request)

Update processes the match form and saves the bouts and scores to the database.

POST /matches/:id/update

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

type SchoolForm struct {
	ID   uint
	Name string `scheme:"name"`
}

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

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

func (*Schools) Show

func (s *Schools) Show(w http.ResponseWriter, r *http.Request)

Show is used to show a particular school's details.

GET /schools/:id

func (*Schools) Update

func (s *Schools) Update(w http.ResponseWriter, r *http.Request)

Update is used to process edit form submissions.

POST /schools/:id/update

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).

func (*Seeding) Export

func (s *Seeding) Export(w http.ResponseWriter, r *http.Request)

Export downloads a seeding: one weapon's fencers or squads, strongest first, as a CSV that Fencing Time (or other tournament software) can import.

GET /export/seeding.csv?category=wf&plurality=individuals

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

func (*Squads) Show

func (s *Squads) Show(w http.ResponseWriter, r *http.Request)

Show is used to show a particular squad's detials, including its fencers.

GET /squads/:id

type StandingsForm

type StandingsForm struct {
	InStandings bool   `schema:"in_standings"`
	Next        string `schema:"next"`
}

StandingsForm carries the new value for a school's standings participation.

type Static

type Static struct {
	Home    *views.View
	Contact *views.View
	Help    *views.View
}

func NewStatic

func NewStatic() *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) Logout

func (u *Users) Logout(w http.ResponseWriter, r *http.Request)

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL