| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- package web
- import (
- "net/http"
- "github.com/pocketbase/pocketbase/core"
- "code.osinet.fr/fgm/jamtrack/internal/query"
- "code.osinet.fr/fgm/jamtrack/views"
- )
- // Dashboard renders the practice-list ranking page.
- func Dashboard(e *core.RequestEvent) error {
- locationID := e.Request.URL.Query().Get("location")
- ranks, err := query.FetchRanking(e.App, locationID)
- if err != nil {
- return err
- }
- return views.Dashboard(ranks, locationID).Render(e.Request.Context(), e.Response)
- }
- // JamList renders the list of all jams.
- func JamList(e *core.RequestEvent) error {
- jams, err := e.App.FindRecordsByFilter("jams", "", "-date", -1, 0)
- if err != nil {
- return err
- }
- locs, err := e.App.FindRecordsByFilter("locations", "", "name", -1, 0)
- if err != nil {
- return err
- }
- locationNames := make(map[string]string, len(locs))
- for _, l := range locs {
- locationNames[l.Id] = l.GetString("name")
- }
- type row struct {
- JamID string `db:"jam"`
- Total int `db:"total"`
- Played int `db:"played"`
- }
- var counts []row
- if err := e.App.DB().
- NewQuery("SELECT jam, COUNT(*) AS total, SUM(played) AS played FROM setlist GROUP BY jam").
- All(&counts); err != nil {
- return err
- }
- songCounts := make(map[string]views.SongCount, len(counts))
- for _, r := range counts {
- songCounts[r.JamID] = views.SongCount{Played: r.Played, Total: r.Total}
- }
- return views.JamList(jams, locationNames, songCounts).Render(e.Request.Context(), e.Response)
- }
- // JamNew renders the new-jam form.
- func JamNew(e *core.RequestEvent) error {
- locations, err := e.App.FindRecordsByFilter("locations", "", "name", -1, 0)
- if err != nil {
- return err
- }
- return views.JamNew(locations).Render(e.Request.Context(), e.Response)
- }
- // JamCreate handles the new-jam form submission.
- func JamCreate(e *core.RequestEvent) error {
- collection, err := e.App.FindCollectionByNameOrId("jams")
- if err != nil {
- return err
- }
- record := core.NewRecord(collection)
- record.Set("date", e.Request.FormValue("date"))
- record.Set("location", e.Request.FormValue("location"))
- record.Set("notes", e.Request.FormValue("notes"))
- if err := e.App.Save(record); err != nil {
- return err
- }
- return e.Redirect(http.StatusFound, "/jams/"+record.Id)
- }
- // JamDetail renders the detail view for a single jam and its setlist.
- func JamDetail(e *core.RequestEvent) error {
- id := e.Request.PathValue("id")
- jam, err := e.App.FindRecordById("jams", id)
- if err != nil {
- return err
- }
- location, err := e.App.FindRecordById("locations", jam.GetString("location"))
- if err != nil {
- return err
- }
- setlistRecords, err := e.App.FindRecordsByFilter(
- "setlist", "jam={:jam}", "id", -1, 0,
- map[string]any{"jam": id},
- )
- if err != nil {
- return err
- }
- rows := make([]views.SetlistRow, 0, len(setlistRecords))
- for _, sl := range setlistRecords {
- song, err := e.App.FindRecordById("songs", sl.GetString("song"))
- if err != nil {
- return err
- }
- rows = append(rows, views.SetlistRow{
- ID: sl.Id,
- Artist: song.GetString("artist"),
- Title: song.GetString("title"),
- Played: sl.GetBool("played"),
- })
- }
- return views.JamDetail(jam, location, rows).Render(e.Request.Context(), e.Response)
- }
- // LocationList renders the locations management page.
- func LocationList(e *core.RequestEvent) error {
- locations, err := e.App.FindRecordsByFilter("locations", "", "name", -1, 0)
- if err != nil {
- return err
- }
- return views.LocationList(locations).Render(e.Request.Context(), e.Response)
- }
- // LocationCreate handles adding a new location.
- func LocationCreate(e *core.RequestEvent) error {
- collection, err := e.App.FindCollectionByNameOrId("locations")
- if err != nil {
- return err
- }
- record := core.NewRecord(collection)
- record.Set("name", e.Request.FormValue("name"))
- record.Set("notes", e.Request.FormValue("notes"))
- if err := e.App.Save(record); err != nil {
- return err
- }
- return e.Redirect(http.StatusFound, "/locations")
- }
- // LocationDetail renders per-location jam history and practice ranking.
- func LocationDetail(e *core.RequestEvent) error {
- id := e.Request.PathValue("id")
- loc, err := e.App.FindRecordById("locations", id)
- if err != nil {
- return err
- }
- jams, err := e.App.FindRecordsByFilter(
- "jams", "location={:loc}", "-date", -1, 0,
- map[string]any{"loc": id},
- )
- if err != nil {
- return err
- }
- ranks, err := query.FetchRanking(e.App, id)
- if err != nil {
- return err
- }
- return views.LocationDetail(loc, jams, ranks).Render(e.Request.Context(), e.Response)
- }
- // SongSearch is the HTMX typeahead endpoint. It dispatches on the "field"
- // query param: "title" returns title suggestions (filtered by artist when
- // provided); anything else returns distinct artist suggestions.
- func SongSearch(e *core.RequestEvent) error {
- if e.Request.URL.Query().Get("field") == "title" {
- return songTitleSearch(e)
- }
- return songArtistSearch(e)
- }
- func songArtistSearch(e *core.RequestEvent) error {
- q := e.Request.URL.Query()
- artistQ := "%" + q.Get("artist") + "%"
- title := q.Get("title")
- filter := "artist~{:artistQ}"
- params := map[string]any{"artistQ": artistQ}
- if title != "" {
- filter += " && title={:title}"
- params["title"] = title
- }
- records, err := e.App.FindRecordsByFilter("songs", filter, "artist", 20, 0, params)
- if err != nil {
- return err
- }
- seen := map[string]bool{}
- artists := make([]string, 0, len(records))
- for _, r := range records {
- a := r.GetString("artist")
- if !seen[a] {
- seen[a] = true
- artists = append(artists, a)
- }
- }
- return views.SongOptions(artists).Render(e.Request.Context(), e.Response)
- }
- func songTitleSearch(e *core.RequestEvent) error {
- q := e.Request.URL.Query()
- artist := q.Get("artist")
- titleQ := "%" + q.Get("title") + "%"
- filter := "title~{:titleQ}"
- params := map[string]any{"titleQ": titleQ}
- if artist != "" {
- filter = "artist={:artist} && " + filter
- params["artist"] = artist
- }
- records, err := e.App.FindRecordsByFilter("songs", filter, "title", 20, 0, params)
- if err != nil {
- return err
- }
- seen := map[string]bool{}
- titles := make([]string, 0, len(records))
- for _, r := range records {
- t := r.GetString("title")
- if !seen[t] {
- seen[t] = true
- titles = append(titles, t)
- }
- }
- return views.SongOptions(titles).Render(e.Request.Context(), e.Response)
- }
- // findOrCreateSong returns an existing song matching artist+title exactly, or
- // creates and saves a new one.
- func findOrCreateSong(app core.App, artist, title string) (*core.Record, error) {
- existing, err := app.FindRecordsByFilter(
- "songs", "artist={:artist} && title={:title}", "", 1, 0,
- map[string]any{"artist": artist, "title": title},
- )
- if err == nil && len(existing) > 0 {
- return existing[0], nil
- }
- col, err := app.FindCollectionByNameOrId("songs")
- if err != nil {
- return nil, err
- }
- song := core.NewRecord(col)
- song.Set("artist", artist)
- song.Set("title", title)
- if err := app.Save(song); err != nil {
- return nil, err
- }
- return song, nil
- }
- // SetlistAdd adds a song to a jam's setlist (creating the song if it doesn't
- // exist) and returns the new setlist row fragment.
- func SetlistAdd(e *core.RequestEvent) error {
- jamID := e.Request.PathValue("id")
- artist := e.Request.FormValue("artist")
- title := e.Request.FormValue("title")
- song, err := findOrCreateSong(e.App, artist, title)
- if err != nil {
- return err
- }
- played := e.Request.FormValue("played") == "on"
- // Create the setlist entry.
- slCol, err := e.App.FindCollectionByNameOrId("setlist")
- if err != nil {
- return err
- }
- sl := core.NewRecord(slCol)
- sl.Set("jam", jamID)
- sl.Set("song", song.Id)
- sl.Set("played", played)
- if err := e.App.Save(sl); err != nil {
- return err
- }
- row := views.SetlistRow{
- ID: sl.Id,
- Artist: artist,
- Title: title,
- Played: played,
- }
- return views.SetlistRowFrag(jamID, row).Render(e.Request.Context(), e.Response)
- }
- // SetlistView returns the display row fragment for a setlist entry (used by
- // the Cancel button in the inline edit form).
- func SetlistView(e *core.RequestEvent) error {
- id := e.Request.PathValue("id")
- sl, err := e.App.FindRecordById("setlist", id)
- if err != nil {
- return err
- }
- song, err := e.App.FindRecordById("songs", sl.GetString("song"))
- if err != nil {
- return err
- }
- row := views.SetlistRow{
- ID: sl.Id,
- Artist: song.GetString("artist"),
- Title: song.GetString("title"),
- Played: sl.GetBool("played"),
- }
- return views.SetlistRowFrag(sl.GetString("jam"), row).Render(e.Request.Context(), e.Response)
- }
- // SetlistEditForm returns the inline edit form fragment for a setlist entry.
- func SetlistEditForm(e *core.RequestEvent) error {
- id := e.Request.PathValue("id")
- sl, err := e.App.FindRecordById("setlist", id)
- if err != nil {
- return err
- }
- song, err := e.App.FindRecordById("songs", sl.GetString("song"))
- if err != nil {
- return err
- }
- row := views.SetlistRow{
- ID: sl.Id,
- Artist: song.GetString("artist"),
- Title: song.GetString("title"),
- Played: sl.GetBool("played"),
- }
- return views.SetlistEditFrag(row).Render(e.Request.Context(), e.Response)
- }
- // SetlistUpdate updates the artist, title, and played status of a setlist
- // entry and returns the updated row fragment.
- func SetlistUpdate(e *core.RequestEvent) error {
- id := e.Request.PathValue("id")
- artist := e.Request.FormValue("artist")
- title := e.Request.FormValue("title")
- played := e.Request.FormValue("played") == "on"
- sl, err := e.App.FindRecordById("setlist", id)
- if err != nil {
- return err
- }
- song, err := findOrCreateSong(e.App, artist, title)
- if err != nil {
- return err
- }
- sl.Set("song", song.Id)
- sl.Set("played", played)
- if err := e.App.Save(sl); err != nil {
- return err
- }
- row := views.SetlistRow{
- ID: sl.Id,
- Artist: artist,
- Title: title,
- Played: played,
- }
- return views.SetlistRowFrag(sl.GetString("jam"), row).Render(e.Request.Context(), e.Response)
- }
- // SetlistToggle toggles the played bool on a setlist entry and returns the
- // updated row fragment.
- func SetlistToggle(e *core.RequestEvent) error {
- id := e.Request.PathValue("id")
- sl, err := e.App.FindRecordById("setlist", id)
- if err != nil {
- return err
- }
- sl.Set("played", !sl.GetBool("played"))
- if err := e.App.Save(sl); err != nil {
- return err
- }
- song, err := e.App.FindRecordById("songs", sl.GetString("song"))
- if err != nil {
- return err
- }
- row := views.SetlistRow{
- ID: sl.Id,
- Artist: song.GetString("artist"),
- Title: song.GetString("title"),
- Played: sl.GetBool("played"),
- }
- return views.SetlistRowFrag(sl.GetString("jam"), row).Render(e.Request.Context(), e.Response)
- }
- // SetlistDelete removes a song from a jam's setlist.
- func SetlistDelete(e *core.RequestEvent) error {
- id := e.Request.PathValue("id")
- sl, err := e.App.FindRecordById("setlist", id)
- if err != nil {
- return err
- }
- return e.App.Delete(sl)
- }
|