From cba791deba86b10266cdc73234c3930aec08ad35 Mon Sep 17 00:00:00 2001 From: ari melody Date: Fri, 2 Aug 2024 00:53:19 +0100 Subject: [PATCH] MORE REFACTORING!! + some improvements Signed-off-by: ari melody --- admin/admin.go | 47 +++++++++ api/v1/admin/admin.go => admin/http.go | 104 +++++++++++-------- colour/colour.go | 11 ++ discord/discord.go | 51 +++++++--- global/global.go | 39 ++++--- main.go | 6 +- {api/v1/music => music}/artist.go | 0 {api/v1/music => music}/credit.go | 18 +++- {api/v1/music => music}/link.go | 0 {api/v1/music => music}/music.go | 14 +-- {api/v1/music => music}/release.go | 87 ++++++++++++---- {api/v1/music => music}/track.go | 0 public/script/header.js | 10 +- public/script/music.js | 13 ++- schema.sql | 61 ++++++----- views/music-gateway.html | 2 +- views/music.html | 136 +++++++++++++------------ 17 files changed, 376 insertions(+), 223 deletions(-) create mode 100644 admin/admin.go rename api/v1/admin/admin.go => admin/http.go (72%) create mode 100644 colour/colour.go rename {api/v1/music => music}/artist.go (100%) rename {api/v1/music => music}/credit.go (84%) rename {api/v1/music => music}/link.go (100%) rename {api/v1/music => music}/music.go (88%) rename {api/v1/music => music}/release.go (80%) rename {api/v1/music => music}/track.go (100%) 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 := .}} -
-
- {{$Release.GetTitle}} artwork -
-
-

{{$Release.GetTitle}}

-

{{$Release.PrintArtists true true}}

-

{{$Release.GetType}}

- -
-
- {{end}} +
+ {{range $Release := .}} +
+
+ {{$Release.GetTitle}} artwork +
+
+

+ + {{$Release.GetTitle}} + +

+

{{$Release.PrintArtists true true}}

+

{{$Release.GetType}}

+ +
+ {{end}} +
-

- - > "can i use your music in my content?" - -

-
-

- 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 -

-
+

+ + > "can i use your music in my content?" + +

+
+

+ 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}}