No description provided.
There are no lyrics.
{{end}}
diff --git a/admin/views/index.html b/admin/views/index.html
index f88576d..8f42e0e 100644
--- a/admin/views/index.html
+++ b/admin/views/index.html
@@ -22,13 +22,13 @@
{{range $Artist := .Artists}}
{{end}}
{{if not .Artists}}
@@ -49,12 +49,12 @@
{{$Track.Title}}
{{if $Track.Description}}
-
{{$Track.Description}}
+
{{$Track.GetDescriptionHTML}}
{{else}}
No description provided.
{{end}}
{{if $Track.Lyrics}}
-
{{$Track.Lyrics}}
+
{{$Track.GetLyricsHTML}}
{{else}}
There are no lyrics.
{{end}}
diff --git a/api/api.go b/api/api.go
index 443904f..699cf1c 100644
--- a/api/api.go
+++ b/api/api.go
@@ -5,10 +5,10 @@ import (
"net/http"
"strings"
- "arimelody.me/arimelody.me/admin"
- "arimelody.me/arimelody.me/global"
- "arimelody.me/arimelody.me/music/model"
- music "arimelody.me/arimelody.me/music/view"
+ "arimelody-web/admin"
+ "arimelody-web/global"
+ music "arimelody-web/music/controller"
+ musicView "arimelody-web/music/view"
)
func Handler() http.Handler {
@@ -18,8 +18,7 @@ func Handler() http.Handler {
mux.Handle("/v1/artist/", http.StripPrefix("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var artistID = strings.Split(r.URL.Path[1:], "/")[0]
- var artist model.Artist
- err := global.DB.Get(&artist, "SELECT * FROM artist WHERE id=$1", artistID)
+ artist, err := music.GetArtist(global.DB, artistID)
if err != nil {
if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r)
@@ -61,8 +60,7 @@ func Handler() http.Handler {
mux.Handle("/v1/music/", http.StripPrefix("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var releaseID = strings.Split(r.URL.Path[1:], "/")[0]
- var release model.Release
- err := global.DB.Get(&release, "SELECT * FROM musicrelease WHERE id=$1", releaseID)
+ release, err := music.GetRelease(global.DB, releaseID, true)
if err != nil {
if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r)
@@ -76,7 +74,7 @@ func Handler() http.Handler {
switch r.Method {
case http.MethodGet:
// GET /api/v1/music/{id}
- music.ServeRelease(release).ServeHTTP(w, r)
+ musicView.ServeRelease(release).ServeHTTP(w, r)
case http.MethodPut:
// PUT /api/v1/music/{id} (admin)
admin.MustAuthorise(UpdateRelease(release)).ServeHTTP(w, r)
@@ -104,8 +102,7 @@ func Handler() http.Handler {
mux.Handle("/v1/track/", http.StripPrefix("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var trackID = strings.Split(r.URL.Path[1:], "/")[0]
- var track model.Track
- err := global.DB.Get(&track, "SELECT * FROM musictrack WHERE id=$1", trackID)
+ track, err := music.GetTrack(global.DB, trackID)
if err != nil {
if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r)
diff --git a/api/artist.go b/api/artist.go
index 164e535..2d5f5e6 100644
--- a/api/artist.go
+++ b/api/artist.go
@@ -3,21 +3,18 @@ package api
import (
"encoding/json"
"fmt"
+ "io/fs"
"net/http"
+ "os"
+ "path/filepath"
"strings"
- "arimelody.me/arimelody.me/global"
- "arimelody.me/arimelody.me/music/model"
- db "arimelody.me/arimelody.me/music/controller"
+ "arimelody-web/global"
+ db "arimelody-web/music/controller"
+ music "arimelody-web/music/controller"
+ "arimelody-web/music/model"
)
-type artistJSON struct {
- ID string `json:"id"`
- Name *string `json:"name"`
- Website *string `json:"website"`
- Avatar *string `json:"avatar"`
-}
-
func ServeAllArtists() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var artists = []*model.Artist{}
@@ -36,7 +33,7 @@ func ServeAllArtists() http.Handler {
})
}
-func ServeArtist(artist model.Artist) http.Handler {
+func ServeArtist(artist *model.Artist) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
type (
creditJSON struct {
@@ -44,7 +41,7 @@ func ServeArtist(artist model.Artist) http.Handler {
Primary bool `json:"primary"`
}
artistJSON struct {
- model.Artist
+ *model.Artist
Credits map[string]creditJSON `json:"credits"`
}
)
@@ -78,39 +75,23 @@ func ServeArtist(artist model.Artist) http.Handler {
func CreateArtist() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- var data artistJSON
- err := json.NewDecoder(r.Body).Decode(&data)
+ var artist model.Artist
+ err := json.NewDecoder(r.Body).Decode(&artist)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
- if data.ID == "" {
+ if artist.ID == "" {
http.Error(w, "Artist ID cannot be blank\n", http.StatusBadRequest)
return
}
- if data.Name == nil || *data.Name == "" {
- http.Error(w, "Artist name cannot be blank\n", http.StatusBadRequest)
- return
- }
+ if artist.Name == "" { artist.Name = artist.ID }
- var artist = model.Artist{
- ID: data.ID,
- Name: *data.Name,
- Website: *data.Website,
- Avatar: *data.Avatar,
- }
-
- _, err = global.DB.Exec(
- "INSERT INTO artist (id, name, website, avatar) "+
- "VALUES ($1, $2, $3, $4)",
- artist.ID,
- artist.Name,
- artist.Website,
- artist.Avatar)
+ err = music.CreateArtist(global.DB, &artist)
if err != nil {
if strings.Contains(err.Error(), "duplicate key") {
- http.Error(w, fmt.Sprintf("Artist %s already exists\n", data.ID), http.StatusBadRequest)
+ http.Error(w, fmt.Sprintf("Artist %s already exists\n", artist.ID), http.StatusBadRequest)
return
}
fmt.Printf("FATAL: Failed to create artist %s: %s\n", artist.ID, err)
@@ -122,43 +103,59 @@ func CreateArtist() http.Handler {
})
}
-func UpdateArtist(artist model.Artist) http.Handler {
+func UpdateArtist(artist *model.Artist) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- var data artistJSON
- err := json.NewDecoder(r.Body).Decode(&data)
+ err := json.NewDecoder(r.Body).Decode(&artist)
if err != nil {
fmt.Printf("FATAL: Failed to update artist: %s\n", err)
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
- if data.ID != "" { artist.ID = data.ID }
- if data.Name != nil { artist.Name = *data.Name }
- if data.Website != nil { artist.Website = *data.Website }
- if data.Avatar != nil { artist.Avatar = *data.Avatar }
+ if artist.Avatar == "" {
+ artist.Avatar = "/img/default-avatar.png"
+ } else {
+ if strings.Contains(artist.Avatar, ";base64,") {
+ var artworkDirectory = filepath.Join("uploads", "avatar")
+ filename, err := HandleImageUpload(&artist.Avatar, artworkDirectory, artist.ID)
- _, err = global.DB.Exec(
- "UPDATE artist "+
- "SET name=$2, website=$3, avatar=$4 "+
- "WHERE id=$1",
- artist.ID,
- artist.Name,
- artist.Website,
- artist.Avatar)
+ // clean up files with this ID and different extensions
+ err = filepath.Walk(artworkDirectory, func(path string, info fs.FileInfo, err error) error {
+ if path == filepath.Join(artworkDirectory, filename) { return nil }
+
+ withoutExt := strings.TrimSuffix(path, filepath.Ext(path))
+ if withoutExt != filepath.Join(artworkDirectory, artist.ID) { return nil }
+
+ return os.Remove(path)
+ })
+ if err != nil {
+ fmt.Printf("WARN: Error while cleaning up avatar files: %s\n", err)
+ }
+
+ artist.Avatar = fmt.Sprintf("/uploads/avatar/%s", filename)
+ }
+ }
+
+ err = music.UpdateArtist(global.DB, artist)
if err != nil {
+ if strings.Contains(err.Error(), "no rows") {
+ http.NotFound(w, r)
+ return
+ }
fmt.Printf("FATAL: Failed to update artist %s: %s\n", artist.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}
-func DeleteArtist(artist model.Artist) http.Handler {
+func DeleteArtist(artist *model.Artist) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- _, err := global.DB.Exec(
- "DELETE FROM artist "+
- "WHERE id=$1",
- artist.ID)
+ err := music.DeleteArtist(global.DB, artist.ID)
if err != nil {
+ if strings.Contains(err.Error(), "no rows") {
+ http.NotFound(w, r)
+ return
+ }
fmt.Printf("FATAL: Failed to delete artist %s: %s\n", artist.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
diff --git a/api/release.go b/api/release.go
index fb1ea54..763383f 100644
--- a/api/release.go
+++ b/api/release.go
@@ -10,35 +10,44 @@ import (
"strings"
"time"
- "arimelody.me/arimelody.me/admin"
- "arimelody.me/arimelody.me/global"
- music "arimelody.me/arimelody.me/music/controller"
- "arimelody.me/arimelody.me/music/model"
+ "arimelody-web/admin"
+ "arimelody-web/global"
+ music "arimelody-web/music/controller"
+ "arimelody-web/music/model"
)
func ServeCatalog() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-
- releases := []*model.Release{}
- err := global.DB.Select(&releases, "SELECT * FROM musicrelease ORDER BY release_date DESC")
+ releases, err := music.GetAllReleases(global.DB, false, 0, true)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
- catalog := []model.ReleaseShorthand{}
+ type Release struct {
+ ID string `json:"id"`
+ Title string `json:"title"`
+ ReleaseType model.ReleaseType `json:"type" db:"type"`
+ ReleaseDate time.Time `json:"releaseDate" db:"release_date"`
+ Artwork string `json:"artwork"`
+ Buylink string `json:"buylink"`
+ Copyright string `json:"copyright" db:"copyright"`
+ }
+
+ catalog := []Release{}
authorised := admin.GetSession(r) != nil
for _, release := range releases {
if !release.Visible && !authorised {
continue
}
- catalog = append(catalog, model.ReleaseShorthand{
+ catalog = append(catalog, Release{
ID: release.ID,
Title: release.Title,
ReleaseType: release.ReleaseType,
ReleaseDate: release.ReleaseDate,
Artwork: release.Artwork,
Buylink: release.Buylink,
+ Copyright: release.Copyright,
})
}
@@ -85,7 +94,7 @@ func CreateRelease() http.Handler {
http.Error(w, fmt.Sprintf("Release %s already exists\n", release.ID), http.StatusBadRequest)
return
}
- fmt.Printf("Failed to create release %s: %s\n", release.ID, err)
+ fmt.Printf("FATAL: Failed to create release %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
@@ -100,7 +109,7 @@ func CreateRelease() http.Handler {
})
}
-func UpdateRelease(release model.Release) http.Handler {
+func UpdateRelease(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
http.NotFound(w, r)
@@ -157,15 +166,19 @@ func UpdateRelease(release model.Release) http.Handler {
}
}
- err = music.UpdateRelease(global.DB, &release)
+ err = music.UpdateRelease(global.DB, release)
if err != nil {
+ if strings.Contains(err.Error(), "no rows") {
+ http.NotFound(w, r)
+ return
+ }
fmt.Printf("FATAL: Failed to update release %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}
-func UpdateReleaseTracks(release model.Release) http.Handler {
+func UpdateReleaseTracks(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var trackIDs = []string{}
err := json.NewDecoder(r.Body).Decode(&trackIDs)
@@ -174,15 +187,19 @@ func UpdateReleaseTracks(release model.Release) http.Handler {
return
}
- err = music.UpdateReleaseTracks(global.DB, &release, trackIDs)
+ err = music.UpdateReleaseTracks(global.DB, release.ID, trackIDs)
if err != nil {
- fmt.Printf("Failed to update tracks for %s: %s\n", release.ID, err)
+ if strings.Contains(err.Error(), "no rows") {
+ http.NotFound(w, r)
+ return
+ }
+ fmt.Printf("FATAL: Failed to update tracks for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}
-func UpdateReleaseCredits(release model.Release) http.Handler {
+func UpdateReleaseCredits(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
type creditJSON struct {
Artist string
@@ -196,9 +213,9 @@ func UpdateReleaseCredits(release model.Release) http.Handler {
return
}
- var credits []model.Credit
+ var credits []*model.Credit
for _, credit := range data {
- credits = append(credits, model.Credit{
+ credits = append(credits, &model.Credit{
Artist: model.Artist{
ID: credit.Artist,
},
@@ -207,19 +224,23 @@ func UpdateReleaseCredits(release model.Release) http.Handler {
})
}
- err = music.UpdateReleaseCredits(global.DB, &release, credits)
+ err = music.UpdateReleaseCredits(global.DB, release.ID, credits)
if err != nil {
if strings.Contains(err.Error(), "duplicate key") {
http.Error(w, "Artists may only be credited once\n", http.StatusBadRequest)
return
}
- fmt.Printf("Failed to update links for %s: %s\n", release.ID, err)
+ if strings.Contains(err.Error(), "no rows") {
+ http.NotFound(w, r)
+ return
+ }
+ fmt.Printf("FATAL: Failed to update links for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}
-func UpdateReleaseLinks(release model.Release) http.Handler {
+func UpdateReleaseLinks(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut {
http.NotFound(w, r)
@@ -233,30 +254,27 @@ func UpdateReleaseLinks(release model.Release) http.Handler {
return
}
- tx := global.DB.MustBegin()
- tx.MustExec("DELETE FROM musiclink WHERE release=$1", release.ID)
- for _, link := range links {
- tx.MustExec(
- "INSERT INTO musiclink "+
- "(release, name, url) "+
- "VALUES ($1, $2, $3)",
- release.ID,
- link.Name,
- link.URL)
- }
- err = tx.Commit()
+ err = music.UpdateReleaseLinks(global.DB, release.ID, links)
if err != nil {
- fmt.Printf("Failed to update links for %s: %s\n", release.ID, err)
+ if strings.Contains(err.Error(), "no rows") {
+ http.NotFound(w, r)
+ return
+ }
+ fmt.Printf("FATAL: Failed to update links for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}
-func DeleteRelease(release model.Release) http.Handler {
+func DeleteRelease(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- _, err := global.DB.Exec("DELETE FROM musicrelease WHERE id=$1", release.ID)
+ err := music.DeleteRelease(global.DB, release.ID)
if err != nil {
- fmt.Printf("Failed to delete release %s: %s\n", release.ID, err)
+ if strings.Contains(err.Error(), "no rows") {
+ http.NotFound(w, r)
+ return
+ }
+ fmt.Printf("FATAL: Failed to delete release %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
diff --git a/api/track.go b/api/track.go
index 75080c5..fd4018b 100644
--- a/api/track.go
+++ b/api/track.go
@@ -5,15 +5,15 @@ import (
"fmt"
"net/http"
- "arimelody.me/arimelody.me/global"
- music "arimelody.me/arimelody.me/music/controller"
- "arimelody.me/arimelody.me/music/model"
+ "arimelody-web/global"
+ music "arimelody-web/music/controller"
+ "arimelody-web/music/model"
)
type (
Track struct {
- model.Track
- Releases []model.ReleaseShorthand
+ *model.Track
+ Releases []string `json:"releases"`
}
)
@@ -48,13 +48,18 @@ func ServeAllTracks() http.Handler {
})
}
-func ServeTrack(track model.Track) http.Handler {
+func ServeTrack(track *model.Track) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- releases, err := music.GetTrackReleases(global.DB, track.ID)
+ dbReleases, err := music.GetTrackReleases(global.DB, track.ID, false)
if err != nil {
fmt.Printf("FATAL: Failed to pull track releases for %s from DB: %s\n", track.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
+
+ releases := []string{}
+ for _, release := range dbReleases {
+ releases = append(releases, release.ID)
+ }
w.Header().Add("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(Track{ track, releases })
@@ -97,7 +102,7 @@ func CreateTrack() http.Handler {
})
}
-func UpdateTrack(track model.Track) http.Handler {
+func UpdateTrack(track *model.Track) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut || r.URL.Path == "/" {
http.NotFound(w, r)
@@ -115,7 +120,7 @@ func UpdateTrack(track model.Track) http.Handler {
return
}
- err = music.UpdateTrack(global.DB, &track)
+ err = music.UpdateTrack(global.DB, track)
if err != nil {
fmt.Printf("Failed to update track %s: %s\n", track.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@@ -130,7 +135,7 @@ func UpdateTrack(track model.Track) http.Handler {
})
}
-func DeleteTrack(track model.Track) http.Handler {
+func DeleteTrack(track *model.Track) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete || r.URL.Path == "/" {
http.NotFound(w, r)
diff --git a/api/uploads.go b/api/uploads.go
index 23479b7..f2fe297 100644
--- a/api/uploads.go
+++ b/api/uploads.go
@@ -35,6 +35,8 @@ func HandleImageUpload(data *string, directory string, filename string) (string,
}
defer file.Close()
+ // TODO: generate compressed versions of image (512x512?)
+
buffer := bufio.NewWriter(file)
_, err = buffer.Write(imageData)
if err != nil {
diff --git a/discord/discord.go b/discord/discord.go
index e749daa..c3b3cec 100644
--- a/discord/discord.go
+++ b/discord/discord.go
@@ -6,26 +6,27 @@ import (
"fmt"
"net/http"
"net/url"
+ "os"
"strings"
- "arimelody.me/arimelody.me/global"
+ "arimelody-web/global"
)
const API_ENDPOINT = "https://discord.com/api/v10"
var CREDENTIALS_PROVIDED = true
var CLIENT_ID = func() string {
- id := global.Args["discordClient"]
+ id := os.Getenv("DISCORD_CLIENT")
if id == "" {
- fmt.Printf("WARN: Discord client ID (-discordClient) was not provided. Admin login will be unavailable.\n")
+ fmt.Printf("WARN: Discord client ID (DISCORD_CLIENT) was not provided. Admin login will be unavailable.\n")
CREDENTIALS_PROVIDED = false
}
return id
}()
var CLIENT_SECRET = func() string {
- secret := global.Args["discordSecret"]
- if secret== "" {
- fmt.Printf("WARN: Discord secret (-discordSecret) was not provided. Admin login will be unavailable.\n")
+ secret := os.Getenv("DISCORD_SECRET")
+ if secret == "" {
+ fmt.Printf("WARN: Discord secret (DISCORD_SECRET) was not provided. Admin login will be unavailable.\n")
CREDENTIALS_PROVIDED = false
}
return secret
@@ -107,7 +108,6 @@ func GetDiscordUserFromAuth(token string) (DiscordUser, error) {
}
auth_info := AuthInfoResponse{}
-
err = json.NewDecoder(res.Body).Decode(&auth_info)
if err != nil {
return DiscordUser{}, errors.New(fmt.Sprintf("Failed to parse auth information from discord: %s\n", err))
diff --git a/docker-compose-db.yml b/docker-compose-db.yml
deleted file mode 100644
index 4722286..0000000
--- a/docker-compose-db.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-version: '3.9'
-
-services:
- db:
- image: postgres:16.1-alpine3.18
- container_name: arimelody.me-db
- ports:
- - 5432:5432
- volumes:
- - arimelody-db:/var/lib/postgresql/data
- environment:
- POSTGRES_DB: arimelody
- POSTGRES_USER: arimelody
- POSTGRES_PASSWORD: fuckingpassword
-
-volumes:
- arimelody-db:
- external: true
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..e29b006
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,22 @@
+services:
+ web:
+ image: docker.arimelody.me/arimelody.me:latest
+ build: .
+ ports:
+ - 8080:8080
+ volumes:
+ - ./uploads:/app/uploads
+ environment:
+ HTTP_DOMAIN: "https://arimelody.me"
+ ARIMELODY_DB_HOST: db
+ DISCORD_ADMIN: # your discord user ID.
+ DISCORD_CLIENT: # your discord OAuth client ID.
+ DISCORD_SECRET: # your discord OAuth secret.
+ db:
+ image: postgres:16.1-alpine3.18
+ volumes:
+ - ./db:/var/lib/postgresql/data
+ environment:
+ POSTGRES_DB: arimelody
+ POSTGRES_USER: arimelody
+ POSTGRES_PASSWORD: fuckingpassword
diff --git a/global/data.go b/global/data.go
index b8d0c02..fb641fc 100644
--- a/global/data.go
+++ b/global/data.go
@@ -35,11 +35,11 @@ var Args = func() map[string]string {
}()
var HTTP_DOMAIN = func() string {
- domain := Args["httpDomain"]
- if domain != "" {
- return domain
+ domain := os.Getenv("HTTP_DOMAIN")
+ if domain == "" {
+ return "https://arimelody.me"
}
- return "https://arimelody.me"
+ return domain
}()
var DB *sqlx.DB
diff --git a/global/funcs.go b/global/funcs.go
index f0f3633..33198ab 100644
--- a/global/funcs.go
+++ b/global/funcs.go
@@ -6,7 +6,7 @@ import (
"strconv"
"time"
- "arimelody.me/arimelody.me/colour"
+ "arimelody-web/colour"
)
func DefaultHeaders(next http.Handler) http.Handler {
diff --git a/go.mod b/go.mod
index 1f74015..e2beaf4 100644
--- a/go.mod
+++ b/go.mod
@@ -1,19 +1,8 @@
-module arimelody.me/arimelody.me
+module arimelody-web
go 1.22
require (
- github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47
- github.com/jmoiron/sqlx v1.3.5
+ github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.10.9
)
-
-require (
- github.com/jackc/pgpassfile v1.0.0 // indirect
- github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
- github.com/jackc/pgx/v5 v5.5.5 // indirect
- github.com/jackc/puddle/v2 v2.2.1 // indirect
- golang.org/x/crypto v0.17.0 // indirect
- golang.org/x/sync v0.1.0 // indirect
- golang.org/x/text v0.14.0 // indirect
-)
diff --git a/go.sum b/go.sum
index 83d5b57..f4ce337 100644
--- a/go.sum
+++ b/go.sum
@@ -1,30 +1,10 @@
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
-github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwro3Uin8eqoET7MDA4JnT8YgbCjc/g5E3k=
-github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
-github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
-github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
-github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
-github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
-github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
-github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
-github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
-github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
-github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
-github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
-github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
+github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
+github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
+github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
+github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
diff --git a/main.go b/main.go
index a60524f..c103696 100644
--- a/main.go
+++ b/main.go
@@ -1,6 +1,7 @@
package main
import (
+ "errors"
"fmt"
"log"
"net/http"
@@ -8,11 +9,11 @@ import (
"path/filepath"
"time"
- "arimelody.me/arimelody.me/admin"
- "arimelody.me/arimelody.me/api"
- "arimelody.me/arimelody.me/global"
- musicView "arimelody.me/arimelody.me/music/view"
- "arimelody.me/arimelody.me/templates"
+ "arimelody-web/admin"
+ "arimelody-web/api"
+ "arimelody-web/global"
+ musicView "arimelody-web/music/view"
+ "arimelody-web/templates"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
@@ -22,8 +23,11 @@ const DEFAULT_PORT int = 8080
func main() {
// initialise database connection
+ var dbHost = os.Getenv("ARIMELODY_DB_HOST")
+ if dbHost == "" { dbHost = "127.0.0.1" }
+
var err error
- global.DB, err = sqlx.Connect("postgres", "user=arimelody dbname=arimelody password=fuckingpassword sslmode=disable")
+ global.DB, err = sqlx.Connect("postgres", "host=" + dbHost + " user=arimelody dbname=arimelody password=fuckingpassword sslmode=disable")
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Unable to create database connection pool: %v\n", err)
os.Exit(1)
@@ -64,9 +68,10 @@ func createServeMux() *http.ServeMux {
func staticHandler(directory string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
info, err := os.Stat(filepath.Join(directory, filepath.Clean(r.URL.Path)))
+
// does the file exist?
if err != nil {
- if os.IsNotExist(err) {
+ if errors.Is(err, os.ErrNotExist) {
http.NotFound(w, r)
return
}
diff --git a/music/controller/artist.go b/music/controller/artist.go
index b6a48f5..2ae92e7 100644
--- a/music/controller/artist.go
+++ b/music/controller/artist.go
@@ -1,7 +1,7 @@
package music
import (
- "arimelody.me/arimelody.me/music/model"
+ "arimelody-web/music/model"
"github.com/jmoiron/sqlx"
)
@@ -45,27 +45,54 @@ func GetArtistsNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Artist, err
}
func GetArtistCredits(db *sqlx.DB, artistID string) ([]*model.Credit, error) {
- type DBCredit struct {
- Release string
- Artist string
- Role string
- Primary bool `db:"is_primary"`
- }
- var dbCredits []DBCredit
-
- err := db.Select(&dbCredits, "SELECT * FROM musiccredit WHERE artist=$1", artistID)
+ rows, err := db.Query(
+ "SELECT release.id,release.title,release.artwork,artist.id,artist.name,artist.website,artist.avatar,role,is_primary "+
+ "FROM musiccredit "+
+ "JOIN musicrelease AS release ON release=release.id "+
+ "JOIN artist ON artist=artist.id "+
+ "WHERE artist=$1 "+
+ "ORDER BY release_date DESC",
+ artistID)
if err != nil {
return nil, err
}
-
+ defer rows.Close()
+
+ type NamePrimary struct {
+ Name string `json:"name"`
+ Primary bool `json:"primary" db:"is_primary"`
+ }
var credits []*model.Credit
- for _, credit := range dbCredits {
- credits = append(credits, &model.Credit{
- Release: model.Release{ ID: credit.Release },
- Artist: model.Artist{ ID: credit.Artist },
- Role: credit.Role,
- Primary: credit.Primary,
- })
+ for rows.Next() {
+ var credit model.Credit
+ err = rows.Scan(
+ &credit.Release.ID,
+ &credit.Release.Title,
+ &credit.Release.Artwork,
+ &credit.Artist.ID,
+ &credit.Artist.Name,
+ &credit.Artist.Website,
+ &credit.Artist.Avatar,
+ &credit.Role,
+ &credit.Primary,
+ )
+
+ otherArtists := []NamePrimary{}
+ err = db.Select(&otherArtists,
+ "SELECT name,is_primary FROM artist "+
+ "JOIN musiccredit ON artist=id "+
+ "WHERE release=$1",
+ credit.Release.ID)
+ for _, otherCredit := range otherArtists {
+ credit.Release.Credits = append(credit.Release.Credits, &model.Credit{
+ Artist: model.Artist{
+ Name: otherCredit.Name,
+ },
+ Primary: otherCredit.Primary,
+ })
+ }
+
+ credits = append(credits, &credit)
}
return credits, nil
@@ -104,11 +131,11 @@ func UpdateArtist(db *sqlx.DB, artist *model.Artist) error {
return nil
}
-func DeleteArtist(db *sqlx.DB, artist *model.Artist) error {
+func DeleteArtist(db *sqlx.DB, artistID string) error {
_, err := db.Exec(
"DELETE FROM artist "+
"WHERE id=$1",
- artist.ID,
+ artistID,
)
if err != nil {
return err
diff --git a/music/controller/release.go b/music/controller/release.go
index c314109..4bd2cd6 100644
--- a/music/controller/release.go
+++ b/music/controller/release.go
@@ -4,29 +4,99 @@ import (
"errors"
"fmt"
- "arimelody.me/arimelody.me/music/model"
+ "arimelody-web/music/model"
"github.com/jmoiron/sqlx"
)
-func GetRelease(db *sqlx.DB, id string) (*model.Release, error) {
- var releases = model.Release{}
+func GetRelease(db *sqlx.DB, id string, full bool) (*model.Release, error) {
+ var release = model.Release{}
- err := db.Get(&releases, "SELECT * FROM musicrelease WHERE id=$1", id)
+ err := db.Get(&release, "SELECT * FROM musicrelease WHERE id=$1", id)
if err != nil {
return nil, err
}
- return &releases, nil
+ if full {
+ // get credits
+ credits, err := GetReleaseCredits(db, id)
+ if err != nil {
+ return nil, errors.New(fmt.Sprintf("Credits: %s", err))
+ }
+ for _, credit := range credits {
+ release.Credits = append(release.Credits, credit)
+ }
+
+ // get tracks
+ tracks, err := GetReleaseTracks(db, id)
+ if err != nil {
+ return nil, errors.New(fmt.Sprintf("Tracks: %s", err))
+ }
+ for _, track := range tracks {
+ release.Tracks = append(release.Tracks, track)
+ }
+
+ // get links
+ links, err := GetReleaseLinks(db, id)
+ if err != nil {
+ return nil, errors.New(fmt.Sprintf("Links: %s", err))
+ }
+ for _, link := range links {
+ release.Links = append(release.Links, link)
+ }
+ }
+
+ return &release, nil
}
-func GetAllReleases(db *sqlx.DB) ([]*model.Release, error) {
+func GetAllReleases(db *sqlx.DB, onlyVisible bool, limit int, full bool) ([]*model.Release, error) {
var releases = []*model.Release{}
- err := db.Select(&releases, "SELECT * FROM musicrelease ORDER BY release_date DESC")
+ query := "SELECT * FROM musicrelease"
+ if onlyVisible {
+ query += " WHERE visible=true"
+ }
+ query += " ORDER BY release_date DESC"
+ var err error
+ if limit > 0 {
+ err = db.Select(&releases, query + " LIMIT $1", limit)
+ } else {
+ err = db.Select(&releases, query)
+ }
if err != nil {
return nil, err
}
+ if full {
+ for _, release := range releases {
+ // get credits
+ credits, err := GetReleaseCredits(db, release.ID)
+ if err != nil {
+ return nil, errors.New(fmt.Sprintf("Credits: %s", err))
+ }
+ for _, credit := range credits {
+ release.Credits = append(release.Credits, credit)
+ }
+
+ // get tracks
+ tracks, err := GetReleaseTracks(db, release.ID)
+ if err != nil {
+ return nil, errors.New(fmt.Sprintf("Tracks: %s", err))
+ }
+ for _, track := range tracks {
+ release.Tracks = append(release.Tracks, track)
+ }
+
+ // get links
+ links, err := GetReleaseLinks(db, release.ID)
+ if err != nil {
+ return nil, errors.New(fmt.Sprintf("Links: %s", err))
+ }
+ for _, link := range links {
+ release.Links = append(release.Links, link)
+ }
+ }
+ }
+
return releases, nil
}
@@ -78,49 +148,53 @@ func UpdateRelease(db *sqlx.DB, release *model.Release) error {
return nil
}
-func UpdateReleaseTracks(db *sqlx.DB, release *model.Release, new_tracks []string) error {
- _, err := db.Exec(
- "DELETE FROM musicreleasetrack "+
- "WHERE release=$1",
- release.ID,
- )
+func UpdateReleaseTracks(db *sqlx.DB, releaseID string, new_tracks []string) error {
+ tx, err := db.Begin()
if err != nil {
return err
}
+ _, err = tx.Exec("DELETE FROM musicreleasetrack WHERE release=$1", releaseID)
+ if err != nil {
+ return err
+ }
for i, trackID := range new_tracks {
- _, err = db.Exec(
+ _, err = tx.Exec(
"INSERT INTO musicreleasetrack "+
"(release, track, number) "+
"VALUES ($1, $2, $3)",
- release.ID,
+ releaseID,
trackID,
- i,
- )
+ i)
if err != nil {
return err
}
}
- return nil
-}
-
-func UpdateReleaseCredits(db *sqlx.DB, release *model.Release, new_credits []model.Credit) error {
- _, err := db.Exec(
- "DELETE FROM musiccredit "+
- "WHERE release=$1",
- release.ID,
- )
+ err = tx.Commit()
if err != nil {
return err
}
+ return nil
+}
+
+func UpdateReleaseCredits(db *sqlx.DB, releaseID string, new_credits []*model.Credit) error {
+ tx, err := db.Begin()
+ if err != nil {
+ return err
+ }
+
+ _, err = tx.Exec("DELETE FROM musiccredit WHERE release=$1", releaseID)
+ if err != nil {
+ return err
+ }
for _, credit := range new_credits {
- _, err = db.Exec(
+ _, err = tx.Exec(
"INSERT INTO musiccredit "+
"(release, artist, role, is_primary) "+
"VALUES ($1, $2, $3, $4)",
- release.ID,
+ releaseID,
credit.Artist.ID,
credit.Role,
credit.Primary,
@@ -130,25 +204,31 @@ func UpdateReleaseCredits(db *sqlx.DB, release *model.Release, new_credits []mod
}
}
- return nil
-}
-
-func UpdateReleaseLinks(db *sqlx.DB, release *model.Release, new_links []*model.Link) error {
- _, err := db.Exec(
- "DELETE FROM musiclink "+
- "WHERE release=$1",
- release.ID,
- )
+ err = tx.Commit()
if err != nil {
return err
}
+ return nil
+}
+
+func UpdateReleaseLinks(db *sqlx.DB, releaseID string, new_links []*model.Link) error {
+ tx, err := db.Begin()
+ if err != nil {
+ return err
+ }
+
+ _, err = tx.Exec("DELETE FROM musiclink WHERE release=$1", releaseID)
+ if err != nil {
+ return err
+ }
for _, link := range new_links {
- _, err = db.Exec(
+ fmt.Printf("%s: %s\n", link.Name, link.URL)
+ _, err := tx.Exec(
"INSERT INTO musiclink "+
"(release, name, url) "+
"VALUES ($1, $2, $3)",
- release.ID,
+ releaseID,
link.Name,
link.URL,
)
@@ -157,14 +237,19 @@ func UpdateReleaseLinks(db *sqlx.DB, release *model.Release, new_links []*model.
}
}
+ err = tx.Commit()
+ if err != nil {
+ return err
+ }
+
return nil
}
-func DeleteRelease(db *sqlx.DB, release *model.Release) error {
+func DeleteRelease(db *sqlx.DB, releaseID string) error {
_, err := db.Exec(
"DELETE FROM musicrelease "+
"WHERE id=$1",
- release.ID,
+ releaseID,
)
if err != nil {
return err
@@ -173,54 +258,8 @@ func DeleteRelease(db *sqlx.DB, release *model.Release) error {
return nil
}
-func GetFullRelease(db *sqlx.DB, releaseID string) (*model.FullRelease, error) {
- // get release
- release, err := GetRelease(db, releaseID)
- if err != nil {
- return nil, err
- }
-
- // get credits
- credits, err := GetReleaseCredits(db, releaseID)
- if err != nil {
- return nil, errors.New(fmt.Sprintf("Credits: %s", err))
- }
-
- // get artists
- for i, credit := range credits {
- artist, err := GetArtist(db, credit.Artist.ID)
- if err != nil {
- return nil, errors.New(fmt.Sprintf("FATAL: Failed to serve release %s: Artists: %s\n", release.ID, err))
- }
- credits[i].Artist = *artist
- }
-
- // get tracks
- dbTracks, err := GetReleaseTracks(db, releaseID)
- if err != nil {
- return nil, errors.New(fmt.Sprintf("Tracks: %s", err))
- }
- tracks := []model.DisplayTrack{}
- for i, track := range dbTracks {
- tracks = append(tracks, track.MakeDisplay(i + 1))
- }
-
- // get links
- links, err := GetReleaseLinks(db, releaseID)
- if err != nil {
- return nil, errors.New(fmt.Sprintf("Links: %s", err))
- }
-
- return &model.FullRelease{
- Release: release,
- Tracks: tracks,
- Credits: credits,
- Links: links,
- }, nil
-}
-
-func GetReleaseTracks(db *sqlx.DB, releaseID string) ([]model.Track, error) {
- var tracks = []model.Track{}
+func GetReleaseTracks(db *sqlx.DB, releaseID string) ([]*model.Track, error) {
+ var tracks = []*model.Track{}
err := db.Select(&tracks,
"SELECT musictrack.* FROM musictrack "+
@@ -236,40 +275,38 @@ func GetReleaseTracks(db *sqlx.DB, releaseID string) ([]model.Track, error) {
return tracks, nil
}
-func GetReleaseCredits(db *sqlx.DB, releaseID string) ([]model.Credit, error) {
- type DBCredit struct {
- Release string
- Artist string
- Role string
- Primary bool `db:"is_primary"`
- }
- var dbCredits []DBCredit
-
- err := db.Select(&dbCredits,
- "SELECT musiccredit.* FROM musiccredit "+
- "JOIN artist ON artist=id "+
- "WHERE release=$1",
+func GetReleaseCredits(db *sqlx.DB, releaseID string) ([]*model.Credit, error) {
+ rows, err := db.Query(
+ "SELECT artist.id,artist.name,artist.website,artist.avatar,role,is_primary "+
+ "FROM musiccredit "+
+ "JOIN artist ON artist=artist.id "+
+ "JOIN musicrelease ON release=musicrelease.id "+
+ "WHERE musicrelease.id=$1 "+
+ "ORDER BY is_primary DESC",
releaseID,
)
if err != nil {
return nil, err
}
- var credits []model.Credit
- for _, credit := range dbCredits {
- credits = append(credits, model.Credit{
- Release: model.Release{ ID: credit.Release },
- Artist: model.Artist{ ID: credit.Artist },
- Role: credit.Role,
- Primary: credit.Primary,
- })
+ var credits []*model.Credit
+ for rows.Next() {
+ credit := model.Credit{}
+ rows.Scan(
+ &credit.Artist.ID,
+ &credit.Artist.Name,
+ &credit.Artist.Website,
+ &credit.Artist.Avatar,
+ &credit.Role,
+ &credit.Primary)
+ credits = append(credits, &credit)
}
return credits, nil
}
-func GetReleaseLinks(db *sqlx.DB, releaseID string) ([]model.Link, error) {
- var links = []model.Link{}
+func GetReleaseLinks(db *sqlx.DB, releaseID string) ([]*model.Link, error) {
+ var links = []*model.Link{}
err := db.Select(&links, "SELECT name,url FROM musiclink WHERE release=$1", releaseID)
if err != nil {
diff --git a/music/controller/track.go b/music/controller/track.go
index a070bff..3187553 100644
--- a/music/controller/track.go
+++ b/music/controller/track.go
@@ -1,7 +1,7 @@
package music
import (
- "arimelody.me/arimelody.me/music/model"
+ "arimelody-web/music/model"
"github.com/jmoiron/sqlx"
)
@@ -55,11 +55,12 @@ func GetTracksNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Track, error
return tracks, nil
}
-func GetTrackReleases(db *sqlx.DB, trackID string) ([]model.ReleaseShorthand, error) {
- var releases = []model.ReleaseShorthand{}
+func GetTrackReleases(db *sqlx.DB, trackID string, full bool) ([]*model.Release, error) {
+ var releases = []*model.Release{}
err := db.Select(&releases,
- "SELECT id,title,type,release_date,artwork,buylink FROM musicrelease "+
+ "SELECT id,title,type,release_date,artwork,buylink "+
+ "FROM musicrelease "+
"JOIN musicreleasetrack ON release=id "+
"WHERE track=$1 "+
"ORDER BY release_date",
@@ -69,6 +70,43 @@ func GetTrackReleases(db *sqlx.DB, trackID string) ([]model.ReleaseShorthand, er
return nil, err
}
+ type NamePrimary struct {
+ Name string `json:"name"`
+ Primary bool `json:"primary" db:"is_primary"`
+ }
+ for _, release := range releases {
+ // get artists
+ credits := []NamePrimary{}
+ err := db.Select(&credits,
+ "SELECT name,is_primary FROM artist "+
+ "JOIN musiccredit ON artist=artist.id "+
+ "JOIN musicrelease ON release=musicrelease.id "+
+ "WHERE musicrelease.id=$1", release.ID)
+ if err != nil {
+ return nil, err
+ }
+ for _, credit := range credits {
+ release.Credits = append(release.Credits, &model.Credit{
+ Artist: model.Artist{
+ Name: credit.Name,
+ },
+ Primary: credit.Primary,
+ })
+ }
+
+ // get tracks
+ tracks := []string{}
+ err = db.Select(&tracks, "SELECT track FROM musicreleasetrack WHERE release=$1", release.ID)
+ if err != nil {
+ return nil, err
+ }
+ for _, trackID := range tracks {
+ release.Tracks = append(release.Tracks, &model.Track{
+ ID: trackID,
+ })
+ }
+ }
+
return releases, nil
}
diff --git a/music/model/artist.go b/music/model/artist.go
index 837aee8..733e537 100644
--- a/music/model/artist.go
+++ b/music/model/artist.go
@@ -1,7 +1,5 @@
package model
-import "strings"
-
type (
Artist struct {
ID string `json:"id"`
@@ -21,56 +19,3 @@ func (artist Artist) GetAvatar() string {
}
return artist.Avatar
}
-
-func (release FullRelease) GetUniqueArtists(only_primary bool) []*Artist {
- var artists = []*Artist{}
-
- for _, credit := range release.Credits {
- if only_primary && !credit.Primary {
- continue
- }
-
- exists := false
- for _, a := range artists {
- if a.ID == credit.Artist.ID {
- exists = true
- break
- }
- }
-
- if exists {
- continue
- }
-
- artists = append(artists, &credit.Artist)
- }
-
- return artists
-}
-
-func (release FullRelease) GetUniqueArtistNames(only_primary bool) []string {
- var names = []string{}
- for _, artist := range release.GetUniqueArtists(only_primary) {
- names = append(names, artist.Name)
- }
-
- return names
-}
-
-func (release FullRelease) PrintArtists(only_primary bool, ampersand bool) string {
- names := release.GetUniqueArtistNames(only_primary)
-
- if len(names) == 0 {
- return "Unknown Artist"
- } else if len(names) == 1 {
- return names[0]
- }
-
- if ampersand {
- res := strings.Join(names[:len(names)-1], ", ")
- res += " & " + names[len(names)-1]
- return res
- } else {
- return strings.Join(names[:], ", ")
- }
-}
diff --git a/music/model/credit.go b/music/model/credit.go
index 9b62b54..2080d83 100644
--- a/music/model/credit.go
+++ b/music/model/credit.go
@@ -1,8 +1,10 @@
package model
-type Credit struct {
- Release Release `json:"release"`
- Artist Artist `json:"artist"`
- Role string `json:"role"`
- Primary bool `json:"primary" db:"is_primary"`
-}
+type (
+ Credit struct {
+ Release Release `json:"release"`
+ Artist Artist `json:"artist"`
+ Role string `json:"role"`
+ Primary bool `json:"primary" db:"is_primary"`
+ }
+)
diff --git a/music/model/release.go b/music/model/release.go
index d9840a4..7b0c8d5 100644
--- a/music/model/release.go
+++ b/music/model/release.go
@@ -1,6 +1,8 @@
package model
import (
+ "html/template"
+ "strings"
"time"
)
@@ -19,22 +21,9 @@ type (
Buylink string `json:"buylink"`
Copyright string `json:"copyright" db:"copyright"`
CopyrightURL string `json:"copyrightURL" db:"copyrighturl"`
- }
-
- FullRelease struct {
- *Release
- Tracks []DisplayTrack `json:"tracks"`
- Credits []Credit `json:"credits"`
- Links []Link `json:"links"`
- }
-
- ReleaseShorthand struct {
- ID string `json:"id"`
- Title string `json:"title"`
- ReleaseType ReleaseType `json:"type" db:"type"`
- ReleaseDate time.Time `json:"releaseDate" db:"release_date"`
- Artwork string `json:"artwork"`
- Buylink string `json:"buylink"`
+ Tracks []*Track `json:"tracks"`
+ Credits []*Credit `json:"credits"`
+ Links []*Link `json:"links"`
}
)
@@ -48,6 +37,10 @@ const (
// GETTERS
+func (release Release) GetDescriptionHTML() template.HTML {
+ return template.HTML(strings.Replace(release.Description, "\n", "
", -1))
+}
+
func (release Release) TextReleaseDate() string {
return release.ReleaseDate.Format("2006-01-02T15:04")
}
@@ -67,10 +60,39 @@ func (release Release) GetArtwork() string {
return release.Artwork
}
-func (release FullRelease) IsSingle() bool {
+func (release Release) IsSingle() bool {
return len(release.Tracks) == 1;
}
func (release Release) IsReleased() bool {
return release.ReleaseDate.Before(time.Now())
}
+
+func (release Release) GetUniqueArtistNames(only_primary bool) []string {
+ names := []string{}
+
+ for _, credit := range release.Credits {
+ if only_primary && !credit.Primary { continue }
+ names = append(names, credit.Artist.Name)
+ }
+
+ return names
+}
+
+func (release Release) PrintArtists(only_primary bool, ampersand bool) string {
+ names := release.GetUniqueArtistNames(only_primary)
+
+ if len(names) == 0 {
+ return "Unknown Artist"
+ } else if len(names) == 1 {
+ return names[0]
+ }
+
+ if ampersand {
+ res := strings.Join(names[:len(names)-1], ", ")
+ res += " & " + names[len(names)-1]
+ return res
+ } else {
+ return strings.Join(names[:], ", ")
+ }
+}
diff --git a/music/model/track.go b/music/model/track.go
index 47b2c46..d44c224 100644
--- a/music/model/track.go
+++ b/music/model/track.go
@@ -7,24 +7,23 @@ import (
type (
Track struct {
- ID string `json:"id"`
- Title string `json:"title"`
- Description string `json:"description"`
- Lyrics string `json:"lyrics" db:"lyrics"`
- PreviewURL string `json:"previewURL" db:"preview_url"`
- }
-
- DisplayTrack struct {
- *Track
- Lyrics template.HTML `json:"lyrics"`
- Number int `json:"-"`
+ ID string `json:"id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ Lyrics string `json:"lyrics" db:"lyrics"`
+ PreviewURL string `json:"previewURL" db:"preview_url"`
}
)
-func (track Track) MakeDisplay(number int) DisplayTrack {
- return DisplayTrack{
- Track: &track,
- Lyrics: template.HTML(strings.Replace(track.Lyrics, "\n", "
", -1)),
- Number: number,
- }
+func (track Track) GetDescriptionHTML() template.HTML {
+ return template.HTML(strings.Replace(track.Description, "\n", "
", -1))
+}
+
+func (track Track) GetLyricsHTML() template.HTML {
+ return template.HTML(strings.Replace(track.Lyrics, "\n", "
", -1))
+}
+
+// this function is stupid and i hate that i need it
+func (track Track) Add(a int, b int) int {
+ return a + b
}
diff --git a/music/view/music.go b/music/view/music.go
index 041680e..6297a42 100644
--- a/music/view/music.go
+++ b/music/view/music.go
@@ -4,10 +4,10 @@ import (
"fmt"
"net/http"
- "arimelody.me/arimelody.me/global"
- music "arimelody.me/arimelody.me/music/controller"
- "arimelody.me/arimelody.me/music/model"
- "arimelody.me/arimelody.me/templates"
+ "arimelody-web/global"
+ music "arimelody-web/music/controller"
+ "arimelody-web/music/model"
+ "arimelody-web/templates"
)
// HTTP HANDLER METHODS
@@ -21,8 +21,7 @@ func Handler() http.Handler {
return
}
- var release model.Release
- err := global.DB.Get(&release, "SELECT * FROM musicrelease WHERE id=$1", r.URL.Path[1:])
+ release, err := music.GetRelease(global.DB, r.URL.Path[1:], true)
if err != nil {
http.NotFound(w, r)
return
@@ -36,25 +35,17 @@ func Handler() http.Handler {
func ServeCatalog() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- dbReleases, err := music.GetAllReleases(global.DB)
+ releases, err := music.GetAllReleases(global.DB, true, 0, true)
if err != nil {
fmt.Printf("FATAL: Failed to pull releases for catalog: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
- releases := []*model.FullRelease{}
- for _, dbRelease := range dbReleases {
- if !dbRelease.Visible { continue }
- if !dbRelease.IsReleased() {
- dbRelease.ReleaseType = model.Upcoming
+
+ for _, release := range releases {
+ if !release.IsReleased() {
+ release.ReleaseType = model.Upcoming
}
- release, err := music.GetFullRelease(global.DB, dbRelease.ID)
- if err != nil {
- fmt.Printf("FATAL: Failed to pull full release for %s: %s\n", dbRelease.ID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
- releases = append(releases, release)
}
err = templates.Pages["music"].Execute(w, releases)
diff --git a/music/view/release.go b/music/view/release.go
index 306c97e..fcb2b29 100644
--- a/music/view/release.go
+++ b/music/view/release.go
@@ -5,11 +5,11 @@ import (
"fmt"
"net/http"
- "arimelody.me/arimelody.me/admin"
- "arimelody.me/arimelody.me/global"
- "arimelody.me/arimelody.me/music/model"
- db "arimelody.me/arimelody.me/music/controller"
- "arimelody.me/arimelody.me/templates"
+ "arimelody-web/admin"
+ "arimelody-web/global"
+ "arimelody-web/music/model"
+ db "arimelody-web/music/controller"
+ "arimelody-web/templates"
)
type (
@@ -26,14 +26,14 @@ type (
}
Release struct {
- model.Release
+ *model.Release
Tracks []Track `json:"tracks"`
Credits []Credit `json:"credits"`
Links map[string]string `json:"links"`
}
)
-func ServeRelease(release model.Release) http.Handler {
+func ServeRelease(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// only allow authorised users to view hidden releases
authorised := admin.GetSession(r) != nil
@@ -108,7 +108,7 @@ func ServeRelease(release model.Release) http.Handler {
})
}
-func ServeGateway(release model.Release) http.Handler {
+func ServeGateway(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// only allow authorised users to view hidden releases
authorised := admin.GetSession(r) != nil
@@ -117,21 +117,15 @@ func ServeGateway(release model.Release) http.Handler {
return
}
- fullRelease := &model.FullRelease{
- Release: &release,
- }
+ response := *release
if authorised || release.IsReleased() {
- fullerRelease, err := db.GetFullRelease(global.DB, release.ID)
- if err != nil {
- fmt.Printf("FATAL: Failed to pull full release data for %s: %s\n", release.ID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
- fullRelease = fullerRelease
+ response.Tracks = release.Tracks
+ response.Credits = release.Credits
+ response.Links = release.Links
}
- err := templates.Pages["music-gateway"].Execute(w, fullRelease)
+ err := templates.Pages["music-gateway"].Execute(w, response)
if err != nil {
fmt.Printf("Error rendering music gateway for %s: %s\n", release.ID, err)
diff --git a/public/style/music-gateway.css b/public/style/music-gateway.css
index 68ad65c..9eff4e4 100644
--- a/public/style/music-gateway.css
+++ b/public/style/music-gateway.css
@@ -296,6 +296,13 @@ div#info p {
display: inline-block;
}
+#upcoming-release {
+ width: fit-content;
+ padding: .3em 1em;
+ font-size: 1em;
+ background: #101010;
+}
+
ul#links {
width: 100%;
margin: 1rem 0;
@@ -357,7 +364,12 @@ ul#links a:hover {
}
#description {
- font-size: 1.2em;
+ font-size: 1.1em;
+}
+
+#copyright {
+ margin-bottom: 0;
+ font-size: .8em;
}
#share {
diff --git a/views/music-gateway.html b/views/music-gateway.html
index 93a6950..1bf3a2f 100644
--- a/views/music-gateway.html
+++ b/views/music-gateway.html
@@ -61,7 +61,7 @@
{{.ReleaseType}}
{{else}}
upcoming
-
Releases: {{.PrintReleaseDate}}
+
Releases: {{.PrintReleaseDate}}
{{end}}
{{if .IsReleased}}
@@ -79,7 +79,7 @@
{{if .Description}}
-
{{.Description}}
+
{{.GetDescriptionHTML}}
{{else if .IsSingle}}
@@ -90,6 +90,10 @@
{{end}}
+ {{if and .Copyright .CopyrightURL}}
+
{{.Title}} © {{.GetReleaseYear}} by {{.PrintArtists true true}} is licensed under {{.Copyright}}
+ {{end}}
+