diff --git a/admin/admin.go b/admin/admin.go
new file mode 100644
index 0000000..4a34d7f
--- /dev/null
+++ b/admin/admin.go
@@ -0,0 +1,47 @@
+package admin
+
+import (
+ "fmt"
+ "math/rand"
+ "os"
+ "time"
+)
+
+type (
+ Session struct {
+ UserID string
+ Token string
+ Expires int64
+ }
+)
+
+const TOKEN_LENGTH = 64
+const TOKEN_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+var ADMIN_ID_DISCORD = func() string {
+ envvar := os.Getenv("DISCORD_ADMIN_ID")
+ if envvar == "" {
+ fmt.Printf("DISCORD_ADMIN_ID was not provided. Admin login will be unavailable.\n")
+ }
+ return envvar
+}()
+
+var sessions []*Session
+
+func createSession(UserID string) Session {
+ return Session{
+ UserID: UserID,
+ Token: string(generateToken()),
+ Expires: time.Now().Add(24 * time.Hour).Unix(),
+ }
+}
+
+func generateToken() string {
+ var token []byte
+
+ for i := 0; i < TOKEN_LENGTH; i++ {
+ token = append(token, TOKEN_CHARS[rand.Intn(len(TOKEN_CHARS))])
+ }
+
+ return string(token)
+}
diff --git a/api/v1/admin/admin.go b/admin/http.go
similarity index 72%
rename from api/v1/admin/admin.go
rename to admin/http.go
index a24722e..ff6d48a 100644
--- a/api/v1/admin/admin.go
+++ b/admin/http.go
@@ -3,45 +3,17 @@ package admin
import (
"context"
"fmt"
- "math/rand"
+ "html/template"
"net/http"
"os"
- // "strings"
+ "path/filepath"
+ "strings"
"time"
"arimelody.me/arimelody.me/discord"
"arimelody.me/arimelody.me/global"
)
-type (
- Session struct {
- UserID string
- Token string
- Expires int64
- }
-)
-
-const TOKEN_LENGTH = 64
-const TOKEN_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
-
-var ADMIN_ID_DISCORD = func() string {
- envvar := os.Getenv("DISCORD_ADMIN_ID")
- if envvar == "" {
- fmt.Printf("DISCORD_ADMIN_ID was not provided. Admin login will be unavailable.\n")
- }
- return envvar
-}()
-
-var sessions []*Session
-
-func CreateSession(UserID string) Session {
- return Session{
- UserID: UserID,
- Token: string(generateToken()),
- Expires: time.Now().Add(24 * time.Hour).Unix(),
- }
-}
-
func Handler() http.Handler {
mux := http.NewServeMux()
@@ -53,18 +25,13 @@ func Handler() http.Handler {
mux.Handle("/login", global.HTTPLog(LoginHandler()))
mux.Handle("/verify", global.HTTPLog(MustAuthorise(VerifyHandler())))
mux.Handle("/logout", global.HTTPLog(MustAuthorise(LogoutHandler())))
+ mux.Handle("/static", global.HTTPLog(MustAuthorise(staticHandler())))
return mux
}
func MustAuthorise(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // TEMPORARY
- ctx := context.WithValue(r.Context(), "role", "admin")
- next.ServeHTTP(w, r.WithContext(ctx))
- return
-
- /*
auth := r.Header.Get("Authorization")
if strings.HasPrefix(auth, "Bearer ") {
auth = auth[7:]
@@ -104,7 +71,6 @@ func MustAuthorise(next http.Handler) http.Handler {
ctx := context.WithValue(r.Context(), "role", "admin")
next.ServeHTTP(w, r.WithContext(ctx))
- */
})
}
@@ -143,7 +109,7 @@ func LoginHandler() http.Handler {
}
// login success!
- session := CreateSession(discord_user.Username)
+ session := createSession(discord_user.Username)
sessions = append(sessions, &session)
cookie := http.Cookie{}
@@ -197,12 +163,60 @@ func VerifyHandler() http.Handler {
})
}
-func generateToken() string {
- var token []byte
+func ServeTemplate(page string, data any) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ lp_layout := filepath.Join("views", "layout.html")
+ lp_header := filepath.Join("views", "header.html")
+ lp_footer := filepath.Join("views", "footer.html")
+ lp_prideflag := filepath.Join("views", "prideflag.html")
+ fp := filepath.Join("views", filepath.Clean(page))
- for i := 0; i < TOKEN_LENGTH; i++ {
- token = append(token, TOKEN_CHARS[rand.Intn(len(TOKEN_CHARS))])
- }
+ info, err := os.Stat(fp)
+ if err != nil {
+ if os.IsNotExist(err) {
+ http.NotFound(w, r)
+ return
+ }
+ }
- return string(token)
+ if info.IsDir() {
+ http.NotFound(w, r)
+ return
+ }
+
+ template, err := template.ParseFiles(lp_layout, lp_header, lp_footer, lp_prideflag, fp)
+ if err != nil {
+ fmt.Printf("Error parsing template files: %s\n", err)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+
+ err = template.ExecuteTemplate(w, "layout.html", data)
+ if err != nil {
+ fmt.Printf("Error executing template: %s\n", err)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ })
+}
+
+func staticHandler() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ info, err := os.Stat(filepath.Join("admin", "static", filepath.Clean(r.URL.Path)))
+ // does the file exist?
+ if err != nil {
+ if os.IsNotExist(err) {
+ http.NotFound(w, r)
+ return
+ }
+ }
+
+ // is thjs a directory? (forbidden)
+ if info.IsDir() {
+ http.NotFound(w, r)
+ return
+ }
+
+ http.FileServer(http.Dir(filepath.Join("admin", "static"))).ServeHTTP(w, r)
+ })
}
diff --git a/colour/colour.go b/colour/colour.go
new file mode 100644
index 0000000..dfbe303
--- /dev/null
+++ b/colour/colour.go
@@ -0,0 +1,11 @@
+package colour
+
+var Reset = "\033[0m"
+var Red = "\033[31m"
+var Green = "\033[32m"
+var Yellow = "\033[33m"
+var Blue = "\033[34m"
+var Purple = "\033[35m"
+var Cyan = "\033[36m"
+var Gray = "\033[37m"
+var White = "\033[97m"
diff --git a/discord/discord.go b/discord/discord.go
index 147b270..c6a1d7a 100644
--- a/discord/discord.go
+++ b/discord/discord.go
@@ -1,24 +1,45 @@
package discord
import (
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "net/url"
- "strings"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
)
const API_ENDPOINT = "https://discord.com/api/v10"
-const CLIENT_ID = "1268013769578119208"
-// TODO: good GOD change this later please i beg you. we've already broken
-// the rules by doing this at all
-const CLIENT_SECRET = "JUEZnixhN7BxmLIHmbECiKETMP85VT0E"
-const REDIRECT_URI = "https://discord.com/oauth2/authorize?client_id=1268013769578119208&response_type=code&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Fapi%2Fv1%2Fadmin%2Flogin&scope=identify"
-
-// TODO: change before prod
-const MY_REDIRECT_URI = "http://127.0.0.1:8080/api/v1/admin/login"
+var CLIENT_ID = func() string {
+ envvar := os.Getenv("DISCORD_CLIENT_ID")
+ if envvar == "" {
+ fmt.Printf("DISCORD_CLIENT_ID was not provided. Admin login will be unavailable.\n")
+ }
+ return envvar
+}()
+var CLIENT_SECRET = func() string {
+ envvar := os.Getenv("DISCORD_CLIENT_SECRET")
+ if envvar == "" {
+ fmt.Printf("DISCORD_CLIENT_SECRET was not provided. Admin login will be unavailable.\n")
+ }
+ return envvar
+}()
+var REDIRECT_URI = func() string {
+ envvar := os.Getenv("DISCORD_REDIRECT_URI")
+ if envvar == "" {
+ fmt.Printf("DISCORD_REDIRECT_URI was not provided. Admin login will be unavailable.\n")
+ }
+ return envvar
+}()
+var OAUTH_CALLBACK_URI = func() string {
+ envvar := os.Getenv("OAUTH_CALLBACK_URI")
+ if envvar == "" {
+ fmt.Printf("OAUTH_CALLBACK_URI was not provided. Admin login will be unavailable.\n")
+ }
+ return envvar
+}()
type (
AccessTokenResponse struct {
@@ -63,7 +84,7 @@ func GetOAuthTokenFromCode(code string) (string, error) {
"client_secret": {CLIENT_SECRET},
"grant_type": {"authorization_code"},
"code": {code},
- "redirect_uri": {MY_REDIRECT_URI},
+ "redirect_uri": {OAUTH_CALLBACK_URI},
}.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
diff --git a/global/global.go b/global/global.go
index a92f51b..f796d03 100644
--- a/global/global.go
+++ b/global/global.go
@@ -1,26 +1,16 @@
package global
import (
- "fmt"
- "net/http"
- "os"
- "path/filepath"
- "html/template"
- "strconv"
- "time"
-)
+ "fmt"
+ "net/http"
+ "os"
+ "path/filepath"
+ "html/template"
+ "strconv"
+ "time"
-var MimeTypes = map[string]string{
- "css": "text/css; charset=utf-8",
- "png": "image/png",
- "jpg": "image/jpg",
- "webp": "image/webp",
- "html": "text/html",
- "asc": "text/plain",
- "pub": "text/plain",
- "txt": "text/plain",
- "js": "application/javascript",
-}
+ "arimelody.me/arimelody.me/colour"
+)
func DefaultHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -54,11 +44,20 @@ func HTTPLog(next http.Handler) http.Handler {
elapsed = strconv.Itoa(difference)
}
- fmt.Printf("[%s] %s %s - %d (%sms) (%s)\n",
+ codeColour := colour.Reset
+
+ if lrw.Code - 600 <= 0 { codeColour = colour.Red }
+ if lrw.Code - 500 <= 0 { codeColour = colour.Yellow }
+ if lrw.Code - 400 <= 0 { codeColour = colour.White }
+ if lrw.Code - 300 <= 0 { codeColour = colour.Green }
+
+ fmt.Printf("[%s] %s %s - %s%d%s (%sms) (%s)\n",
after.Format(time.UnixDate),
r.Method,
r.URL.Path,
+ codeColour,
lrw.Code,
+ colour.Reset,
elapsed,
r.Header["User-Agent"][0])
})
diff --git a/main.go b/main.go
index dc609f6..9270dcb 100644
--- a/main.go
+++ b/main.go
@@ -7,8 +7,8 @@ import (
"os"
"path/filepath"
- "arimelody.me/arimelody.me/api/v1/admin"
- "arimelody.me/arimelody.me/api/v1/music"
+ "arimelody.me/arimelody.me/admin"
+ "arimelody.me/arimelody.me/music"
"arimelody.me/arimelody.me/db"
"arimelody.me/arimelody.me/global"
)
@@ -44,7 +44,7 @@ func main() {
func createServeMux() *http.ServeMux {
mux := http.NewServeMux()
- mux.Handle("/api/v1/admin/", global.HTTPLog(http.StripPrefix("/api/v1/admin", admin.Handler())))
+ mux.Handle("/admin/", global.HTTPLog(http.StripPrefix("/admin", admin.Handler())))
mux.Handle("/api/v1/music/artist/", global.HTTPLog(http.StripPrefix("/api/v1/music/artist", music.ServeArtist())))
mux.Handle("/api/v1/music/", global.HTTPLog(http.StripPrefix("/api/v1/music", music.ServeRelease())))
diff --git a/api/v1/music/artist.go b/music/artist.go
similarity index 100%
rename from api/v1/music/artist.go
rename to music/artist.go
diff --git a/api/v1/music/credit.go b/music/credit.go
similarity index 84%
rename from api/v1/music/credit.go
rename to music/credit.go
index 57c1ed7..232b5af 100644
--- a/api/v1/music/credit.go
+++ b/music/credit.go
@@ -6,11 +6,19 @@ import (
"github.com/jmoiron/sqlx"
)
-type Credit struct {
- Artist *Artist `json:"artist"`
- Role string `json:"role"`
- Primary bool `json:"primary"`
-}
+type (
+ Credit struct {
+ Artist *Artist `json:"artist"`
+ Role string `json:"role"`
+ Primary bool `json:"primary"`
+ }
+
+ PostCreditBody struct {
+ Artist string `json:"artist"`
+ Role string `json:"role"`
+ Primary bool `json:"primary"`
+ }
+)
// GETTERS
diff --git a/api/v1/music/link.go b/music/link.go
similarity index 100%
rename from api/v1/music/link.go
rename to music/link.go
diff --git a/api/v1/music/music.go b/music/music.go
similarity index 88%
rename from api/v1/music/music.go
rename to music/music.go
index ef49d99..5ff6833 100644
--- a/api/v1/music/music.go
+++ b/music/music.go
@@ -7,19 +7,10 @@ import (
"path/filepath"
"strings"
- "arimelody.me/arimelody.me/api/v1/admin"
+ "arimelody.me/arimelody.me/admin"
"arimelody.me/arimelody.me/global"
)
-// func make_date_work(date string) time.Time {
-// res, err := time.Parse("2-Jan-2006", date)
-// if err != nil {
-// fmt.Printf("somehow we failed to parse %s! falling back to epoch :]\n", date)
-// return time.Unix(0, 0)
-// }
-// return res
-// }
-
// HTTP HANDLER METHODS
func ServeCatalog() http.Handler {
@@ -33,7 +24,7 @@ func ServeCatalog() http.Handler {
releases = append(releases, release)
}
- global.ServeTemplate("music.html", releases).ServeHTTP(w, r)
+ global.ServeTemplate("music.html", Releases).ServeHTTP(w, r)
})
}
@@ -96,7 +87,6 @@ func ServeArtwork() http.Handler {
}
fp := filepath.Join("data", "music-artwork", releaseID + ".png")
- fmt.Println(fp)
info, err := os.Stat(fp)
if err != nil {
if os.IsNotExist(err) {
diff --git a/api/v1/music/release.go b/music/release.go
similarity index 80%
rename from api/v1/music/release.go
rename to music/release.go
index 34620b9..ed409f7 100644
--- a/api/v1/music/release.go
+++ b/music/release.go
@@ -8,7 +8,7 @@ import (
"strings"
"time"
- "arimelody.me/arimelody.me/api/v1/admin"
+ "arimelody.me/arimelody.me/admin"
"github.com/jmoiron/sqlx"
)
@@ -21,19 +21,35 @@ const (
Compilation ReleaseType = "Compilation"
)
-type Release struct {
- ID string `json:"id"`
- Title string `json:"title"`
- Description string `json:"description"`
- ReleaseType ReleaseType `json:"type"`
- ReleaseDate time.Time `json:"releaseDate"`
- Artwork string `json:"artwork"`
- Buyname string `json:"buyname"`
- Buylink string `json:"buylink"`
- Links []Link `json:"links"`
- Credits []Credit `json:"credits"`
- Tracks []Track `json:"tracks"`
-}
+type (
+ Release struct {
+ ID string `json:"id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ ReleaseType ReleaseType `json:"type"`
+ ReleaseDate time.Time `json:"releaseDate"`
+ Artwork string `json:"artwork"`
+ Buyname string `json:"buyname"`
+ Buylink string `json:"buylink"`
+ Links []Link `json:"links"`
+ Credits []Credit `json:"credits"`
+ Tracks []Track `json:"tracks"`
+ }
+
+ PostReleaseBody struct {
+ ID string `json:"id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ ReleaseType ReleaseType `json:"type"`
+ ReleaseDate time.Time `json:"releaseDate"`
+ Artwork string `json:"artwork"`
+ Buyname string `json:"buyname"`
+ Buylink string `json:"buylink"`
+ Links []Link `json:"links"`
+ Credits []PostCreditBody `json:"credits"`
+ Tracks []Track `json:"tracks"`
+ }
+)
var Releases []Release;
@@ -337,7 +353,7 @@ func (release Release) DeleteFromDB(db *sqlx.DB) error {
func PullAllReleases(db *sqlx.DB) ([]Release, error) {
releases := []Release{}
- rows, err := db.Query("SELECT id, title, description, type, release_date, artwork, buyname, buylink FROM musicreleases")
+ rows, err := db.Query("SELECT id, title, description, type, release_date, artwork, buyname, buylink FROM musicreleases ORDER BY release_date DESC")
if err != nil {
return nil, err
}
@@ -423,19 +439,50 @@ func PostRelease() http.Handler {
return
}
- var release Release
- err := json.NewDecoder(r.Body).Decode(&release)
+ var data PostReleaseBody
+ err := json.NewDecoder(r.Body).Decode(&data)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
- if GetRelease(release.ID) != nil {
- http.Error(w, fmt.Sprintf("Release %s already exists", release.ID), http.StatusBadRequest)
+ if GetRelease(data.ID) != nil {
+ http.Error(w, fmt.Sprintf("Release %s already exists", data.ID), http.StatusBadRequest)
return
}
- Releases = append(Releases, release)
+ var credits = []Credit{}
+
+ for _, credit := range data.Credits {
+ var artist = GetArtist(credit.Artist)
+
+ if artist == nil {
+ http.Error(w, fmt.Sprintf("Artist %s does not exist", credit.Artist), http.StatusBadRequest)
+ return
+ }
+
+ credits = append(credits, Credit{
+ Artist: artist,
+ Role: credit.Role,
+ Primary: credit.Primary,
+ })
+ }
+
+ var release = Release{
+ ID: data.ID,
+ Title: data.Title,
+ Description: data.Description,
+ ReleaseType: data.ReleaseType,
+ ReleaseDate: data.ReleaseDate,
+ Artwork: data.Artwork,
+ Buyname: data.Buyname,
+ Buylink: data.Buylink,
+ Links: data.Links,
+ Credits: credits,
+ Tracks: data.Tracks,
+ }
+
+ Releases = append([]Release{release}, Releases...)
jsonBytes, err := json.Marshal(release)
w.Header().Add("Content-Type", "application/json")
diff --git a/api/v1/music/track.go b/music/track.go
similarity index 100%
rename from api/v1/music/track.go
rename to music/track.go
diff --git a/public/script/header.js b/public/script/header.js
index bda0487..f45db5c 100644
--- a/public/script/header.js
+++ b/public/script/header.js
@@ -2,11 +2,11 @@ const header_links = document.getElementById("header-links");
const hamburger = document.getElementById("header-links-toggle");
document.addEventListener("click", event => {
- if (!header_links.contains(event.target) && !hamburger.contains(event.target) && !header_links.href) {
- header_links.classList.remove("open");
- }
+ if (!header_links.contains(event.target) && !hamburger.contains(event.target) && !header_links.href) {
+ header_links.classList.remove("open");
+ }
});
-hamburger.addEventListener("click", event => {
- header_links.classList.toggle("open");
+hamburger.addEventListener("click", () => {
+ header_links.classList.toggle("open");
});
diff --git a/public/script/music.js b/public/script/music.js
index afbcf49..273ce2b 100644
--- a/public/script/music.js
+++ b/public/script/music.js
@@ -1,7 +1,12 @@
import "./main.js";
-document.addEventListener("swap", () => {
- document.querySelectorAll("h1.music-title").forEach(element => {
- element.href = "";
- });
+document.querySelectorAll("div.music").forEach(container => {
+ const link = container.querySelector(".music-title a").href
+
+ container.addEventListener("click", event => {
+ if (event.target.href) return;
+
+ event.preventDefault();
+ location = link;
+ });
});
diff --git a/schema.sql b/schema.sql
index 7aa5ef7..e0ada43 100644
--- a/schema.sql
+++ b/schema.sql
@@ -1,17 +1,18 @@
--
-- Artists (should be applicable to all art)
--
-CREATE TABLE IF NOT EXISTS artists (
- id text NOT NULL,
- name text,
- website text
+CREATE TABLE artist (
+ id uuid DEFAULT gen_random_uuid(),
+ name text NOT NULL,
+ website text,
+ avatar text
);
-ALTER TABLE artists ADD CONSTRAINT artists_pk PRIMARY KEY (id);
+ALTER TABLE artist ADD CONSTRAINT artist_pk PRIMARY KEY (id);
--
-- Music releases
--
-CREATE TABLE IF NOT EXISTS musicreleases (
+CREATE TABLE musicrelease (
id character varying(64) NOT NULL,
title text NOT NULL,
description text,
@@ -21,50 +22,56 @@ CREATE TABLE IF NOT EXISTS musicreleases (
buyname text,
buylink text
);
-ALTER TABLE musicreleases ADD CONSTRAINT musicreleases_pk PRIMARY KEY (id);
+ALTER TABLE musicrelease ADD CONSTRAINT musicrelease_pk PRIMARY KEY (id);
--
-- Music links (external platform links under a release)
--
-CREATE TABLE IF NOT EXISTS musiclinks (
+CREATE TABLE musiclink (
release character varying(64) NOT NULL,
name text NOT NULL,
- url text
+ url text NOT NULL
);
-ALTER TABLE musiclinks ADD CONSTRAINT musiclinks_pk PRIMARY KEY (release, name);
+ALTER TABLE musiclink ADD CONSTRAINT musiclink_pk PRIMARY KEY (release, name);
--
-- Music credits (artist credits under a release)
--
-CREATE TABLE IF NOT EXISTS musiccredits (
+CREATE TABLE musiccredit (
release character varying(64) NOT NULL,
- artist text NOT NULL,
- role text,
- is_primary boolean
+ artist uuid NOT NULL,
+ role text NOT NULL,
+ is_primary boolean DEFAULT false
);
-ALTER TABLE musiccredits ADD CONSTRAINT musiccredits_pk PRIMARY KEY (release, artist);
+ALTER TABLE musiccredit ADD CONSTRAINT musiccredit_pk PRIMARY KEY (release, artist);
--
-- Music tracks (tracks under a release)
--
-CREATE TABLE IF NOT EXISTS musictracks (
- release character varying(64) NOT NULL,
- number integer NOT NULL,
+CREATE TABLE musictrack (
+ id uuid DEFAULT gen_random_uuid(),
title text NOT NULL,
description text,
lyrics text,
preview_url text
);
-ALTER TABLE musictracks ADD CONSTRAINT musictracks_pk PRIMARY KEY (release, number);
+ALTER TABLE musictrack ADD CONSTRAINT musictrack_pk PRIMARY KEY (id);
+
+--
+-- Music release/track pairs
+--
+CREATE TABLE musicreleasetrack (
+ release character varying(64) NOT NULL,
+ track uuid NOT NULL,
+ number integer NOT NULL
+);
+ALTER TABLE musicreleasetrack ADD CONSTRAINT musictrack_pk PRIMARY KEY (release, track);
--
-- Foreign keys
--
-
-ALTER TABLE musiccredits ADD CONSTRAINT IF NOT EXISTS musiccredits_artist_fk FOREIGN KEY (artist) REFERENCES artists(id) ON DELETE CASCADE ON UPDATE CASCADE;
-
-ALTER TABLE musiccredits ADD CONSTRAINT IF NOT EXISTS musiccredits_release_fk FOREIGN KEY (release) REFERENCES musicreleases(id) ON DELETE CASCADE;
-
-ALTER TABLE musiclinks ADD CONSTRAINT IF NOT EXISTS musiclinks_release_fk FOREIGN KEY (release) REFERENCES musicreleases(id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE musictracks ADD CONSTRAINT IF NOT EXISTS musictracks_release_fk FOREIGN KEY (release) REFERENCES musicreleases(id) ON DELETE CASCADE;
+ALTER TABLE musiccredit ADD CONSTRAINT musiccredit_artist_fk FOREIGN KEY (artist) REFERENCES artist(id) ON DELETE CASCADE ON UPDATE CASCADE;
+ALTER TABLE musiccredit ADD CONSTRAINT musiccredit_release_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON DELETE CASCADE;
+ALTER TABLE musiclink ADD CONSTRAINT musiclink_release_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON UPDATE CASCADE ON DELETE CASCADE;
+ALTER TABLE musicreleasetrack ADD CONSTRAINT music_pair_trackref_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON DELETE CASCADE;
+ALTER TABLE musicreleasetrack ADD CONSTRAINT music_pair_releaseref_fk FOREIGN KEY (track) REFERENCES musictrack(id) ON DELETE CASCADE;
diff --git a/views/music-gateway.html b/views/music-gateway.html
index 1cc9b7a..531b6d3 100644
--- a/views/music-gateway.html
+++ b/views/music-gateway.html
@@ -111,7 +111,7 @@
tracks:
{{range $i, $track := .GetTracks}}
- {{$i}}. {{$track.GetTitle}}
+ {{$track.GetNumber}}. {{$track.GetTitle}}
{{$track.GetLyrics}}
{{end}}
diff --git a/views/music.html b/views/music.html
index f523a09..ee65de8 100644
--- a/views/music.html
+++ b/views/music.html
@@ -18,76 +18,80 @@
{{define "content"}}
-
+
-
- # my music
-
+
+ # my music
+
-
- {{range $Release := .}}
-
-
-
-
-
-
- {{end}}
+
+ {{range $Release := .}}
+
+
+
+
+
+
+
{{$Release.PrintArtists true true}}
+
{{$Release.GetType}}
+
+
+ {{end}}
+
-
-
-
- yes! well, in most cases...
-
-
- from Dream (2022) onward, all of my self-released songs are
- licensed under Creative Commons Attribution-ShareAlike 4.0.
- anyone may use and remix these songs freely, so long as they provide credit back to me and link back to this license!
- please note that all derivative works must inherit this license.
-
-
- a great example of some credit text would be as follows:
-
-
- music used: mellodoot - Dream
- https://arimelody.me/music/dream
- licensed under CC BY-SA 4.0.
-
-
- for any songs prior to this, they were all either released by me (in which case, i honestly
- don't mind), or in collaboration with chill people who i don't see having an issue with it.
- do be sure to ask them about it, though!
-
-
- in the event the song you want to use is released under some other label, their usage rights
- will more than likely trump whatever i'd otherwise have in mind. i'll try to negotiate some
- nice terms, though! ;3
-
-
- i love the idea of other creators using my songs in their work, so if you do happen to use
- my stuff in a work you're particularly proud of, feel free to send it my way!
-
-
- > ari@arimelody.me
-
-
+
+
+
+ yes! well, in most cases...
+
+
+ from Dream (2022) onward, all of my self-released songs are
+ licensed under Creative Commons Attribution-ShareAlike 4.0.
+ anyone may use and remix these songs freely, so long as they provide credit back to me and link back to this license!
+ please note that all derivative works must inherit this license.
+
+
+ a great example of some credit text would be as follows:
+
+
+ music used: mellodoot - Dream
+ https://arimelody.me/music/dream
+ licensed under CC BY-SA 4.0.
+
+
+ for any songs prior to this, they were all either released by me (in which case, i honestly
+ don't mind), or in collaboration with chill people who i don't see having an issue with it.
+ do be sure to ask them about it, though!
+
+
+ in the event the song you want to use is released under some other label, their usage rights
+ will more than likely trump whatever i'd otherwise have in mind. i'll try to negotiate some
+ nice terms, though! ;3
+
+
+ i love the idea of other creators using my songs in their work, so if you do happen to use
+ my stuff in a work you're particularly proud of, feel free to send it my way!
+
+
+ > ari@arimelody.me
+
+
-
back to top
+
back to top
{{end}}