home to your local SPACEGIRL 💫 arimelody.space
1
fork

Configure Feed

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

this is immensely broken but i swear i'll fix it later

+460 -304
+1
.dockerignore
··· 6 6 uploads/* 7 7 test/ 8 8 tmp/ 9 + res/ 9 10 docker-compose.yml 10 11 Dockerfile 11 12 schema.sql
+1 -1
.gitignore
··· 3 3 db/ 4 4 tmp/ 5 5 test/ 6 - uploads/* 6 + uploads/ 7 7 docker-compose-test.yml
+1 -1
Dockerfile
··· 11 11 12 12 # --- 13 13 14 - FROM build-stage AS build-release-stage 14 + FROM scratch 15 15 16 16 WORKDIR /app 17 17
+1 -7
admin/admin.go
··· 28 28 return false 29 29 }() 30 30 31 - var ADMIN_ID_DISCORD = func() string { 32 - id := os.Getenv("DISCORD_ADMIN") 33 - if id == "" { 34 - fmt.Printf("WARN: Discord admin ID (DISCORD_ADMIN) was not provided. Admin login will be unavailable.\n") 35 - } 36 - return id 37 - }() 31 + var ADMIN_ID_DISCORD = os.Getenv("DISCORD_ADMIN") 38 32 39 33 var sessions []*Session 40 34
+4 -4
admin/artisthttp.go
··· 6 6 "strings" 7 7 8 8 "arimelody-web/global" 9 - "arimelody-web/music/model" 10 - "arimelody-web/music/controller" 9 + "arimelody-web/model" 10 + "arimelody-web/controller" 11 11 ) 12 12 13 13 func serveArtist() http.Handler { 14 14 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 15 slices := strings.Split(r.URL.Path[1:], "/") 16 16 id := slices[0] 17 - artist, err := music.GetArtist(global.DB, id) 17 + artist, err := controller.GetArtist(global.DB, id) 18 18 if err != nil { 19 19 if artist == nil { 20 20 http.NotFound(w, r) ··· 25 25 return 26 26 } 27 27 28 - credits, err := music.GetArtistCredits(global.DB, artist.ID, true) 28 + credits, err := controller.GetArtistCredits(global.DB, artist.ID, true) 29 29 if err != nil { 30 30 fmt.Printf("Error rendering admin track page for %s: %s\n", id, err) 31 31 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+28 -16
admin/http.go
··· 11 11 12 12 "arimelody-web/discord" 13 13 "arimelody-web/global" 14 - musicDB "arimelody-web/music/controller" 15 - musicModel "arimelody-web/music/model" 14 + "arimelody-web/controller" 15 + "arimelody-web/model" 16 16 ) 17 17 18 18 type loginData struct { ··· 24 24 mux := http.NewServeMux() 25 25 26 26 mux.Handle("/login", LoginHandler()) 27 + mux.Handle("/create-account", createAccountHandler()) 27 28 mux.Handle("/logout", MustAuthorise(LogoutHandler())) 28 29 mux.Handle("/static/", http.StripPrefix("/static", staticHandler())) 29 30 mux.Handle("/release/", MustAuthorise(http.StripPrefix("/release", serveRelease()))) ··· 41 42 return 42 43 } 43 44 44 - releases, err := musicDB.GetAllReleases(global.DB, false, 0, true) 45 + releases, err := controller.GetAllReleases(global.DB, false, 0, true) 45 46 if err != nil { 46 47 fmt.Printf("FATAL: Failed to pull releases: %s\n", err) 47 48 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 48 49 return 49 50 } 50 51 51 - artists, err := musicDB.GetAllArtists(global.DB) 52 + artists, err := controller.GetAllArtists(global.DB) 52 53 if err != nil { 53 54 fmt.Printf("FATAL: Failed to pull artists: %s\n", err) 54 55 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 55 56 return 56 57 } 57 58 58 - tracks, err := musicDB.GetOrphanTracks(global.DB) 59 + tracks, err := controller.GetOrphanTracks(global.DB) 59 60 if err != nil { 60 61 fmt.Printf("FATAL: Failed to pull orphan tracks: %s\n", err) 61 62 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 63 64 } 64 65 65 66 type IndexData struct { 66 - Releases []*musicModel.Release 67 - Artists []*musicModel.Artist 68 - Tracks []*musicModel.Track 67 + Releases []*model.Release 68 + Artists []*model.Artist 69 + Tracks []*model.Track 69 70 } 70 71 71 72 err = pages["index"].Execute(w, IndexData{ ··· 149 150 150 151 func LoginHandler() http.Handler { 151 152 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 152 - if !discord.CREDENTIALS_PROVIDED || ADMIN_ID_DISCORD == "" { 153 - http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) 154 - return 155 - } 156 - 157 - fmt.Println(discord.CLIENT_ID) 158 - fmt.Println(discord.API_ENDPOINT) 159 - fmt.Println(discord.REDIRECT_URI) 153 + // if !discord.CREDENTIALS_PROVIDED || ADMIN_ID_DISCORD == "" { 154 + // http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) 155 + // return 156 + // } 157 + // 158 + // fmt.Println(discord.CLIENT_ID) 159 + // fmt.Println(discord.API_ENDPOINT) 160 + // fmt.Println(discord.REDIRECT_URI) 160 161 161 162 code := r.URL.Query().Get("code") 162 163 ··· 233 234 err := pages["logout"].Execute(w, nil) 234 235 if err != nil { 235 236 fmt.Printf("Error rendering admin logout page: %s\n", err) 237 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 238 + return 239 + } 240 + }) 241 + } 242 + 243 + func createAccountHandler() http.Handler { 244 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 245 + err := pages["create-account"].Execute(w, nil) 246 + if err != nil { 247 + fmt.Printf("Error rendering admin crearte account page: %s\n", err) 236 248 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 237 249 return 238 250 }
+7 -7
admin/releasehttp.go
··· 6 6 "strings" 7 7 8 8 "arimelody-web/global" 9 - db "arimelody-web/music/controller" 10 - "arimelody-web/music/model" 9 + "arimelody-web/controller" 10 + "arimelody-web/model" 11 11 ) 12 12 13 13 func serveRelease() http.Handler { ··· 15 15 slices := strings.Split(r.URL.Path[1:], "/") 16 16 releaseID := slices[0] 17 17 18 - release, err := db.GetRelease(global.DB, releaseID, true) 18 + release, err := controller.GetRelease(global.DB, releaseID, true) 19 19 if err != nil { 20 20 if strings.Contains(err.Error(), "no rows") { 21 21 http.NotFound(w, r) ··· 81 81 82 82 func serveAddCredit(release *model.Release) http.Handler { 83 83 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 84 - artists, err := db.GetArtistsNotOnRelease(global.DB, release.ID) 84 + artists, err := controller.GetArtistsNotOnRelease(global.DB, release.ID) 85 85 if err != nil { 86 86 fmt.Printf("FATAL: Failed to pull artists not on %s: %s\n", release.ID, err) 87 87 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 108 108 func serveNewCredit() http.Handler { 109 109 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 110 110 artistID := strings.Split(r.URL.Path, "/")[3] 111 - artist, err := db.GetArtist(global.DB, artistID) 111 + artist, err := controller.GetArtist(global.DB, artistID) 112 112 if err != nil { 113 113 fmt.Printf("FATAL: Failed to pull artists %s: %s\n", artistID, err) 114 114 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 152 152 153 153 func serveAddTrack(release *model.Release) http.Handler { 154 154 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 155 - tracks, err := db.GetTracksNotOnRelease(global.DB, release.ID) 155 + tracks, err := controller.GetTracksNotOnRelease(global.DB, release.ID) 156 156 if err != nil { 157 157 fmt.Printf("FATAL: Failed to pull tracks not on %s: %s\n", release.ID, err) 158 158 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 180 180 func serveNewTrack() http.Handler { 181 181 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 182 182 trackID := strings.Split(r.URL.Path, "/")[3] 183 - track, err := db.GetTrack(global.DB, trackID) 183 + track, err := controller.GetTrack(global.DB, trackID) 184 184 if err != nil { 185 185 fmt.Printf("Error rendering new track component for %s: %s\n", trackID, err) 186 186 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+5
admin/templates.go
··· 18 18 filepath.Join("views", "prideflag.html"), 19 19 filepath.Join("admin", "views", "login.html"), 20 20 )), 21 + "create-account": template.Must(template.ParseFiles( 22 + filepath.Join("admin", "views", "layout.html"), 23 + filepath.Join("views", "prideflag.html"), 24 + filepath.Join("admin", "views", "create-account.html"), 25 + )), 21 26 "logout": template.Must(template.ParseFiles( 22 27 filepath.Join("admin", "views", "layout.html"), 23 28 filepath.Join("views", "prideflag.html"),
+4 -4
admin/trackhttp.go
··· 6 6 "strings" 7 7 8 8 "arimelody-web/global" 9 - "arimelody-web/music/model" 10 - "arimelody-web/music/controller" 9 + "arimelody-web/model" 10 + "arimelody-web/controller" 11 11 ) 12 12 13 13 func serveTrack() http.Handler { 14 14 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 15 slices := strings.Split(r.URL.Path[1:], "/") 16 16 id := slices[0] 17 - track, err := music.GetTrack(global.DB, id) 17 + track, err := controller.GetTrack(global.DB, id) 18 18 if err != nil { 19 19 fmt.Printf("Error rendering admin track page for %s: %s\n", id, err) 20 20 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 25 25 return 26 26 } 27 27 28 - releases, err := music.GetTrackReleases(global.DB, track.ID, true) 28 + releases, err := controller.GetTrackReleases(global.DB, track.ID, true) 29 29 if err != nil { 30 30 fmt.Printf("FATAL: Failed to pull releases for %s: %s\n", id, err) 31 31 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+107
admin/views/create-account.html
··· 1 + {{define "head"}} 2 + <title>Register - ari melody 💫</title> 3 + <link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon"> 4 + 5 + <style> 6 + p a { 7 + color: #2a67c8; 8 + } 9 + 10 + a.discord { 11 + color: #5865F2; 12 + } 13 + 14 + form { 15 + width: 100%; 16 + display: flex; 17 + flex-direction: column; 18 + align-items: center; 19 + } 20 + 21 + form div { 22 + width: 20rem; 23 + } 24 + 25 + form button { 26 + margin-top: 1rem; 27 + } 28 + 29 + label { 30 + width: 100%; 31 + margin: 1rem 0 .5rem 0; 32 + display: block; 33 + color: #10101080; 34 + } 35 + input { 36 + width: 100%; 37 + margin: .5rem 0; 38 + padding: .3rem .5rem; 39 + display: block; 40 + border-radius: 4px; 41 + border: 1px solid #808080; 42 + font-size: inherit; 43 + font-family: inherit; 44 + color: inherit; 45 + } 46 + 47 + button { 48 + padding: .5em .8em; 49 + font-family: inherit; 50 + font-size: inherit; 51 + border-radius: .5em; 52 + border: 1px solid #a0a0a0; 53 + background: #f0f0f0; 54 + color: inherit; 55 + } 56 + button.new { 57 + background: #c4ff6a; 58 + border-color: #84b141; 59 + } 60 + button:hover { 61 + background: #fff; 62 + border-color: #d0d0d0; 63 + } 64 + button:active { 65 + background: #d0d0d0; 66 + border-color: #808080; 67 + } 68 + </style> 69 + {{end}} 70 + 71 + {{define "content"}} 72 + <main> 73 + {{if .Success}} 74 + 75 + <meta http-equiv="refresh" content="5;url=/admin/" /> 76 + <p> 77 + {{.Message}} 78 + You should be redirected to <a href="/admin">/admin</a> in 5 seconds. 79 + </p> 80 + 81 + {{else}} 82 + 83 + {{if .Message}} 84 + <p id="error">{{.Message}}</p> 85 + {{end}} 86 + 87 + <form action="/admin/create-account" method="POST" id="create-account"> 88 + <div> 89 + <label for="username">Username</label> 90 + <input type="text" name="username" value=""> 91 + 92 + <label for="email">Email</label> 93 + <input type="text" name="email" value=""> 94 + 95 + <label for="password">Password</label> 96 + <input type="password" name="password" value=""> 97 + 98 + <label for="invite">Invite Code</label> 99 + <input type="text" name="invite" value=""> 100 + </div> 101 + 102 + <button type="submit" class="new">Create Account</button> 103 + </form> 104 + 105 + {{end}} 106 + </main> 107 + {{end}}
+71 -1
admin/views/login.html
··· 10 10 a.discord { 11 11 color: #5865F2; 12 12 } 13 + 14 + form { 15 + width: 100%; 16 + display: flex; 17 + flex-direction: column; 18 + align-items: center; 19 + } 20 + 21 + form div { 22 + width: 20rem; 23 + } 24 + 25 + form button { 26 + margin-top: 1rem; 27 + } 28 + 29 + label { 30 + width: 100%; 31 + margin: 1rem 0 .5rem 0; 32 + display: block; 33 + color: #10101080; 34 + } 35 + input { 36 + width: 100%; 37 + margin: .5rem 0; 38 + padding: .3rem .5rem; 39 + display: block; 40 + border-radius: 4px; 41 + border: 1px solid #808080; 42 + font-size: inherit; 43 + font-family: inherit; 44 + color: inherit; 45 + } 46 + 47 + button { 48 + padding: .5em .8em; 49 + font-family: inherit; 50 + font-size: inherit; 51 + border-radius: .5em; 52 + border: 1px solid #a0a0a0; 53 + background: #f0f0f0; 54 + color: inherit; 55 + } 56 + button.save { 57 + background: #6fd7ff; 58 + border-color: #6f9eb0; 59 + } 60 + button:hover { 61 + background: #fff; 62 + border-color: #d0d0d0; 63 + } 64 + button:active { 65 + background: #d0d0d0; 66 + border-color: #808080; 67 + } 13 68 </style> 14 69 {{end}} 15 70 ··· 25 80 26 81 {{else}} 27 82 28 - <p>Log in with <a href="{{.DiscordURI}}" class="discord">Discord</a>.</p> 83 + <!-- <p>Log in with <a href="{{.DiscordURI}}" class="discord">Discord</a>.</p> --> 84 + 85 + <form action="/admin/login" method="POST" id="login"> 86 + <div> 87 + <label for="username">Username</label> 88 + <input type="text" name="username" value=""> 89 + 90 + <label for="password">Password</label> 91 + <input type="password" name="password" value=""> 92 + 93 + <label for="code">Code</label> 94 + <input type="text" name="code" value=""> 95 + </div> 96 + 97 + <button type="submit" class="save">Login</button> 98 + </form> 29 99 30 100 {{end}} 31 101 </main>
+5 -6
api/api.go
··· 7 7 8 8 "arimelody-web/admin" 9 9 "arimelody-web/global" 10 - music "arimelody-web/music/controller" 11 - musicView "arimelody-web/music/view" 10 + "arimelody-web/controller" 12 11 ) 13 12 14 13 func Handler() http.Handler { ··· 18 17 19 18 mux.Handle("/v1/artist/", http.StripPrefix("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 19 var artistID = strings.Split(r.URL.Path[1:], "/")[0] 21 - artist, err := music.GetArtist(global.DB, artistID) 20 + artist, err := controller.GetArtist(global.DB, artistID) 22 21 if err != nil { 23 22 if strings.Contains(err.Error(), "no rows") { 24 23 http.NotFound(w, r) ··· 60 59 61 60 mux.Handle("/v1/music/", http.StripPrefix("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 62 61 var releaseID = strings.Split(r.URL.Path[1:], "/")[0] 63 - release, err := music.GetRelease(global.DB, releaseID, true) 62 + release, err := controller.GetRelease(global.DB, releaseID, true) 64 63 if err != nil { 65 64 if strings.Contains(err.Error(), "no rows") { 66 65 http.NotFound(w, r) ··· 74 73 switch r.Method { 75 74 case http.MethodGet: 76 75 // GET /api/v1/music/{id} 77 - musicView.ServeRelease(release).ServeHTTP(w, r) 76 + ServeRelease(release).ServeHTTP(w, r) 78 77 case http.MethodPut: 79 78 // PUT /api/v1/music/{id} (admin) 80 79 admin.MustAuthorise(UpdateRelease(release)).ServeHTTP(w, r) ··· 102 101 103 102 mux.Handle("/v1/track/", http.StripPrefix("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 104 103 var trackID = strings.Split(r.URL.Path[1:], "/")[0] 105 - track, err := music.GetTrack(global.DB, trackID) 104 + track, err := controller.GetTrack(global.DB, trackID) 106 105 if err != nil { 107 106 if strings.Contains(err.Error(), "no rows") { 108 107 http.NotFound(w, r)
+7 -8
api/artist.go
··· 12 12 13 13 "arimelody-web/admin" 14 14 "arimelody-web/global" 15 - db "arimelody-web/music/controller" 16 - music "arimelody-web/music/controller" 17 - "arimelody-web/music/model" 15 + "arimelody-web/controller" 16 + "arimelody-web/model" 18 17 ) 19 18 20 19 func ServeAllArtists() http.Handler { 21 20 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 21 var artists = []*model.Artist{} 23 - artists, err := db.GetAllArtists(global.DB) 22 + artists, err := controller.GetAllArtists(global.DB) 24 23 if err != nil { 25 24 fmt.Printf("FATAL: Failed to serve all artists: %s\n", err) 26 25 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 55 54 show_hidden_releases := admin.GetSession(r) != nil 56 55 57 56 var dbCredits []*model.Credit 58 - dbCredits, err := db.GetArtistCredits(global.DB, artist.ID, show_hidden_releases) 57 + dbCredits, err := controller.GetArtistCredits(global.DB, artist.ID, show_hidden_releases) 59 58 if err != nil { 60 59 fmt.Printf("FATAL: Failed to retrieve artist credits for %s: %s\n", artist.ID, err) 61 60 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 100 99 } 101 100 if artist.Name == "" { artist.Name = artist.ID } 102 101 103 - err = music.CreateArtist(global.DB, &artist) 102 + err = controller.CreateArtist(global.DB, &artist) 104 103 if err != nil { 105 104 if strings.Contains(err.Error(), "duplicate key") { 106 105 http.Error(w, fmt.Sprintf("Artist %s already exists\n", artist.ID), http.StatusBadRequest) ··· 148 147 } 149 148 } 150 149 151 - err = music.UpdateArtist(global.DB, artist) 150 + err = controller.UpdateArtist(global.DB, artist) 152 151 if err != nil { 153 152 if strings.Contains(err.Error(), "no rows") { 154 153 http.NotFound(w, r) ··· 162 161 163 162 func DeleteArtist(artist *model.Artist) http.Handler { 164 163 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 165 - err := music.DeleteArtist(global.DB, artist.ID) 164 + err := controller.DeleteArtist(global.DB, artist.ID) 166 165 if err != nil { 167 166 if strings.Contains(err.Error(), "no rows") { 168 167 http.NotFound(w, r)
+105 -9
api/release.go
··· 12 12 13 13 "arimelody-web/admin" 14 14 "arimelody-web/global" 15 - music "arimelody-web/music/controller" 16 - "arimelody-web/music/model" 15 + "arimelody-web/controller" 16 + "arimelody-web/model" 17 17 ) 18 18 19 + func ServeRelease(release *model.Release) http.Handler { 20 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 21 + // only allow authorised users to view hidden releases 22 + authorised := admin.GetSession(r) != nil 23 + if !authorised && !release.Visible { 24 + http.NotFound(w, r) 25 + return 26 + } 27 + 28 + type ( 29 + Track struct { 30 + Title string `json:"title"` 31 + Description string `json:"description"` 32 + Lyrics string `json:"lyrics"` 33 + } 34 + 35 + Credit struct { 36 + *model.Artist 37 + Role string `json:"role"` 38 + Primary bool `json:"primary"` 39 + } 40 + 41 + Release struct { 42 + *model.Release 43 + Tracks []Track `json:"tracks"` 44 + Credits []Credit `json:"credits"` 45 + Links map[string]string `json:"links"` 46 + } 47 + ) 48 + 49 + response := Release{ 50 + Release: release, 51 + Tracks: []Track{}, 52 + Credits: []Credit{}, 53 + Links: make(map[string]string), 54 + } 55 + 56 + if authorised || release.IsReleased() { 57 + // get credits 58 + credits, err := controller.GetReleaseCredits(global.DB, release.ID) 59 + if err != nil { 60 + fmt.Printf("FATAL: Failed to serve release %s: Credits: %s\n", release.ID, err) 61 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 62 + return 63 + } 64 + for _, credit := range credits { 65 + artist, err := controller.GetArtist(global.DB, credit.Artist.ID) 66 + if err != nil { 67 + fmt.Printf("FATAL: Failed to serve release %s: Artists: %s\n", release.ID, err) 68 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 69 + return 70 + } 71 + 72 + response.Credits = append(response.Credits, Credit{ 73 + Artist: artist, 74 + Role: credit.Role, 75 + Primary: credit.Primary, 76 + }) 77 + } 78 + 79 + // get tracks 80 + tracks, err := controller.GetReleaseTracks(global.DB, release.ID) 81 + if err != nil { 82 + fmt.Printf("FATAL: Failed to serve release %s: Tracks: %s\n", release.ID, err) 83 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 84 + return 85 + } 86 + for _, track := range tracks { 87 + response.Tracks = append(response.Tracks, Track{ 88 + Title: track.Title, 89 + Description: track.Description, 90 + Lyrics: track.Lyrics, 91 + }) 92 + } 93 + 94 + // get links 95 + links, err := controller.GetReleaseLinks(global.DB, release.ID) 96 + if err != nil { 97 + fmt.Printf("FATAL: Failed to serve release %s: Links: %s\n", release.ID, err) 98 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 99 + return 100 + } 101 + for _, link := range links { 102 + response.Links[link.Name] = link.URL 103 + } 104 + } 105 + 106 + w.Header().Add("Content-Type", "application/json") 107 + err := json.NewEncoder(w).Encode(response) 108 + if err != nil { 109 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 110 + return 111 + } 112 + }) 113 + } 114 + 19 115 func ServeCatalog() http.Handler { 20 116 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 21 - releases, err := music.GetAllReleases(global.DB, false, 0, true) 117 + releases, err := controller.GetAllReleases(global.DB, false, 0, true) 22 118 if err != nil { 23 119 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 24 120 return ··· 95 191 96 192 if release.Artwork == "" { release.Artwork = "/img/default-cover-art.png" } 97 193 98 - err = music.CreateRelease(global.DB, &release) 194 + err = controller.CreateRelease(global.DB, &release) 99 195 if err != nil { 100 196 if strings.Contains(err.Error(), "duplicate key") { 101 197 http.Error(w, fmt.Sprintf("Release %s already exists\n", release.ID), http.StatusBadRequest) ··· 173 269 } 174 270 } 175 271 176 - err = music.UpdateRelease(global.DB, release) 272 + err = controller.UpdateRelease(global.DB, release) 177 273 if err != nil { 178 274 if strings.Contains(err.Error(), "no rows") { 179 275 http.NotFound(w, r) ··· 194 290 return 195 291 } 196 292 197 - err = music.UpdateReleaseTracks(global.DB, release.ID, trackIDs) 293 + err = controller.UpdateReleaseTracks(global.DB, release.ID, trackIDs) 198 294 if err != nil { 199 295 if strings.Contains(err.Error(), "no rows") { 200 296 http.NotFound(w, r) ··· 231 327 }) 232 328 } 233 329 234 - err = music.UpdateReleaseCredits(global.DB, release.ID, credits) 330 + err = controller.UpdateReleaseCredits(global.DB, release.ID, credits) 235 331 if err != nil { 236 332 if strings.Contains(err.Error(), "duplicate key") { 237 333 http.Error(w, "Artists may only be credited once\n", http.StatusBadRequest) ··· 261 357 return 262 358 } 263 359 264 - err = music.UpdateReleaseLinks(global.DB, release.ID, links) 360 + err = controller.UpdateReleaseLinks(global.DB, release.ID, links) 265 361 if err != nil { 266 362 if strings.Contains(err.Error(), "no rows") { 267 363 http.NotFound(w, r) ··· 275 371 276 372 func DeleteRelease(release *model.Release) http.Handler { 277 373 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 278 - err := music.DeleteRelease(global.DB, release.ID) 374 + err := controller.DeleteRelease(global.DB, release.ID) 279 375 if err != nil { 280 376 if strings.Contains(err.Error(), "no rows") { 281 377 http.NotFound(w, r)
+7 -7
api/track.go
··· 6 6 "net/http" 7 7 8 8 "arimelody-web/global" 9 - music "arimelody-web/music/controller" 10 - "arimelody-web/music/model" 9 + "arimelody-web/controller" 10 + "arimelody-web/model" 11 11 ) 12 12 13 13 type ( ··· 26 26 var tracks = []Track{} 27 27 28 28 var dbTracks = []*model.Track{} 29 - dbTracks, err := music.GetAllTracks(global.DB) 29 + dbTracks, err := controller.GetAllTracks(global.DB) 30 30 if err != nil { 31 31 fmt.Printf("FATAL: Failed to pull tracks from DB: %s\n", err) 32 32 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 50 50 51 51 func ServeTrack(track *model.Track) http.Handler { 52 52 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 53 - dbReleases, err := music.GetTrackReleases(global.DB, track.ID, false) 53 + dbReleases, err := controller.GetTrackReleases(global.DB, track.ID, false) 54 54 if err != nil { 55 55 fmt.Printf("FATAL: Failed to pull track releases for %s from DB: %s\n", track.ID, err) 56 56 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 89 89 return 90 90 } 91 91 92 - id, err := music.CreateTrack(global.DB, &track) 92 + id, err := controller.CreateTrack(global.DB, &track) 93 93 if err != nil { 94 94 fmt.Printf("FATAL: Failed to create track: %s\n", err) 95 95 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 120 120 return 121 121 } 122 122 123 - err = music.UpdateTrack(global.DB, track) 123 + err = controller.UpdateTrack(global.DB, track) 124 124 if err != nil { 125 125 fmt.Printf("Failed to update track %s: %s\n", track.ID, err) 126 126 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 143 143 } 144 144 145 145 var trackID = r.URL.Path[1:] 146 - err := music.DeleteTrack(global.DB, trackID) 146 + err := controller.DeleteTrack(global.DB, trackID) 147 147 if err != nil { 148 148 fmt.Printf("Failed to delete track %s: %s\n", trackID, err) 149 149 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+2 -16
discord/discord.go
··· 15 15 const API_ENDPOINT = "https://discord.com/api/v10" 16 16 17 17 var CREDENTIALS_PROVIDED = true 18 - var CLIENT_ID = func() string { 19 - id := os.Getenv("DISCORD_CLIENT") 20 - if id == "" { 21 - fmt.Printf("WARN: Discord client ID (DISCORD_CLIENT) was not provided. Admin login will be unavailable.\n") 22 - CREDENTIALS_PROVIDED = false 23 - } 24 - return id 25 - }() 26 - var CLIENT_SECRET = func() string { 27 - secret := os.Getenv("DISCORD_SECRET") 28 - if secret == "" { 29 - fmt.Printf("WARN: Discord secret (DISCORD_SECRET) was not provided. Admin login will be unavailable.\n") 30 - CREDENTIALS_PROVIDED = false 31 - } 32 - return secret 33 - }() 18 + var CLIENT_ID = os.Getenv("DISCORD_CLIENT") 19 + var CLIENT_SECRET = os.Getenv("DISCORD_SECRET") 34 20 var OAUTH_CALLBACK_URI = fmt.Sprintf("%s/admin/login", global.HTTP_DOMAIN) 35 21 var REDIRECT_URI = fmt.Sprintf("https://discord.com/oauth2/authorize?client_id=%s&response_type=code&redirect_uri=%s&scope=identify", CLIENT_ID, OAUTH_CALLBACK_URI) 36 22
+2 -2
main.go
··· 12 12 "arimelody-web/admin" 13 13 "arimelody-web/api" 14 14 "arimelody-web/global" 15 - musicView "arimelody-web/music/view" 15 + "arimelody-web/view" 16 16 "arimelody-web/templates" 17 17 18 18 "github.com/jmoiron/sqlx" ··· 49 49 50 50 mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler())) 51 51 mux.Handle("/api/", http.StripPrefix("/api", api.Handler())) 52 - mux.Handle("/music/", http.StripPrefix("/music", musicView.Handler())) 52 + mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler())) 53 53 mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler("uploads"))) 54 54 mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 55 55 if r.URL.Path == "/" || r.URL.Path == "/index.html" {
+4 -3
music/controller/artist.go controller/artist.go
··· 1 - package music 1 + package controller 2 2 3 3 import ( 4 - "arimelody-web/music/model" 4 + "arimelody-web/model" 5 5 "github.com/jmoiron/sqlx" 6 6 ) 7 7 ··· 45 45 } 46 46 47 47 func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.Credit, error) { 48 - var query string = "SELECT release.id,release.title,release.artwork,artist.id,artist.name,artist.website,artist.avatar,role,is_primary "+ 48 + var query string = "SELECT release.id,title,artwork,release_date,artist.id,name,website,avatar,role,is_primary "+ 49 49 "FROM musiccredit "+ 50 50 "JOIN musicrelease AS release ON release=release.id "+ 51 51 "JOIN artist ON artist=artist.id "+ ··· 69 69 &credit.Release.ID, 70 70 &credit.Release.Title, 71 71 &credit.Release.Artwork, 72 + &credit.Release.ReleaseDate, 72 73 &credit.Artist.ID, 73 74 &credit.Artist.Name, 74 75 &credit.Artist.Website,
+2 -2
music/controller/release.go controller/release.go
··· 1 - package music 1 + package controller 2 2 3 3 import ( 4 4 "errors" 5 5 "fmt" 6 6 7 - "arimelody-web/music/model" 7 + "arimelody-web/model" 8 8 "github.com/jmoiron/sqlx" 9 9 ) 10 10
+2 -2
music/controller/track.go controller/track.go
··· 1 - package music 1 + package controller 2 2 3 3 import ( 4 - "arimelody-web/music/model" 4 + "arimelody-web/model" 5 5 "github.com/jmoiron/sqlx" 6 6 ) 7 7
music/model/artist.go model/artist.go
music/model/credit.go model/credit.go
music/model/link.go model/link.go
music/model/release.go model/release.go
music/model/track.go model/track.go
-56
music/view/music.go
··· 1 - package view 2 - 3 - import ( 4 - "fmt" 5 - "net/http" 6 - 7 - "arimelody-web/global" 8 - music "arimelody-web/music/controller" 9 - "arimelody-web/music/model" 10 - "arimelody-web/templates" 11 - ) 12 - 13 - // HTTP HANDLER METHODS 14 - 15 - func Handler() http.Handler { 16 - mux := http.NewServeMux() 17 - 18 - mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 19 - if r.URL.Path == "/" { 20 - ServeCatalog().ServeHTTP(w, r) 21 - return 22 - } 23 - 24 - release, err := music.GetRelease(global.DB, r.URL.Path[1:], true) 25 - if err != nil { 26 - http.NotFound(w, r) 27 - return 28 - } 29 - 30 - ServeGateway(release).ServeHTTP(w, r) 31 - })) 32 - 33 - return mux 34 - } 35 - 36 - func ServeCatalog() http.Handler { 37 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 38 - releases, err := music.GetAllReleases(global.DB, true, 0, true) 39 - if err != nil { 40 - fmt.Printf("FATAL: Failed to pull releases for catalog: %s\n", err) 41 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 42 - return 43 - } 44 - 45 - for _, release := range releases { 46 - if !release.IsReleased() { 47 - release.ReleaseType = model.Upcoming 48 - } 49 - } 50 - 51 - err = templates.Pages["music"].Execute(w, releases) 52 - if err != nil { 53 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 54 - } 55 - }) 56 - }
-136
music/view/release.go
··· 1 - package view 2 - 3 - import ( 4 - "encoding/json" 5 - "fmt" 6 - "net/http" 7 - 8 - "arimelody-web/admin" 9 - "arimelody-web/global" 10 - "arimelody-web/music/model" 11 - db "arimelody-web/music/controller" 12 - "arimelody-web/templates" 13 - ) 14 - 15 - type ( 16 - Track struct { 17 - Title string `json:"title"` 18 - Description string `json:"description"` 19 - Lyrics string `json:"lyrics"` 20 - } 21 - 22 - Credit struct { 23 - *model.Artist 24 - Role string `json:"role"` 25 - Primary bool `json:"primary"` 26 - } 27 - 28 - Release struct { 29 - *model.Release 30 - Tracks []Track `json:"tracks"` 31 - Credits []Credit `json:"credits"` 32 - Links map[string]string `json:"links"` 33 - } 34 - ) 35 - 36 - func ServeRelease(release *model.Release) http.Handler { 37 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 38 - // only allow authorised users to view hidden releases 39 - authorised := admin.GetSession(r) != nil 40 - if !authorised && !release.Visible { 41 - http.NotFound(w, r) 42 - return 43 - } 44 - 45 - response := Release{ 46 - Release: release, 47 - Tracks: []Track{}, 48 - Credits: []Credit{}, 49 - Links: make(map[string]string), 50 - } 51 - 52 - if authorised || release.IsReleased() { 53 - // get credits 54 - credits, err := db.GetReleaseCredits(global.DB, release.ID) 55 - if err != nil { 56 - fmt.Printf("FATAL: Failed to serve release %s: Credits: %s\n", release.ID, err) 57 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 58 - return 59 - } 60 - for _, credit := range credits { 61 - artist, err := db.GetArtist(global.DB, credit.Artist.ID) 62 - if err != nil { 63 - fmt.Printf("FATAL: Failed to serve release %s: Artists: %s\n", release.ID, err) 64 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 65 - return 66 - } 67 - 68 - response.Credits = append(response.Credits, Credit{ 69 - Artist: artist, 70 - Role: credit.Role, 71 - Primary: credit.Primary, 72 - }) 73 - } 74 - 75 - // get tracks 76 - tracks, err := db.GetReleaseTracks(global.DB, release.ID) 77 - if err != nil { 78 - fmt.Printf("FATAL: Failed to serve release %s: Tracks: %s\n", release.ID, err) 79 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 80 - return 81 - } 82 - for _, track := range tracks { 83 - response.Tracks = append(response.Tracks, Track{ 84 - Title: track.Title, 85 - Description: track.Description, 86 - Lyrics: track.Lyrics, 87 - }) 88 - } 89 - 90 - // get links 91 - links, err := db.GetReleaseLinks(global.DB, release.ID) 92 - if err != nil { 93 - fmt.Printf("FATAL: Failed to serve release %s: Links: %s\n", release.ID, err) 94 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 95 - return 96 - } 97 - for _, link := range links { 98 - response.Links[link.Name] = link.URL 99 - } 100 - } 101 - 102 - w.Header().Add("Content-Type", "application/json") 103 - err := json.NewEncoder(w).Encode(response) 104 - if err != nil { 105 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 106 - return 107 - } 108 - }) 109 - } 110 - 111 - func ServeGateway(release *model.Release) http.Handler { 112 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 113 - // only allow authorised users to view hidden releases 114 - authorised := admin.GetSession(r) != nil 115 - if !authorised && !release.Visible { 116 - http.NotFound(w, r) 117 - return 118 - } 119 - 120 - response := *release 121 - 122 - if authorised || release.IsReleased() { 123 - response.Tracks = release.Tracks 124 - response.Credits = release.Credits 125 - response.Links = release.Links 126 - } 127 - 128 - err := templates.Pages["music-gateway"].Execute(w, response) 129 - 130 - if err != nil { 131 - fmt.Printf("Error rendering music gateway for %s: %s\n", release.ID, err) 132 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 133 - return 134 - } 135 - }) 136 - }
+84
view/music.go
··· 1 + package view 2 + 3 + import ( 4 + "fmt" 5 + "net/http" 6 + 7 + "arimelody-web/admin" 8 + "arimelody-web/controller" 9 + "arimelody-web/global" 10 + "arimelody-web/model" 11 + "arimelody-web/templates" 12 + ) 13 + 14 + // HTTP HANDLER METHODS 15 + 16 + func MusicHandler() http.Handler { 17 + mux := http.NewServeMux() 18 + 19 + mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 + if r.URL.Path == "/" { 21 + ServeCatalog().ServeHTTP(w, r) 22 + return 23 + } 24 + 25 + release, err := controller.GetRelease(global.DB, r.URL.Path[1:], true) 26 + if err != nil { 27 + http.NotFound(w, r) 28 + return 29 + } 30 + 31 + ServeGateway(release).ServeHTTP(w, r) 32 + })) 33 + 34 + return mux 35 + } 36 + 37 + func ServeCatalog() http.Handler { 38 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 39 + releases, err := controller.GetAllReleases(global.DB, true, 0, true) 40 + if err != nil { 41 + fmt.Printf("FATAL: Failed to pull releases for catalog: %s\n", err) 42 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 43 + return 44 + } 45 + 46 + for _, release := range releases { 47 + if !release.IsReleased() { 48 + release.ReleaseType = model.Upcoming 49 + } 50 + } 51 + 52 + err = templates.Pages["music"].Execute(w, releases) 53 + if err != nil { 54 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 55 + } 56 + }) 57 + } 58 + 59 + func ServeGateway(release *model.Release) http.Handler { 60 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 61 + // only allow authorised users to view hidden releases 62 + authorised := admin.GetSession(r) != nil 63 + if !authorised && !release.Visible { 64 + http.NotFound(w, r) 65 + return 66 + } 67 + 68 + response := *release 69 + 70 + if authorised || release.IsReleased() { 71 + response.Tracks = release.Tracks 72 + response.Credits = release.Credits 73 + response.Links = release.Links 74 + } 75 + 76 + err := templates.Pages["music-gateway"].Execute(w, response) 77 + 78 + if err != nil { 79 + fmt.Printf("Error rendering music gateway for %s: %s\n", release.ID, err) 80 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 81 + return 82 + } 83 + }) 84 + }
+10 -16
views/music.html
··· 59 59 </h2> 60 60 <div class="answer"> 61 61 <p> 62 - <strong class="big">yes!</strong> well, in most cases... 62 + <strong class="big">yes!*</strong> <em>in most cases...</em> 63 63 </p> 64 64 <p> 65 - from <a href="/music/dream">Dream (2022)</a> onward, all of my <em>self-released</em> songs are 66 - licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">Creative Commons Attribution-ShareAlike 4.0</a>. 65 + all of my <em>self-released</em> songs are licensed under 66 + <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">Creative Commons Attribution-ShareAlike 4.0</a>. 67 67 anyone may use and remix these songs freely, so long as they provide credit back to me and link back to this license! 68 68 please note that all derivative works must inherit this license. 69 69 </p> ··· 71 71 a great example of some credit text would be as follows: 72 72 </p> 73 73 <blockquote> 74 - music used: mellodoot - Dream<br> 75 - <a href="/music/dream">https://arimelody.me/music/dream</a><br> 76 - licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a>. 74 + music used: ari melody - free2play<br> 75 + <a href="/music/free2play">https://arimelody.me/music/free2play</a><br> 76 + licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a>. 77 77 </blockquote> 78 78 <p> 79 - for any songs prior to this, they were all either released by me (in which case, i honestly 80 - don't mind), or in collaboration with chill people who i don't see having an issue with it. 81 - do be sure to ask them about it, though! 79 + if the song you want to use is <em>not</em> released by me (i.e. under a record label), their usage rights 80 + will likely trump whatever i'd otherwise have in mind. i'll try to negotiate some nice terms, though! 82 81 </p> 83 82 <p> 84 - in the event the song you want to use is released under some other label, their usage rights 85 - will more than likely trump whatever i'd otherwise have in mind. i'll try to negotiate some 86 - nice terms, though! ;3 87 - </p> 88 - <p> 89 - i love the idea of other creators using my songs in their work, so if you do happen to use 90 - my stuff in a work you're particularly proud of, feel free to send it my way! 83 + i believe that encouraging creative use of artistic works is better than stifling any use at all. 84 + if you do happen to use my work in something you're particularly proud of, feel free to send it my way! 91 85 </p> 92 86 <p> 93 87 &gt; <a href="mailto:ari@arimelody.me">ari@arimelody.me</a>