home to your local SPACEGIRL 💫 arimelody.space
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

i think that's all the api endpoints!

Signed-off-by: ari melody <ari@arimelody.me>

+810 -231
+7
admin/http.go
··· 39 39 Tracks []*musicModel.Track 40 40 } 41 41 42 + var orphan_tracks = []*musicModel.Track{} 43 + for _, track := range global.Tracks { 44 + if track.Release == nil { 45 + orphan_tracks = append(orphan_tracks, track) 46 + } 47 + } 48 + 42 49 serveTemplate("index.html", IndexData{ 43 50 Releases: global.Releases, 44 51 Artists: global.Artists,
+57 -16
api/api.go
··· 10 10 func Handler() http.Handler { 11 11 mux := http.NewServeMux() 12 12 13 - mux.Handle("/v1/artist/", http.StripPrefix("/v1/artist", ServeArtist())) 13 + // ARTIST ENDPOINTS 14 + 15 + mux.Handle("/v1/artist/", http.StripPrefix("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 + switch r.Method { 17 + case http.MethodGet: 18 + // GET /api/v1/artist/{id} 19 + ServeArtist().ServeHTTP(w, r) 20 + case http.MethodPut: 21 + // PUT /api/v1/artist/{id} (admin) 22 + admin.MustAuthorise(UpdateArtist()).ServeHTTP(w, r) 23 + case http.MethodDelete: 24 + // DELETE /api/v1/artist/{id} (admin) 25 + admin.MustAuthorise(DeleteArtist()).ServeHTTP(w, r) 26 + default: 27 + http.NotFound(w, r) 28 + } 29 + }))) 14 30 mux.Handle("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 31 switch r.Method { 16 32 case http.MethodGet: 33 + // GET /api/v1/artist 17 34 ServeAllArtists().ServeHTTP(w, r) 18 - return 19 35 case http.MethodPost: 36 + // POST /api/v1/artist (admin) 20 37 admin.MustAuthorise(CreateArtist()).ServeHTTP(w, r) 21 - return 22 38 default: 23 39 http.NotFound(w, r) 24 - return 25 40 } 26 41 })) 27 42 43 + // RELEASE ENDPOINTS 44 + 28 45 mux.Handle("/v1/music/", http.StripPrefix("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 29 46 switch r.Method { 30 47 case http.MethodGet: 48 + // GET /api/v1/music/{id} 31 49 music.ServeRelease().ServeHTTP(w, r) 32 - return 50 + case http.MethodPut: 51 + // PUT /api/v1/music/{id} (admin) 52 + admin.MustAuthorise(UpdateRelease()).ServeHTTP(w, r) 33 53 case http.MethodDelete: 54 + // DELETE /api/v1/music/{id} (admin) 34 55 admin.MustAuthorise(DeleteRelease()).ServeHTTP(w, r) 35 - return 36 56 default: 37 57 http.NotFound(w, r) 38 - return 39 58 } 40 59 }))) 41 60 mux.Handle("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 42 61 switch r.Method { 43 62 case http.MethodGet: 63 + // GET /api/v1/music 44 64 ServeCatalog().ServeHTTP(w, r) 45 - return 46 65 case http.MethodPost: 66 + // POST /api/v1/music (admin) 47 67 admin.MustAuthorise(CreateRelease()).ServeHTTP(w, r) 48 - return 68 + default: 69 + http.NotFound(w, r) 70 + } 71 + })) 72 + 73 + // TRACK ENDPOINTS 74 + 75 + mux.Handle("/v1/track/", http.StripPrefix("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 76 + switch r.Method { 77 + case http.MethodGet: 78 + // GET /api/v1/track/{id} (admin) 79 + admin.MustAuthorise(ServeTrack()).ServeHTTP(w, r) 80 + case http.MethodPut: 81 + // PUT /api/v1/track/{id} (admin) 82 + admin.MustAuthorise(UpdateTrack()).ServeHTTP(w, r) 49 83 case http.MethodDelete: 50 - admin.MustAuthorise(DeleteRelease()).ServeHTTP(w, r) 51 - return 84 + // DELETE /api/v1/track/{id} (admin) 85 + admin.MustAuthorise(DeleteTrack()).ServeHTTP(w, r) 52 86 default: 53 87 http.NotFound(w, r) 54 - return 88 + } 89 + }))) 90 + mux.Handle("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 91 + switch r.Method { 92 + case http.MethodGet: 93 + // GET /api/v1/track (admin) 94 + admin.MustAuthorise(ServeAllTracks()).ServeHTTP(w, r) 95 + case http.MethodPost: 96 + // POST /api/v1/track (admin) 97 + admin.MustAuthorise(CreateTrack()).ServeHTTP(w, r) 98 + default: 99 + http.NotFound(w, r) 55 100 } 56 101 })) 57 - 58 - mux.Handle("/v1/musiccredit", CreateMusicCredit()) 59 - mux.Handle("/v1/musiclink", CreateMusicLink()) 60 - mux.Handle("/v1/track", CreateTrack()) 61 102 62 103 return mux 63 104 }
+110 -28
api/artist.go
··· 12 12 13 13 func ServeAllArtists() http.Handler { 14 14 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 - type ( 16 - creditJSON struct { 17 - Role string `json:"role"` 18 - Primary bool `json:"primary"` 19 - } 20 - ) 21 - 22 - var artists = []model.Artist{} 23 - for _, artist := range global.Artists { 24 - artists = append(artists, model.Artist{ 25 - ID: artist.ID, 26 - Name: artist.Name, 27 - Website: artist.Website, 28 - Avatar: artist.Avatar, 29 - }) 30 - } 31 - 32 15 w.Header().Add("Content-Type", "application/json") 33 - err := json.NewEncoder(w).Encode(artists) 16 + err := json.NewEncoder(w).Encode(global.Artists) 34 17 if err != nil { 35 18 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 36 19 return ··· 55 38 Credits map[string]creditJSON `json:"credits"` 56 39 } 57 40 ) 58 - var res = artistJSON{} 41 + var artist = artistJSON{} 59 42 60 - res.ID = r.URL.Path[1:] 61 - var artist = global.GetArtist(res.ID) 62 - if artist == nil { 43 + artist.ID = r.URL.Path[1:] 44 + var a = global.GetArtist(artist.ID) 45 + if a == nil { 63 46 http.NotFound(w, r) 64 47 return 65 48 } 66 - res.Name = artist.Name 67 - res.Website = artist.Website 68 - res.Credits = make(map[string]creditJSON) 49 + artist.Name = a.Name 50 + artist.Website = a.Website 51 + artist.Credits = make(map[string]creditJSON) 69 52 70 53 for _, release := range global.Releases { 71 54 for _, credit := range release.Credits { 72 - if credit.Artist.ID != res.ID { 55 + if credit.Artist.ID != artist.ID { 73 56 continue 74 57 } 75 - res.Credits[release.ID] = creditJSON{ 58 + artist.Credits[release.ID] = creditJSON{ 76 59 Role: credit.Role, 77 60 Primary: credit.Primary, 78 61 } ··· 80 63 } 81 64 82 65 w.Header().Add("Content-Type", "application/json") 83 - err := json.NewEncoder(w).Encode(res) 66 + err := json.NewEncoder(w).Encode(artist) 84 67 if err != nil { 85 68 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 86 69 return ··· 136 119 w.Header().Add("Content-Type", "application/json") 137 120 w.WriteHeader(http.StatusCreated) 138 121 err = json.NewEncoder(w).Encode(artist) 122 + if err != nil { 123 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 124 + return 125 + } 126 + }) 127 + } 128 + 129 + func UpdateArtist() http.Handler { 130 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 131 + if r.Method != http.MethodPut { 132 + http.NotFound(w, r) 133 + return 134 + } 135 + 136 + if r.URL.Path == "/" { 137 + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 138 + return 139 + } 140 + 141 + var data model.Artist 142 + err := json.NewDecoder(r.Body).Decode(&data) 143 + if err != nil { 144 + fmt.Printf("Failed to update artist: %s\n", err) 145 + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 146 + return 147 + } 148 + 149 + var artistID = r.URL.Path[1:] 150 + var artist = global.GetArtist(artistID) 151 + if artist == nil { 152 + http.Error(w, fmt.Sprintf("Artist %s does not exist\n", artistID), http.StatusBadRequest) 153 + return 154 + } 155 + 156 + if data.ID == "" { data.ID = artist.ID } 157 + 158 + if data.Name == "" { 159 + http.Error(w, "Artist name cannot be blank\n", http.StatusBadRequest) 160 + return 161 + } 162 + 163 + err = controller.UpdateArtistDB(global.DB, &data) 164 + if err != nil { 165 + fmt.Printf("Failed to update artist %s: %s\n", artist.ID, err) 166 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 167 + return 168 + } 169 + 170 + artist.ID = data.ID 171 + artist.Name = data.Name 172 + artist.Website = data.Website 173 + artist.Avatar = data.Avatar 174 + 175 + w.Header().Add("Content-Type", "application/json") 176 + err = json.NewEncoder(w).Encode(artist) 177 + if err != nil { 178 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 179 + return 180 + } 181 + }) 182 + } 183 + 184 + func DeleteArtist() http.Handler { 185 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 186 + if r.Method != http.MethodDelete { 187 + http.NotFound(w, r) 188 + return 189 + } 190 + 191 + if r.URL.Path == "/" { 192 + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 193 + return 194 + } 195 + 196 + var artistID = r.URL.Path[1:] 197 + var artist = global.GetArtist(artistID) 198 + if artist == nil { 199 + http.Error(w, fmt.Sprintf("Artist %s does not exist\n", artistID), http.StatusBadRequest) 200 + return 201 + } 202 + 203 + err := controller.DeleteArtistDB(global.DB, artist) 204 + if err != nil { 205 + fmt.Printf("Failed to delete artist %s: %s\n", artist.ID, err) 206 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 207 + return 208 + } 209 + 210 + global.Artists = func () []*model.Artist { 211 + var artists = []*model.Artist{} 212 + for _, a := range global.Artists { 213 + if a.ID == artist.ID { continue } 214 + artists = append(artists, a) 215 + } 216 + return artists 217 + }() 218 + 219 + w.WriteHeader(http.StatusOK) 220 + w.Write([]byte(fmt.Sprintf("Artist %s has been deleted\n", artist.ID))) 139 221 }) 140 222 }
-65
api/musiccredit.go
··· 1 - package api 2 - 3 - import ( 4 - "encoding/json" 5 - "fmt" 6 - "net/http" 7 - 8 - "arimelody.me/arimelody.me/global" 9 - "arimelody.me/arimelody.me/music/model" 10 - controller "arimelody.me/arimelody.me/music/controller" 11 - ) 12 - 13 - func CreateMusicCredit() http.Handler { 14 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 - if r.Method != http.MethodPost { 16 - http.NotFound(w, r) 17 - return 18 - } 19 - 20 - type creditJSON struct { 21 - Release string 22 - Artist string 23 - Role string 24 - Primary bool 25 - } 26 - 27 - var data creditJSON 28 - err := json.NewDecoder(r.Body).Decode(&data) 29 - if err != nil { 30 - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 31 - return 32 - } 33 - 34 - var release = global.GetRelease(data.Release) 35 - if release == nil { 36 - http.Error(w, fmt.Sprintf("Release %s does not exist\n", data.Release), http.StatusBadRequest) 37 - return 38 - } 39 - 40 - var artist = global.GetArtist(data.Artist) 41 - if artist == nil { 42 - http.Error(w, fmt.Sprintf("Artist %s does not exist\n", data.Artist), http.StatusBadRequest) 43 - return 44 - } 45 - 46 - var credit = model.Credit{ 47 - Artist: artist, 48 - Role: data.Role, 49 - Primary: data.Primary, 50 - } 51 - 52 - err = controller.CreateCreditDB(global.DB, release.ID, artist.ID, &credit) 53 - if err != nil { 54 - fmt.Printf("Failed to create credit: %s\n", err) 55 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 56 - return 57 - } 58 - 59 - release.Credits = append(release.Credits, &credit) 60 - 61 - w.Header().Add("Content-Type", "application/json") 62 - w.WriteHeader(http.StatusCreated) 63 - err = json.NewEncoder(w).Encode(credit) 64 - }) 65 - }
-66
api/musiclink.go
··· 1 - package api 2 - 3 - import ( 4 - "encoding/json" 5 - "fmt" 6 - "net/http" 7 - 8 - "arimelody.me/arimelody.me/global" 9 - "arimelody.me/arimelody.me/music/model" 10 - controller "arimelody.me/arimelody.me/music/controller" 11 - ) 12 - 13 - func CreateMusicLink() http.Handler { 14 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 - if r.Method != http.MethodPost { 16 - http.NotFound(w, r) 17 - return 18 - } 19 - 20 - type linkJSON struct { 21 - Release string 22 - Name string 23 - URL string 24 - } 25 - 26 - var data linkJSON 27 - err := json.NewDecoder(r.Body).Decode(&data) 28 - if err != nil { 29 - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 30 - return 31 - } 32 - 33 - if data.Release == "" { 34 - http.Error(w, "Release cannot be empty\n", http.StatusBadRequest) 35 - return 36 - } 37 - if data.Name == "" { 38 - http.Error(w, "Link name cannot be empty\n", http.StatusBadRequest) 39 - return 40 - } 41 - 42 - var release = global.GetRelease(data.Release) 43 - if release == nil { 44 - http.Error(w, fmt.Sprintf("Release %s does not exist\n", data.Release), http.StatusBadRequest) 45 - return 46 - } 47 - 48 - var link = model.Link{ 49 - Name: data.Name, 50 - URL: data.URL, 51 - } 52 - 53 - err = controller.CreateLinkDB(global.DB, release.ID, &link) 54 - if err != nil { 55 - fmt.Printf("Failed to create link: %s\n", err) 56 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 57 - return 58 - } 59 - 60 - release.Links = append(release.Links, &link) 61 - 62 - w.Header().Add("Content-Type", "application/json") 63 - w.WriteHeader(http.StatusCreated) 64 - err = json.NewEncoder(w).Encode(release.Links) 65 - }) 66 - }
+288 -27
api/release.go
··· 4 4 "encoding/json" 5 5 "fmt" 6 6 "net/http" 7 + "strings" 7 8 "time" 8 9 9 10 "arimelody.me/arimelody.me/admin" 10 11 "arimelody.me/arimelody.me/global" 11 - "arimelody.me/arimelody.me/music/model" 12 12 controller "arimelody.me/arimelody.me/music/controller" 13 + "arimelody.me/arimelody.me/music/model" 13 14 ) 14 15 16 + type releaseBodyJSON struct { 17 + ID string `json:"id"` 18 + Visible bool `json:"visible"` 19 + Title string `json:"title"` 20 + Description string `json:"description"` 21 + ReleaseType model.ReleaseType `json:"type"` 22 + ReleaseDate time.Time `json:"releaseDate"` 23 + Artwork string `json:"artwork"` 24 + Buyname string `json:"buyname"` 25 + Buylink string `json:"buylink"` 26 + } 27 + 15 28 func ServeCatalog() http.Handler { 16 29 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 - releases := []*model.Release{} 30 + type CatalogItem struct { 31 + ID string `json:"id"` 32 + Title string `json:"title"` 33 + Description string `json:"description"` 34 + ReleaseType model.ReleaseType `json:"type"` 35 + ReleaseDate time.Time `json:"releaseDate"` 36 + Artwork string `json:"artwork"` 37 + Buyname string `json:"buyname"` 38 + Buylink string `json:"buylink"` 39 + Links []*model.Link `json:"links"` 40 + } 41 + 42 + catalog := []CatalogItem{} 18 43 authorised := admin.GetSession(r) != nil 19 44 for _, release := range global.Releases { 20 - if !release.IsReleased() && !authorised { 45 + if !release.Visible && !authorised { 21 46 continue 22 47 } 23 - releases = append(releases, release) 48 + catalog = append(catalog, CatalogItem{ 49 + ID: release.ID, 50 + Title: release.Title, 51 + Description: release.Description, 52 + ReleaseType: release.ReleaseType, 53 + ReleaseDate: release.ReleaseDate, 54 + Artwork: release.Artwork, 55 + Buyname: release.Buyname, 56 + Buylink: release.Buylink, 57 + Links: release.Links, 58 + }) 24 59 } 25 60 26 61 w.Header().Add("Content-Type", "application/json") 27 - err := json.NewEncoder(w).Encode(releases) 62 + err := json.NewEncoder(w).Encode(catalog) 28 63 if err != nil { 29 64 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 30 65 return ··· 39 74 return 40 75 } 41 76 42 - type PostReleaseBody struct { 43 - ID string `json:"id"` 44 - Visible bool `json:"visible"` 45 - Title string `json:"title"` 46 - Description string `json:"description"` 47 - ReleaseType model.ReleaseType `json:"type"` 48 - ReleaseDate time.Time `json:"releaseDate"` 49 - Artwork string `json:"artwork"` 50 - Buyname string `json:"buyname"` 51 - Buylink string `json:"buylink"` 52 - } 53 - 54 - var data PostReleaseBody 77 + var data releaseBodyJSON 55 78 err := json.NewDecoder(r.Body).Decode(&data) 56 79 if err != nil { 57 80 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) ··· 66 89 http.Error(w, "Release title cannot be empty\n", http.StatusBadRequest) 67 90 return 68 91 } 69 - if data.ReleaseDate.Unix() == 0 { 70 - http.Error(w, "Release date cannot be empty or 0\n", http.StatusBadRequest) 71 - return 72 - } 73 92 74 93 if global.GetRelease(data.ID) != nil { 75 94 http.Error(w, fmt.Sprintf("Release %s already exists\n", data.ID), http.StatusBadRequest) ··· 110 129 }) 111 130 } 112 131 113 - func DeleteRelease() http.Handler { 132 + func UpdateRelease() http.Handler { 133 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 134 + if r.URL.Path == "/" { 135 + http.NotFound(w, r) 136 + return 137 + } 138 + 139 + segments := strings.Split(r.URL.Path[1:], "/") 140 + var releaseID = segments[0] 141 + var release = global.GetRelease(releaseID) 142 + if release == nil { 143 + http.Error(w, fmt.Sprintf("Release %s does not exist\n", releaseID), http.StatusBadRequest) 144 + return 145 + } 146 + 147 + if len(segments) == 1 { 148 + var data releaseBodyJSON 149 + err := json.NewDecoder(r.Body).Decode(&data) 150 + if err != nil { 151 + fmt.Printf("Failed to update release: %s\n", err) 152 + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 153 + return 154 + } 155 + 156 + if data.ID == "" { data.ID = release.ID } 157 + 158 + if data.Title == "" { 159 + http.Error(w, "Release title cannot be blank\n", http.StatusBadRequest) 160 + return 161 + } 162 + 163 + var new_release = model.Release{ 164 + ID: data.ID, 165 + Visible: data.Visible, 166 + Title: data.Title, 167 + Description: data.Description, 168 + ReleaseType: data.ReleaseType, 169 + ReleaseDate: data.ReleaseDate, 170 + Artwork: data.Artwork, 171 + Buyname: data.Buyname, 172 + Buylink: data.Buylink, 173 + } 174 + 175 + err = controller.UpdateReleaseDB(global.DB, release) 176 + if err != nil { 177 + fmt.Printf("Failed to update release %s: %s\n", release.ID, err) 178 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 179 + return 180 + } 181 + 182 + release.ID = new_release.ID 183 + release.Visible = new_release.Visible 184 + release.Title = new_release.Title 185 + release.Description = new_release.Description 186 + release.ReleaseType = new_release.ReleaseType 187 + release.ReleaseDate = new_release.ReleaseDate 188 + release.Artwork = new_release.Artwork 189 + release.Buyname = new_release.Buyname 190 + release.Buylink = new_release.Buylink 191 + 192 + w.Header().Add("Content-Type", "application/json") 193 + err = json.NewEncoder(w).Encode(release) 194 + if err != nil { 195 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 196 + return 197 + } 198 + } 199 + 200 + if len(segments) == 2 { 201 + switch segments[1] { 202 + case "tracks": 203 + UpdateReleaseTracks(release).ServeHTTP(w, r) 204 + case "credits": 205 + UpdateReleaseCredits(release).ServeHTTP(w, r) 206 + case "links": 207 + UpdateReleaseLinks(release).ServeHTTP(w, r) 208 + } 209 + return 210 + } 211 + 212 + http.NotFound(w, r) 213 + }) 214 + } 215 + 216 + func UpdateReleaseTracks(release *model.Release) http.Handler { 217 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 218 + if r.URL.Path == "/" || r.Method != http.MethodPut { 219 + http.NotFound(w, r) 220 + return 221 + } 222 + 223 + var trackIDs = []string{} 224 + err := json.NewDecoder(r.Body).Decode(&trackIDs) 225 + if err != nil { 226 + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 227 + return 228 + } 229 + 230 + var old_tracks = (*release).Tracks 231 + var new_tracks = []*model.Track{} 232 + for _, trackID := range trackIDs { 233 + var track = global.GetTrack(trackID) 234 + if track == nil { 235 + http.Error(w, fmt.Sprintf("Track %s does not exist\n", trackID), http.StatusBadRequest) 236 + return 237 + } 238 + new_tracks = append(new_tracks, track) 239 + } 240 + 241 + err = controller.UpdateReleaseTracksDB(global.DB, release, new_tracks) 242 + if err != nil { 243 + fmt.Printf("Failed to update tracks for %s: %s\n", release.ID, err) 244 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 245 + return 246 + } 247 + 248 + release.Tracks = new_tracks 249 + 250 + // remove release from orphaned tracks 251 + for _, old_track := range old_tracks { 252 + var exists = false 253 + for _, track := range new_tracks { 254 + if track.ID == old_track.ID { 255 + exists = true 256 + break 257 + } 258 + } 259 + if !exists { 260 + old_track.Release = nil 261 + } 262 + } 263 + 264 + w.Header().Add("Content-Type", "application/json") 265 + w.WriteHeader(http.StatusOK) 266 + err = json.NewEncoder(w).Encode(release) 267 + if err != nil { 268 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 269 + return 270 + } 271 + }) 272 + } 273 + 274 + func UpdateReleaseCredits(release *model.Release) http.Handler { 275 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 276 + if r.Method != http.MethodPut { 277 + http.NotFound(w, r) 278 + return 279 + } 280 + 281 + type creditJSON struct { 282 + Artist string 283 + Role string 284 + Primary bool 285 + } 286 + 287 + var list []creditJSON 288 + err := json.NewDecoder(r.Body).Decode(&list) 289 + if err != nil { 290 + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 291 + return 292 + } 293 + 294 + var credits = []*model.Credit{} 295 + for i, data := range list { 296 + if data.Artist == "" { 297 + http.Error(w, fmt.Sprintf("Artist ID cannot be blank (%d)", i), http.StatusBadRequest) 298 + return 299 + } 300 + 301 + for _, credit := range credits { 302 + if data.Artist == credit.Artist.ID { 303 + http.Error(w, fmt.Sprintf("Artist %s credited more than once", data.Artist), http.StatusBadRequest) 304 + return 305 + } 306 + } 307 + 308 + if data.Role == "" { 309 + http.Error(w, fmt.Sprintf("Artist role cannot be blank (%d)", i), http.StatusBadRequest) 310 + return 311 + } 312 + 313 + var artist = global.GetArtist(data.Artist) 314 + if artist == nil { 315 + http.Error(w, fmt.Sprintf("Artist %s does not exist\n", data.Artist), http.StatusBadRequest) 316 + return 317 + } 318 + 319 + credits = append(credits, &model.Credit{ 320 + Artist: artist, 321 + Role: data.Role, 322 + Primary: data.Primary, 323 + }) 324 + } 325 + 326 + err = controller.UpdateReleaseCreditsDB(global.DB, release, credits) 327 + if err != nil { 328 + fmt.Printf("Failed to update links %s: %s\n", release.ID, err) 329 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 330 + return 331 + } 332 + 333 + release.Credits = credits 334 + 335 + w.Header().Add("Content-Type", "application/json") 336 + w.WriteHeader(http.StatusOK) 337 + err = json.NewEncoder(w).Encode(release) 338 + if err != nil { 339 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 340 + return 341 + } 342 + }) 343 + } 344 + 345 + func UpdateReleaseLinks(release *model.Release) http.Handler { 114 346 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 115 - if r.Method != http.MethodDelete { 347 + if r.Method != http.MethodPut { 116 348 http.NotFound(w, r) 117 349 return 118 350 } 119 351 120 - if r.URL.Path == "/" { 352 + var links = []*model.Link{} 353 + err := json.NewDecoder(r.Body).Decode(&links) 354 + if err != nil { 121 355 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 122 356 return 123 357 } 124 358 359 + err = controller.UpdateReleaseLinksDB(global.DB, release, links) 360 + if err != nil { 361 + fmt.Printf("Failed to update links %s: %s\n", release.ID, err) 362 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 363 + return 364 + } 365 + 366 + release.Links = links 367 + 368 + w.Header().Add("Content-Type", "application/json") 369 + w.WriteHeader(http.StatusOK) 370 + err = json.NewEncoder(w).Encode(release) 371 + if err != nil { 372 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 373 + return 374 + } 375 + }) 376 + } 377 + 378 + func DeleteRelease() http.Handler { 379 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 380 + if r.Method != http.MethodDelete { 381 + http.NotFound(w, r) 382 + return 383 + } 384 + 125 385 var releaseID = r.URL.Path[1:] 126 386 var release = global.GetRelease(releaseID) 127 387 if release == nil { ··· 131 391 132 392 err := controller.DeleteReleaseDB(global.DB, release) 133 393 if err != nil { 394 + fmt.Printf("Failed to delete release %s: %s\n", release.ID, err) 134 395 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 135 396 return 136 397 } ··· 138 399 global.Releases = func () []*model.Release { 139 400 var releases = []*model.Release{} 140 401 for _, r := range global.Releases { 141 - if r.ID == releaseID { continue } 402 + if r.ID == release.ID { continue } 142 403 releases = append(releases, r) 143 404 } 144 405 return releases 145 406 }() 146 407 147 - w.WriteHeader(200) 408 + w.WriteHeader(http.StatusOK) 148 409 w.Write([]byte(fmt.Sprintf("Release %s has been deleted\n", release.ID))) 149 410 }) 150 411 }
+156 -1
api/track.go
··· 10 10 controller "arimelody.me/arimelody.me/music/controller" 11 11 ) 12 12 13 + func ServeAllTracks() http.Handler { 14 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 + // type trackJSON struct { 16 + // model.Track 17 + // Release string `json:"release"` 18 + // } 19 + // var tracks = []trackJSON{} 20 + // 21 + // for _, track := range global.Tracks { 22 + // for _, release := range global. { 23 + // tracks = append(tracks, { 24 + // track, 25 + // Release 26 + // }) 27 + // } 28 + 29 + w.Header().Add("Content-Type", "application/json") 30 + err := json.NewEncoder(w).Encode(global.Tracks) 31 + if err != nil { 32 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 33 + return 34 + } 35 + }) 36 + } 37 + 38 + func ServeTrack() http.Handler { 39 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 + if r.URL.Path == "/" { 41 + ServeAllTracks().ServeHTTP(w, r) 42 + return 43 + } 44 + 45 + var trackID = r.URL.Path[1:] 46 + var track = global.GetTrack(trackID) 47 + if track == nil { 48 + http.NotFound(w, r) 49 + return 50 + } 51 + 52 + w.Header().Add("Content-Type", "application/json") 53 + err := json.NewEncoder(w).Encode(track) 54 + if err != nil { 55 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 56 + return 57 + } 58 + }) 59 + } 60 + 13 61 func CreateTrack() http.Handler { 14 62 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 63 if r.Method != http.MethodPost { ··· 31 79 32 80 trackID, err := controller.CreateTrackDB(global.DB, &track) 33 81 if err != nil { 34 - fmt.Printf("Failed to create credit: %s\n", err) 82 + fmt.Printf("Failed to create track: %s\n", err) 35 83 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 36 84 return 37 85 } ··· 44 92 err = json.NewEncoder(w).Encode(track) 45 93 }) 46 94 } 95 + 96 + func UpdateTrack() http.Handler { 97 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 98 + if r.Method != http.MethodPut { 99 + http.NotFound(w, r) 100 + return 101 + } 102 + 103 + if r.URL.Path == "/" { 104 + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 105 + return 106 + } 107 + 108 + var data model.Track 109 + err := json.NewDecoder(r.Body).Decode(&data) 110 + if err != nil { 111 + fmt.Printf("Failed to update track: %s\n", err) 112 + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 113 + return 114 + } 115 + 116 + var trackID = r.URL.Path[1:] 117 + var track = global.GetTrack(trackID) 118 + if track == nil { 119 + http.Error(w, fmt.Sprintf("Track %s does not exist\n", trackID), http.StatusBadRequest) 120 + return 121 + } 122 + 123 + data.ID = trackID 124 + 125 + if data.Title == "" { 126 + http.Error(w, "Track title cannot be blank\n", http.StatusBadRequest) 127 + return 128 + } 129 + 130 + err = controller.UpdateTrackDB(global.DB, &data) 131 + if err != nil { 132 + fmt.Printf("Failed to update track %s: %s\n", track.ID, err) 133 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 134 + return 135 + } 136 + 137 + track.Title = data.Title 138 + track.Description = data.Description 139 + track.Lyrics = data.Lyrics 140 + track.PreviewURL = data.PreviewURL 141 + 142 + w.Header().Add("Content-Type", "application/json") 143 + err = json.NewEncoder(w).Encode(track) 144 + if err != nil { 145 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 146 + return 147 + } 148 + }) 149 + } 150 + 151 + func DeleteTrack() http.Handler { 152 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 153 + if r.Method != http.MethodDelete { 154 + http.NotFound(w, r) 155 + return 156 + } 157 + 158 + if r.URL.Path == "/" { 159 + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 160 + return 161 + } 162 + 163 + var trackID = r.URL.Path[1:] 164 + var track = global.GetTrack(trackID) 165 + if track == nil { 166 + http.Error(w, fmt.Sprintf("Track %s does not exist\n", trackID), http.StatusBadRequest) 167 + return 168 + } 169 + 170 + err := controller.DeleteTrackDB(global.DB, track) 171 + if err != nil { 172 + fmt.Printf("Failed to delete track %s: %s\n", track.ID, err) 173 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 174 + return 175 + } 176 + 177 + // clear track from releases 178 + for _, release := range global.Releases { 179 + release.Tracks = func () []*model.Track { 180 + var tracks = []*model.Track{} 181 + for _, t := range release.Tracks { 182 + if t.ID == track.ID { continue } 183 + tracks = append(tracks, t) 184 + } 185 + return tracks 186 + }() 187 + } 188 + 189 + global.Tracks = func () []*model.Track { 190 + var tracks = []*model.Track{} 191 + for _, t := range global.Tracks { 192 + if t.ID == track.ID { continue } 193 + tracks = append(tracks, t) 194 + } 195 + return tracks 196 + }() 197 + 198 + w.WriteHeader(http.StatusOK) 199 + w.Write([]byte(fmt.Sprintf("Track %s has been deleted\n", track.ID))) 200 + }) 201 + }
+9
global/data.go
··· 38 38 } 39 39 return nil 40 40 } 41 + 42 + func GetTrack(id string) *model.Track { 43 + for _, track := range Tracks { 44 + if track.ID == id { 45 + return track 46 + } 47 + } 48 + return nil 49 + }
+8 -8
main.go
··· 41 41 } 42 42 fmt.Printf("%d artists loaded successfully.\n", len(global.Artists)) 43 43 44 - // pull release data from DB 45 - global.Releases, err = musicController.PullAllReleases(global.DB) 46 - if err != nil { 47 - fmt.Printf("Failed to pull releases from database: %v\n", err); 48 - panic(1) 49 - } 50 - fmt.Printf("%d releases loaded successfully.\n", len(global.Releases)) 51 - 52 44 // pull track data from DB 53 45 global.Tracks, err = musicController.PullAllTracks(global.DB) 54 46 if err != nil { ··· 56 48 panic(1) 57 49 } 58 50 fmt.Printf("%d tracks loaded successfully.\n", len(global.Tracks)) 51 + 52 + // pull release data from DB 53 + global.Releases, err = musicController.PullAllReleases(global.DB) 54 + if err != nil { 55 + fmt.Printf("Failed to pull releases from database: %v\n", err); 56 + panic(1) 57 + } 58 + fmt.Printf("%d releases loaded successfully.\n", len(global.Releases)) 59 59 60 60 // start the web server! 61 61 mux := createServeMux()
+3 -3
music/controller/link.go
··· 1 1 package music 2 2 3 3 import ( 4 - "arimelody.me/arimelody.me/music/model" 5 - "github.com/jmoiron/sqlx" 4 + "arimelody.me/arimelody.me/music/model" 5 + "github.com/jmoiron/sqlx" 6 6 ) 7 7 8 8 // DATABASE ··· 12 12 13 13 err := db.Select( 14 14 &links, 15 - "SELECT * FROM musiclink WHERE release=$1", 15 + "SELECT name, url FROM musiclink WHERE release=$1", 16 16 releaseID, 17 17 ) 18 18 if err != nil {
+122 -8
music/controller/release.go
··· 1 1 package music 2 2 3 3 import ( 4 + "errors" 4 5 "fmt" 5 6 6 7 "arimelody.me/arimelody.me/global" ··· 11 12 // DATABASE 12 13 13 14 func PullAllReleases(db *sqlx.DB) ([]*model.Release, error) { 14 - var release_rows = []*model.Release{} 15 15 var releases = []*model.Release{} 16 16 17 - err := db.Select(&release_rows, "SELECT * FROM musicrelease ORDER BY release_date DESC") 17 + err := db.Select(&releases, "SELECT * FROM musicrelease ORDER BY release_date DESC") 18 18 if err != nil { 19 19 return nil, err 20 20 } 21 21 22 - for _, release := range release_rows { 22 + for _, release := range releases { 23 23 release.Credits, err = PullReleaseCredits(global.DB, release.ID) 24 24 if err != nil { 25 25 fmt.Printf("Error pulling credits for %s: %s\n", release.ID, err) 26 + release.Credits = []*model.Credit{} 26 27 } 27 - release.Links, _ = PullReleaseLinks(global.DB, release.ID) 28 + release.Links, err = PullReleaseLinks(global.DB, release.ID) 28 29 if err != nil { 29 30 fmt.Printf("Error pulling links for %s: %s\n", release.ID, err) 31 + release.Links = []*model.Link{} 30 32 } 31 - release.Tracks = make([]*model.Track, 0) 32 - releases = append(releases, release) 33 + release.Tracks, err = PullReleaseTracksDB(global.DB, release) 34 + if err != nil { 35 + fmt.Printf("Error pulling tracks for %s: %s\n", release.ID, err) 36 + release.Tracks = []*model.Track{} 37 + } 33 38 } 34 39 35 40 return releases, nil 36 41 } 37 42 43 + func PullReleaseTracksDB(db *sqlx.DB, release *model.Release) ([]*model.Track, error) { 44 + var track_rows = []string{} 45 + var tracks = []*model.Track{} 46 + 47 + err := db.Select(&track_rows, 48 + "SELECT track FROM musicreleasetrack "+ 49 + "WHERE release=$1 "+ 50 + "ORDER BY number DESC", 51 + release.ID, 52 + ) 53 + if err != nil { 54 + return nil, err 55 + } 56 + 57 + for _, trackID := range track_rows { 58 + var track = global.GetTrack(trackID) 59 + if track == nil { 60 + return nil, errors.New("Recieved a track from the DB that does not exist in memory") 61 + } 62 + track.Release = release 63 + tracks = append(tracks, track) 64 + } 65 + 66 + return tracks, nil 67 + } 68 + 38 69 func CreateReleaseDB(db *sqlx.DB, release *model.Release) error { 39 70 _, err := db.Exec( 40 71 "INSERT INTO musicrelease "+ ··· 60 91 func UpdateReleaseDB(db *sqlx.DB, release *model.Release) error { 61 92 _, err := db.Exec( 62 93 "UPDATE musicrelease SET "+ 63 - "title=$2, description=$3, type=$4, release_date=$5, artwork=$6, buyname=$7, buylink=$8) "+ 64 - "VALUES ($2, $3, $4, $5, $6, $7, $8) "+ 94 + "visible=$2, title=$3, description=$4, type=$5, release_date=$6, artwork=$7, buyname=$8, buylink=$9) "+ 95 + "VALUES ($2, $3, $4, $5, $6, $7, $8, $9) "+ 65 96 "WHERE id=$1", 66 97 release.ID, 98 + release.Visible, 67 99 release.Title, 68 100 release.Description, 69 101 release.ReleaseType, ··· 74 106 ) 75 107 if err != nil { 76 108 return err 109 + } 110 + 111 + return nil 112 + } 113 + 114 + func UpdateReleaseTracksDB(db *sqlx.DB, release *model.Release, new_tracks []*model.Track) error { 115 + _, err := db.Exec( 116 + "DELETE FROM musicreleasetrack "+ 117 + "WHERE release=$1", 118 + release.ID, 119 + ) 120 + if err != nil { 121 + return err 122 + } 123 + 124 + for i, track := range new_tracks { 125 + _, err = db.Exec( 126 + "INSERT INTO musicreleasetrack "+ 127 + "(release, track, number) "+ 128 + "VALUES ($1, $2, $3)", 129 + release.ID, 130 + track.ID, 131 + i, 132 + ) 133 + if err != nil { 134 + return err 135 + } 136 + } 137 + 138 + return nil 139 + } 140 + 141 + func UpdateReleaseCreditsDB(db *sqlx.DB, release *model.Release, new_credits []*model.Credit) error { 142 + _, err := db.Exec( 143 + "DELETE FROM musiccredit "+ 144 + "WHERE release=$1", 145 + release.ID, 146 + ) 147 + if err != nil { 148 + return err 149 + } 150 + 151 + for _, credit := range new_credits { 152 + _, err = db.Exec( 153 + "INSERT INTO musiccredit "+ 154 + "(release, artist, role, is_primary) "+ 155 + "VALUES ($1, $2, $3, $4)", 156 + release.ID, 157 + credit.Artist.ID, 158 + credit.Role, 159 + credit.Primary, 160 + ) 161 + if err != nil { 162 + return err 163 + } 164 + } 165 + 166 + return nil 167 + } 168 + 169 + func UpdateReleaseLinksDB(db *sqlx.DB, release *model.Release, new_links []*model.Link) error { 170 + _, err := db.Exec( 171 + "DELETE FROM musiclink "+ 172 + "WHERE release=$1", 173 + release.ID, 174 + ) 175 + if err != nil { 176 + return err 177 + } 178 + 179 + for _, link := range new_links { 180 + _, err = db.Exec( 181 + "INSERT INTO musiclink "+ 182 + "(release, name, url) "+ 183 + "VALUES ($1, $2, $3)", 184 + release.ID, 185 + link.Name, 186 + link.URL, 187 + ) 188 + if err != nil { 189 + return err 190 + } 77 191 } 78 192 79 193 return nil
+15
music/controller/track.go
··· 18 18 return tracks, nil 19 19 } 20 20 21 + func PullOrphanTracks(db *sqlx.DB) ([]*model.Track, error) { 22 + var tracks = []*model.Track{} 23 + 24 + err := db.Select(&tracks, 25 + "SELECT id, title, description, lyrics, preview_url FROM musictrack "+ 26 + "WHERE id NOT IN "+ 27 + "(SELECT track FROM musicreleasetrack)", 28 + ) 29 + if err != nil { 30 + return nil, err 31 + } 32 + 33 + return tracks, nil 34 + } 35 + 21 36 func CreateTrackDB(db *sqlx.DB, track *model.Track) (string, error) { 22 37 var trackID string 23 38 err := db.QueryRow(
+6 -5
music/model/track.go
··· 1 1 package model 2 2 3 3 type Track struct { 4 - ID string `json:"id"` 5 - Title string `json:"title"` 6 - Description string `json:"description"` 7 - Lyrics string `json:"lyrics"` 8 - PreviewURL string `json:"previewURL" db:"preview_url"` 4 + ID string `json:"id"` 5 + Title string `json:"title"` 6 + Description string `json:"description"` 7 + Lyrics string `json:"lyrics"` 8 + PreviewURL string `json:"previewURL" db:"preview_url"` 9 + Release *Release `json:"-" db:"-"` 9 10 }
+22 -1
music/view/release.go
··· 7 7 8 8 "arimelody.me/arimelody.me/admin" 9 9 "arimelody.me/arimelody.me/global" 10 + "arimelody.me/arimelody.me/music/model" 10 11 ) 11 12 12 13 // HTTP HANDLERS ··· 48 49 return 49 50 } 50 51 52 + type ( 53 + GatewayTrack struct { 54 + *model.Track 55 + Number int 56 + } 57 + 58 + GatewayRelease struct { 59 + *model.Release 60 + Tracks []GatewayTrack 61 + } 62 + ) 63 + 51 64 id := r.URL.Path[1:] 52 65 release := global.GetRelease(id) 53 66 if release == nil { ··· 55 68 return 56 69 } 57 70 71 + tracks := []GatewayTrack{} 72 + for i, track := range release.Tracks { 73 + tracks = append([]GatewayTrack{GatewayTrack{ 74 + Track: track, 75 + Number: len(release.Tracks) - i, 76 + }}, tracks...) 77 + } 78 + 58 79 // only allow authorised users to view unreleased releases 59 80 authorised := admin.GetSession(r) != nil 60 81 if !release.IsReleased() && !authorised { ··· 64 85 65 86 lrw := global.LoggingResponseWriter{w, http.StatusOK} 66 87 67 - global.ServeTemplate("music-gateway.html", release).ServeHTTP(&lrw, r) 88 + global.ServeTemplate("music-gateway.html", GatewayRelease{release, tracks}).ServeHTTP(&lrw, r) 68 89 69 90 if lrw.Code != http.StatusOK { 70 91 fmt.Printf("Error rendering music gateway for %s\n", id)
+1 -1
public/style/music-gateway.css
··· 429 429 } 430 430 431 431 #tracks details[open] { 432 - margin-bottom: 2em; 432 + margin-bottom: 1.5em; 433 433 } 434 434 435 435 #tracks summary {
+1 -1
views/admin/index.html
··· 26 26 <div class="release-info"> 27 27 <h3 class="release-title">{{$Release.Title}} <small>{{$Release.GetReleaseYear}}</small></h3> 28 28 <p class="release-artists">{{$Release.PrintArtists true true}}</p> 29 - <p class="release-type-single">{{$Release.ReleaseType}}</p> 29 + <p class="release-type-single">{{$Release.ReleaseType}} ({{len $Release.Tracks}} tracks)</p> 30 30 <div class="release-actions"> 31 31 <a href="/admin/releases/{{$Release.ID}}">Edit</a> 32 32 <a href="/music/{{$Release.ID}}" target="_blank">Gateway</a>
+5 -1
views/music-gateway.html
··· 1 1 {{define "head"}} 2 2 <title>{{.Title}} - {{.PrintArtists true true}}</title> 3 - <link rel="icon" type="image/png" href="{{.GetArtwork}}"> 3 + <link rel="shortcut icon" href="{{.GetArtwork}}" type="image/x-icon"> 4 4 5 5 <meta name="description" content="Stream &quot;{{.Title}}&quot; by {{.PrintArtists true true}} on all platforms!"> 6 6 <meta name="author" content="{{.PrintArtists true true}}"> ··· 112 112 {{range $i, $track := .Tracks}} 113 113 <details> 114 114 <summary class="album-track-title">{{$track.Number}}. {{$track.Title}}</summary> 115 + {{if $track.Lyrics}} 115 116 {{$track.Lyrics}} 117 + {{else}} 118 + <span class="empty">No lyrics.</span> 119 + {{end}} 116 120 </details> 117 121 {{end}} 118 122 </div>