home to your local SPACEGIRL 💫 arimelody.space
1
fork

Configure Feed

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

refactoring everything teehee (i'm so glad this isn't a team project)

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

+963 -540
+1 -1
.air.toml
··· 14 14 follow_symlink = false 15 15 full_bin = "" 16 16 include_dir = [".", "views", "api"] 17 - include_ext = ["go", "tpl", "tmpl", "html"] 17 + include_ext = ["go", "tpl", "tmpl"] 18 18 include_file = [] 19 19 kill_delay = "0s" 20 20 log = "build-errors.log"
-12
api/api.go
··· 1 - package api 2 - 3 - import ( 4 - "net/http" 5 - "html/template" 6 - ) 7 - 8 - func Handle(writer http.ResponseWriter, req *http.Request, root *template.Template) int { 9 - writer.WriteHeader(501) 10 - writer.Write([]byte("501 Not Implemented")) 11 - return 501; 12 - }
+30 -28
api/v1/admin/admin.go
··· 24 24 const TOKEN_LENGTH = 64 25 25 const TOKEN_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 26 26 27 - // TODO: consider relying *entirely* on env vars instead of hard-coded fallbacks 28 27 var ADMIN_ID_DISCORD = func() string { 29 28 envvar := os.Getenv("DISCORD_ADMIN_ID") 30 - if envvar != "" { 31 - return envvar 32 - } else { 33 - return "356210742200107009" 29 + if envvar == "" { 30 + fmt.Printf("DISCORD_ADMIN_ID was not provided. Admin login will be unavailable.\n") 34 31 } 32 + return envvar 35 33 }() 36 34 37 35 var sessions []*Session ··· 48 46 mux := http.NewServeMux() 49 47 50 48 mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 51 - w.WriteHeader(200) 49 + w.WriteHeader(http.StatusOK) 52 50 w.Write([]byte("hello /admin!")) 53 51 })) 54 52 mux.Handle("/callback", global.HTTPLog(OAuthCallbackHandler())) 55 53 mux.Handle("/login", global.HTTPLog(LoginHandler())) 56 - mux.Handle("/verify", global.HTTPLog(AuthorisedHandler(VerifyHandler()))) 57 - mux.Handle("/logout", global.HTTPLog(AuthorisedHandler(LogoutHandler()))) 54 + mux.Handle("/verify", global.HTTPLog(MustAuthorise(VerifyHandler()))) 55 + mux.Handle("/logout", global.HTTPLog(MustAuthorise(LogoutHandler()))) 58 56 59 57 return mux 60 58 } 61 59 62 - func AuthorisedHandler(next http.Handler) http.Handler { 60 + func MustAuthorise(next http.Handler) http.Handler { 63 61 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 64 62 auth := r.Header.Get("Authorization") 65 - if auth == "" || !strings.HasPrefix(auth, "Bearer ") { 63 + if strings.HasPrefix(auth, "Bearer ") { 64 + auth = auth[7:] 65 + } else { 66 66 cookie, err := r.Cookie("token") 67 67 if err != nil { 68 - w.WriteHeader(401) 69 - w.Write([]byte("Unauthorized")) 68 + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 70 69 return 71 70 } 72 71 auth = cookie.Value 73 72 } 74 - auth = auth[7:] 75 73 76 74 var session *Session 77 75 for _, s := range sessions { ··· 86 84 } 87 85 continue 88 86 } 87 + 89 88 if s.Token == auth { 90 89 session = s 91 90 break ··· 93 92 } 94 93 95 94 if session == nil { 96 - w.WriteHeader(401) 97 - w.Write([]byte("Unauthorized")) 95 + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 98 96 return 99 97 } 100 98 101 99 ctx := context.WithValue(r.Context(), "token", session.Token) 100 + ctx = context.WithValue(ctx, "role", "admin") 102 101 next.ServeHTTP(w, r.WithContext(ctx)) 103 102 }) 104 103 } 105 104 106 105 func LoginHandler() http.Handler { 107 106 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 107 + if ADMIN_ID_DISCORD == "" { 108 + http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) 109 + return 110 + } 111 + 108 112 code := r.URL.Query().Get("code") 109 113 110 114 if code == "" { 111 - w.Header().Add("Location", discord.REDIRECT_URI) 112 - w.WriteHeader(307) 115 + http.Redirect(w, r, discord.REDIRECT_URI, http.StatusTemporaryRedirect) 113 116 return 114 117 } 115 118 116 119 auth_token, err := discord.GetOAuthTokenFromCode(code) 117 120 if err != nil { 118 121 fmt.Printf("Failed to retrieve discord access token: %s\n", err) 119 - w.WriteHeader(500) 120 - w.Write([]byte("Internal server error")) 122 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 121 123 return 122 124 } 123 125 124 126 discord_user, err := discord.GetDiscordUserFromAuth(auth_token) 125 127 if err != nil { 126 128 fmt.Printf("Failed to retrieve discord user information: %s\n", err) 127 - w.WriteHeader(500) 128 - w.Write([]byte("Internal server error")) 129 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 129 130 return 130 131 } 131 132 132 133 if discord_user.Id != ADMIN_ID_DISCORD { 133 - // TODO: unauthorized user. revoke the token 134 - w.WriteHeader(401) 135 - w.Write([]byte("Unauthorized")) 134 + // TODO: unauthorized user; revoke the token 135 + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 136 136 return 137 137 } 138 138 ··· 149 149 cookie.Path = "/" 150 150 http.SetCookie(w, &cookie) 151 151 152 - w.WriteHeader(200) 152 + w.WriteHeader(http.StatusOK) 153 153 w.Write([]byte(session.Token)) 154 154 }) 155 155 } ··· 159 159 token := r.Context().Value("token").(string) 160 160 161 161 if token == "" { 162 - w.WriteHeader(401) 162 + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 163 163 return 164 164 } 165 165 ··· 172 172 return new_sessions 173 173 }(token) 174 174 175 - w.WriteHeader(200) 175 + w.WriteHeader(http.StatusOK) 176 + w.Write([]byte("OK")) 176 177 }) 177 178 } 178 179 ··· 185 186 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 186 187 // this is an authorised endpoint, so you *must* supply a valid token 187 188 // before accessing this route. 188 - w.WriteHeader(200) 189 + w.WriteHeader(http.StatusOK) 190 + w.Write([]byte("OK")) 189 191 }) 190 192 } 191 193
+95
api/v1/music/artist.go
··· 1 + package music 2 + 3 + import ( 4 + "fmt" 5 + 6 + "github.com/jmoiron/sqlx" 7 + ) 8 + 9 + type Artist struct { 10 + id string 11 + name string 12 + website string 13 + } 14 + 15 + var Artists []Artist 16 + 17 + func GetArtist(id string) *Artist { 18 + for _, artist := range Artists { 19 + if artist.GetID() == id { 20 + return &artist 21 + } 22 + } 23 + return nil 24 + } 25 + 26 + // GETTERS 27 + 28 + func (artist Artist) GetID() string { 29 + return artist.id 30 + } 31 + 32 + func (artist Artist) GetName() string { 33 + return artist.name 34 + } 35 + 36 + func (artist Artist) GetWebsite() string { 37 + return artist.website 38 + } 39 + 40 + // SETTERS 41 + 42 + func (artist Artist) SetID(id string) error { 43 + artist.id = id 44 + return nil 45 + } 46 + 47 + func (artist Artist) SetName(name string) error { 48 + artist.name = name 49 + return nil 50 + } 51 + 52 + func (artist Artist) SetWebsite(website string) error { 53 + artist.website = website 54 + return nil 55 + } 56 + 57 + // DATABASE 58 + 59 + func (artist Artist) PushToDB(db *sqlx.DB) { 60 + fmt.Printf("Pushing artist [%s] to database...", artist.name) 61 + 62 + db.MustExec("INSERT INTO artists (id, name, website) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name=$2, website=$3", 63 + artist.id, 64 + artist.name, 65 + artist.website, 66 + ) 67 + 68 + fmt.Printf("done!\n") 69 + } 70 + 71 + func PullAllArtists(db *sqlx.DB) ([]Artist, error) { 72 + artists := []Artist{} 73 + 74 + rows, err := db.Query("SELECT id, name, website FROM artists") 75 + if err != nil { 76 + return nil, err 77 + } 78 + 79 + for rows.Next() { 80 + var artist = Artist{} 81 + 82 + err = rows.Scan( 83 + &artist.id, 84 + &artist.name, 85 + &artist.website, 86 + ) 87 + if err != nil { 88 + return nil, err 89 + } 90 + 91 + artists = append(artists, artist) 92 + } 93 + 94 + return artists, nil 95 + }
+84
api/v1/music/credit.go
··· 1 + package music 2 + 3 + import ( 4 + "fmt" 5 + 6 + "github.com/jmoiron/sqlx" 7 + ) 8 + 9 + type Credit struct { 10 + artist *Artist 11 + role string 12 + primary bool 13 + } 14 + 15 + // GETTERS 16 + 17 + func (credit Credit) GetArtist() Artist { 18 + return *credit.artist 19 + } 20 + 21 + func (credit Credit) GetRole() string { 22 + return credit.role 23 + } 24 + 25 + func (credit Credit) IsPrimary() bool { 26 + return credit.primary 27 + } 28 + 29 + // SETTERS 30 + 31 + func (credit Credit) SetArtist(artist *Artist) error { 32 + // TODO: update DB 33 + credit.artist = artist 34 + return nil 35 + } 36 + 37 + func (credit Credit) SetRole(role string) error { 38 + // TODO: update DB 39 + credit.role = role 40 + return nil 41 + } 42 + 43 + func (credit Credit) SetPrimary(primary bool) error { 44 + // TODO: update DB 45 + credit.primary = primary 46 + return nil 47 + } 48 + 49 + // DATABASE 50 + 51 + func PullReleaseCredits(db *sqlx.DB, releaseID string) ([]Credit, error) { 52 + var credits = []Credit{} 53 + 54 + credit_rows, err := db.Query("SELECT artist, role, is_primary FROM musiccredits WHERE release=$1", releaseID) 55 + if err != nil { 56 + return []Credit{}, err 57 + } 58 + 59 + for credit_rows.Next() { 60 + var artistID string 61 + var credit = Credit{} 62 + err = credit_rows.Scan( 63 + &artistID, 64 + &credit.role, 65 + &credit.primary, 66 + ) 67 + if err != nil { 68 + fmt.Printf("Error while pulling credit for release %s: %s\n", releaseID, err) 69 + continue 70 + } 71 + 72 + credit.artist = GetArtist(artistID) 73 + if credit.artist == nil { 74 + // this should absolutely not happen ever due to foreign key 75 + // constraints, but it doesn't hurt to be sure! 76 + fmt.Printf("Error while pulling credit for release %s: Artist %s does not exist\n", releaseID, artistID) 77 + continue 78 + } 79 + 80 + credits = append(credits, credit) 81 + } 82 + 83 + return credits, nil 84 + }
+73
api/v1/music/link.go
··· 1 + package music 2 + 3 + import ( 4 + "fmt" 5 + "regexp" 6 + "strings" 7 + 8 + "github.com/jmoiron/sqlx" 9 + ) 10 + 11 + type Link struct { 12 + name string 13 + url string 14 + } 15 + 16 + // GETTERS 17 + 18 + func (link Link) GetName() string { 19 + return link.name 20 + } 21 + 22 + func (link Link) GetURL() string { 23 + return link.url 24 + } 25 + 26 + // SETTERS 27 + 28 + func (link Link) SetName(name string) error { 29 + // TODO: update DB 30 + link.name = name 31 + return nil 32 + } 33 + 34 + func (link Link) SetURL(url string) error { 35 + // TODO: update DB 36 + link.url = url 37 + return nil 38 + } 39 + 40 + // MISC 41 + 42 + func (link Link) NormaliseName() string { 43 + rgx := regexp.MustCompile(`[^a-z0-9]`) 44 + return strings.ToLower(rgx.ReplaceAllString(link.name, "")) 45 + } 46 + 47 + // DATABASE 48 + 49 + func PullReleaseLinks(db *sqlx.DB, releaseID string) ([]Link, error) { 50 + var links = []Link{} 51 + 52 + link_rows, err := db.Query("SELECT name, url FROM musiclinks WHERE release=$1", releaseID); 53 + if err != nil { 54 + return []Link{}, err 55 + } 56 + 57 + for link_rows.Next() { 58 + var link = Link{} 59 + 60 + err = link_rows.Scan( 61 + &link.name, 62 + &link.url, 63 + ) 64 + if err != nil { 65 + fmt.Printf("Error while pulling link for release %s: %s\n", releaseID, err) 66 + continue 67 + } 68 + 69 + links = append(links, link) 70 + } 71 + 72 + return links, nil 73 + }
+50 -25
api/v1/music/music.go
··· 1 1 package music 2 2 3 3 import ( 4 - "errors" 5 - "fmt" 6 - "time" 4 + "fmt" 5 + "net/http" 6 + 7 + "arimelody.me/arimelody.me/api/v1/admin" 8 + "arimelody.me/arimelody.me/global" 7 9 ) 8 10 9 - var Releases []*MusicRelease; 10 - var Artists []*Artist; 11 + // func make_date_work(date string) time.Time { 12 + // res, err := time.Parse("2-Jan-2006", date) 13 + // if err != nil { 14 + // fmt.Printf("somehow we failed to parse %s! falling back to epoch :]\n", date) 15 + // return time.Unix(0, 0) 16 + // } 17 + // return res 18 + // } 11 19 12 - func make_date_work(date string) time.Time { 13 - res, err := time.Parse("2-Jan-2006", date) 14 - if err != nil { 15 - fmt.Printf("somehow we failed to parse %s! falling back to epoch :]\n", date) 16 - return time.Unix(0, 0) 17 - } 18 - return res 20 + // HTTP HANDLER METHODS 21 + 22 + func ServeCatalog() http.Handler { 23 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 24 + releases := []Release{} 25 + authorised := r.Context().Value("role") != nil && r.Context().Value("role") == "admin" 26 + for _, release := range Releases { 27 + if !release.IsReleased() && !authorised { 28 + continue 29 + } 30 + releases = append(releases, release) 31 + } 32 + 33 + global.ServeTemplate("music.html", releases).ServeHTTP(w, r) 34 + }) 19 35 } 20 36 21 - func GetRelease(id string) (*MusicRelease, error) { 22 - for _, release := range Releases { 23 - if release.Id == id { 24 - return release, nil 37 + func ServeGateway() http.Handler { 38 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 39 + id := r.URL.Path[1:] 40 + release := GetRelease(id) 41 + if release == nil { 42 + http.NotFound(w, r) 43 + return 44 + } 45 + 46 + // only allow authorised users to view unreleased releases 47 + authorised := r.Context().Value("role") != nil && r.Context().Value("role") == "admin" 48 + if !release.IsReleased() && !authorised { 49 + admin.MustAuthorise(ServeGateway()).ServeHTTP(w, r) 50 + return 25 51 } 26 - } 27 - return nil, errors.New(fmt.Sprintf("Release %s not found", id)) 28 - } 29 52 30 - func GetArtist(id string) (*Artist, error) { 31 - for _, artist := range Artists { 32 - if artist.Id == id { 33 - return artist, nil 53 + lrw := global.LoggingResponseWriter{w, http.StatusOK} 54 + 55 + global.ServeTemplate("music-gateway.html", release).ServeHTTP(&lrw, r) 56 + 57 + if lrw.Code != http.StatusOK { 58 + fmt.Printf("Error loading music gateway for %s\n", id) 59 + return 34 60 } 35 - } 36 - return nil, errors.New(fmt.Sprintf("Artist %s not found", id)) 61 + }) 37 62 }
-133
api/v1/music/music_types.go
··· 1 - package music 2 - 3 - import ( 4 - "regexp" 5 - "strings" 6 - "time" 7 - ) 8 - 9 - type ( 10 - Artist struct { 11 - Id string 12 - Name string 13 - Website string 14 - } 15 - 16 - MusicRelease struct { 17 - Id string 18 - Title string 19 - Type string 20 - ReleaseDate time.Time 21 - Artwork string 22 - Buyname string 23 - Buylink string 24 - Links []MusicLink 25 - Description string 26 - Credits []MusicCredit 27 - Tracks []MusicTrack 28 - } 29 - 30 - MusicLink struct { 31 - Name string 32 - Url string 33 - } 34 - 35 - MusicCredit struct { 36 - Artist *Artist 37 - Role string 38 - Primary bool 39 - } 40 - 41 - MusicTrack struct { 42 - Number int 43 - Title string 44 - Description string 45 - Lyrics string 46 - PreviewUrl string 47 - } 48 - ) 49 - 50 - func (release MusicRelease) GetUniqueArtists(include_non_primary bool) []*Artist { 51 - res := []*Artist{} 52 - for _, credit := range release.Credits { 53 - if !include_non_primary && !credit.Primary { 54 - continue 55 - } 56 - 57 - exists := false 58 - for _, a := range res { 59 - if a == credit.Artist { 60 - exists = true 61 - break 62 - } 63 - } 64 - if exists { 65 - continue 66 - } 67 - 68 - res = append(res, credit.Artist) 69 - } 70 - 71 - return res 72 - } 73 - 74 - func (release MusicRelease) GetUniqueArtistNames(include_non_primary bool) []string { 75 - artists := release.GetUniqueArtists(include_non_primary) 76 - names := []string{} 77 - for _, artist := range artists { 78 - names = append(names, artist.Name) 79 - } 80 - 81 - return names 82 - } 83 - 84 - func (release MusicRelease) PrintPrimaryArtists(include_non_primary bool, ampersand bool) string { 85 - names := release.GetUniqueArtistNames(include_non_primary) 86 - if len(names) == 0 { 87 - return "Unknown Artist" 88 - } else if len(names) == 1 { 89 - return names[0] 90 - } 91 - if ampersand { 92 - res := strings.Join(names[:len(names)-1], ", ") 93 - res += " & " + names[len(names)-1] 94 - return res 95 - } else { 96 - return strings.Join(names[:], ", ") 97 - } 98 - } 99 - 100 - func (release MusicRelease) ResolveType() string { 101 - if release.Type != "" { 102 - return release.Type 103 - } 104 - return "unknown" 105 - } 106 - 107 - func (release MusicRelease) ResolveArtwork() string { 108 - if release.Artwork != "" { 109 - return release.Artwork 110 - } 111 - return "/img/music-artwork/default.png" 112 - } 113 - 114 - func (release MusicRelease) PrintReleaseDate() string { 115 - return release.ReleaseDate.Format("02 January 2006") 116 - } 117 - 118 - func (release MusicRelease) GetReleaseYear() int { 119 - return release.ReleaseDate.Year() 120 - } 121 - 122 - func (link MusicLink) NormaliseName() string { 123 - re := regexp.MustCompile(`[^a-z0-9]`) 124 - return strings.ToLower(re.ReplaceAllString(link.Name, "")) 125 - } 126 - 127 - func (release MusicRelease) IsSingle() bool { 128 - return len(release.Tracks) == 1; 129 - } 130 - 131 - func (credit MusicCredit) ResolveArtist() Artist { 132 - return *credit.Artist 133 - }
+368
api/v1/music/release.go
··· 1 + package music 2 + 3 + import ( 4 + "errors" 5 + "fmt" 6 + "strings" 7 + "time" 8 + 9 + "github.com/jmoiron/sqlx" 10 + ) 11 + 12 + type ReleaseType string 13 + 14 + const ( 15 + Single ReleaseType = "Single" 16 + Album ReleaseType = "Album" 17 + EP ReleaseType = "EP" 18 + Compilation ReleaseType = "Compilation" 19 + ) 20 + 21 + type Release struct { 22 + id string 23 + title string 24 + releaseType ReleaseType 25 + releaseDate time.Time 26 + artwork string 27 + buyname string 28 + buylink string 29 + links []Link 30 + description string 31 + credits []Credit 32 + tracks []Track 33 + } 34 + 35 + var Releases []Release; 36 + 37 + // GETTERS 38 + 39 + func (release Release) GetID() string { 40 + return release.id 41 + } 42 + 43 + func (release Release) GetTitle() string { 44 + return release.title 45 + } 46 + 47 + func (release Release) GetType() ReleaseType { 48 + return release.releaseType 49 + } 50 + 51 + func (release Release) GetReleaseDate() time.Time { 52 + return release.releaseDate 53 + } 54 + 55 + func (release Release) GetArtwork() string { 56 + if release.artwork == "" { 57 + return "/img/music-artwork/default.png" 58 + } 59 + return release.artwork 60 + } 61 + 62 + func (release Release) GetBuyName() string { 63 + return release.buyname 64 + } 65 + 66 + func (release Release) GetBuyLink() string { 67 + return release.buylink 68 + } 69 + 70 + func (release Release) GetLinks() []Link { 71 + return release.links 72 + } 73 + 74 + func (release Release) GetDescription() string { 75 + return release.description 76 + } 77 + 78 + func (release Release) GetCredits() []Credit { 79 + return release.credits 80 + } 81 + 82 + func (release Release) GetTracks() []Track { 83 + return release.tracks 84 + } 85 + 86 + // SETTERS 87 + 88 + func (release Release) SetID(id string) error { 89 + // TODO: update DB 90 + release.id = id 91 + return nil 92 + } 93 + 94 + func (release Release) SetTitle(title string) error { 95 + // TODO: update DB 96 + release.title = title 97 + return nil 98 + } 99 + 100 + func (release Release) SetType(releaseType ReleaseType) error { 101 + // TODO: update DB 102 + release.releaseType = releaseType 103 + return nil 104 + } 105 + 106 + func (release Release) SetReleaseDate(releaseDate time.Time) error { 107 + // TODO: update DB 108 + release.releaseDate = releaseDate 109 + return nil 110 + } 111 + 112 + func (release Release) SetArtwork(artwork string) error { 113 + // TODO: update DB 114 + release.artwork = artwork 115 + return nil 116 + } 117 + 118 + func (release Release) SetBuyName(buyname string) error { 119 + // TODO: update DB 120 + release.buyname = buyname 121 + return nil 122 + } 123 + 124 + func (release Release) SetBuyLink(buylink string) error { 125 + // TODO: update DB 126 + release.buylink = buylink 127 + return nil 128 + } 129 + 130 + func (release Release) SetLinks(links []Link) error { 131 + // TODO: update DB 132 + release.links = links 133 + return nil 134 + } 135 + 136 + func (release Release) SetDescription(description string) error { 137 + // TODO: update DB 138 + release.description = description 139 + return nil 140 + } 141 + 142 + func (release Release) SetCredits(credits []Credit) error { 143 + // TODO: update DB 144 + release.credits = credits 145 + return nil 146 + } 147 + 148 + func (release Release) SetTracks(tracks []Track) error { 149 + // TODO: update DB 150 + release.tracks = tracks 151 + return nil 152 + } 153 + 154 + // MISC 155 + 156 + func GetRelease(id string) *Release { 157 + for _, release := range Releases { 158 + if release.GetID() == id { 159 + return &release 160 + } 161 + } 162 + return nil 163 + } 164 + 165 + func (release Release) PrintReleaseDate() string { 166 + return release.releaseDate.Format("02 January 2006") 167 + } 168 + 169 + func (release Release) GetReleaseYear() int { 170 + return release.releaseDate.Year() 171 + } 172 + 173 + func (release Release) IsSingle() bool { 174 + return len(release.tracks) == 1; 175 + } 176 + 177 + func (release Release) IsReleased() bool { 178 + return release.releaseDate.Before(time.Now()) 179 + } 180 + 181 + func (release Release) GetUniqueArtists(only_primary bool) []Artist { 182 + var artists = []Artist{} 183 + 184 + for _, credit := range release.credits { 185 + if only_primary && !credit.primary { 186 + continue 187 + } 188 + 189 + exists := false 190 + for _, a := range artists { 191 + if a.id == credit.artist.id { 192 + exists = true 193 + break 194 + } 195 + } 196 + 197 + if exists { 198 + continue 199 + } 200 + 201 + artists = append(artists, *credit.artist) 202 + } 203 + 204 + return artists 205 + } 206 + 207 + func (release Release) GetUniqueArtistNames(only_primary bool) []string { 208 + var names = []string{} 209 + for _, artist := range release.GetUniqueArtists(only_primary) { 210 + names = append(names, artist.GetName()) 211 + } 212 + 213 + return names 214 + } 215 + 216 + func (release Release) PrintArtists(only_primary bool, ampersand bool) string { 217 + names := release.GetUniqueArtistNames(only_primary) 218 + 219 + if len(names) == 0 { 220 + return "Unknown Artist" 221 + } else if len(names) == 1 { 222 + return names[0] 223 + } 224 + 225 + if ampersand { 226 + res := strings.Join(names[:len(names)-1], ", ") 227 + res += " & " + names[len(names)-1] 228 + return res 229 + } else { 230 + return strings.Join(names[:], ", ") 231 + } 232 + } 233 + 234 + // DATABASE 235 + 236 + func (release Release) PushToDB(db *sqlx.DB) error { 237 + // fmt.Printf("Pushing release [%s] to database...", release.id) 238 + 239 + tx, err := db.Begin() 240 + if err != nil { 241 + return errors.New(fmt.Sprintf("Failed to initiate transaction: %s", err)) 242 + } 243 + 244 + _, err = tx.Exec("INSERT INTO musicreleases (id, title, type, release_date, artwork, buyname, buylink) VALUES ($1, $2, $3, $4, $5, $6, $7) "+ 245 + "ON CONFLICT (id) DO UPDATE SET title=$2, type=$3, release_date=$4, artwork=$5, buyname=$6, buylink=$7", 246 + release.id, release.title, release.releaseType, release.releaseDate.Format("2-Jan-2006"), release.artwork, release.buyname, release.buylink) 247 + 248 + for _, link := range release.links { 249 + _, err = tx.Exec( 250 + "INSERT INTO musiclinks (release, name, url) "+ 251 + "VALUES ($1, $2, $3) "+ 252 + "ON CONFLICT (release, name) "+ 253 + "DO UPDATE SET url=$3 ", 254 + release.id, 255 + link.name, 256 + link.url, 257 + ) 258 + if err != nil { 259 + return errors.New(fmt.Sprintf("Failed to add music link to transaction: %s", err)) 260 + } 261 + } 262 + for _, credit := range release.credits { 263 + _, err = tx.Exec( 264 + "INSERT INTO musiccredits (release, artist, role, is_primary) "+ 265 + "VALUES ($1, $2, $3, $4) "+ 266 + "ON CONFLICT (release, artist) "+ 267 + "DO UPDATE SET role=$3, is_primary=$4", 268 + release.id, 269 + credit.artist.id, 270 + credit.role, 271 + credit.primary, 272 + ) 273 + if err != nil { 274 + return errors.New(fmt.Sprintf("Failed to add music credit to transaction: %s", err)) 275 + } 276 + } 277 + for _, track := range release.tracks { 278 + _, err = tx.Exec( 279 + "INSERT INTO musictracks (release, number, title, description, lyrics, preview_url) "+ 280 + "VALUES ($1, $2, $3, $4, $5, $6) "+ 281 + "ON CONFLICT (release, number) "+ 282 + "DO UPDATE SET title=$3, description=$4, lyrics=$5, preview_url=$6", 283 + release.id, 284 + track.number, 285 + track.title, 286 + track.description, 287 + track.lyrics, 288 + track.previewURL, 289 + ) 290 + if err != nil { 291 + return errors.New(fmt.Sprintf("Failed to add music track to transaction: %s", err)) 292 + } 293 + } 294 + 295 + err = tx.Commit() 296 + if err != nil { 297 + return errors.New(fmt.Sprintf("Failed to commit transaction: %s", err)) 298 + } 299 + 300 + // fmt.Printf("done!\n") 301 + 302 + return nil 303 + } 304 + 305 + func (release Release) DeleteFromDB(db *sqlx.DB) error { 306 + // this probably doesn't need to be a transaction; 307 + // i just felt like making it one 308 + tx, err := db.Begin() 309 + if err != nil { 310 + return errors.New(fmt.Sprintf("Failed to initiate transaction: %s", err)) 311 + } 312 + 313 + _, err = tx.Exec("DELETE FROM musicreleases WHERE id=$1", release.id) 314 + 315 + err = tx.Commit() 316 + if err != nil { 317 + return errors.New(fmt.Sprintf("Failed to commit transaction: %s", err)) 318 + } 319 + 320 + return nil 321 + } 322 + 323 + func PullAllReleases(db *sqlx.DB) ([]Release, error) { 324 + releases := []Release{} 325 + 326 + rows, err := db.Query("SELECT id, title, description, type, release_date, artwork, buyname, buylink FROM musicreleases") 327 + if err != nil { 328 + return nil, err 329 + } 330 + 331 + for rows.Next() { 332 + var release = Release{} 333 + 334 + err = rows.Scan( 335 + &release.id, 336 + &release.title, 337 + &release.description, 338 + &release.releaseType, 339 + &release.releaseDate, 340 + &release.artwork, 341 + &release.buyname, 342 + &release.buylink, 343 + ) 344 + if err != nil { 345 + fmt.Printf("Error while pulling a release: %s\n", err) 346 + continue 347 + } 348 + 349 + release.credits, err = PullReleaseCredits(db, release.id) 350 + if err != nil { 351 + fmt.Printf("Failed to pull credits for %s: %v\n", release.id, err) 352 + } 353 + 354 + release.links, err = PullReleaseLinks(db, release.id) 355 + if err != nil { 356 + fmt.Printf("Failed to pull links for %s: %v\n", release.id, err) 357 + } 358 + 359 + release.tracks, err = PullReleaseTracks(db, release.id) 360 + if err != nil { 361 + return nil, errors.New(fmt.Sprintf("error pulling tracks for %s: %v\n", release.id, err)) 362 + } 363 + 364 + releases = append(releases, release) 365 + } 366 + 367 + return releases, nil 368 + }
+100
api/v1/music/track.go
··· 1 + package music 2 + 3 + import ( 4 + "fmt" 5 + 6 + "github.com/jmoiron/sqlx" 7 + ) 8 + 9 + type Track struct { 10 + number int 11 + title string 12 + description string 13 + lyrics string 14 + previewURL string 15 + } 16 + 17 + // GETTERS 18 + 19 + func (track Track) GetNumber() int { 20 + return track.number 21 + } 22 + 23 + func (track Track) GetTitle() string { 24 + return track.title 25 + } 26 + 27 + func (track Track) GetDescription() string { 28 + return track.description 29 + } 30 + 31 + func (track Track) GetLyrics() string { 32 + return track.lyrics 33 + } 34 + 35 + func (track Track) GetPreviewURL() string { 36 + return track.previewURL 37 + } 38 + 39 + // SETTERS 40 + 41 + func (track Track) SetNumber(number int) error { 42 + // TODO: update DB 43 + track.number = number 44 + return nil 45 + } 46 + 47 + func (track Track) SetTitle(title string) error { 48 + // TODO: update DB 49 + track.title = title 50 + return nil 51 + } 52 + 53 + func (track Track) SetDescription(description string) error { 54 + // TODO: update DB 55 + track.description = description 56 + return nil 57 + } 58 + 59 + func (track Track) SetLyrics(lyrics string) error { 60 + // TODO: update DB 61 + track.lyrics = lyrics 62 + return nil 63 + } 64 + 65 + func (track Track) SetPreviewURL(previewURL string) error { 66 + // TODO: update DB 67 + track.previewURL = previewURL 68 + return nil 69 + } 70 + 71 + // DATABASE 72 + 73 + func PullReleaseTracks(db *sqlx.DB, releaseID string) ([]Track, error) { 74 + var tracks = []Track{} 75 + 76 + track_rows, err := db.Query("SELECT number, title, description, lyrics, preview_url FROM musictracks WHERE release=$1", releaseID) 77 + if err != nil { 78 + return []Track{}, err 79 + } 80 + 81 + for track_rows.Next() { 82 + var track = Track{} 83 + 84 + err = track_rows.Scan( 85 + &track.number, 86 + &track.title, 87 + &track.description, 88 + &track.lyrics, 89 + &track.previewURL, 90 + ) 91 + if err != nil { 92 + fmt.Printf("Error while pulling track for release %s: %s\n", releaseID, err) 93 + continue 94 + } 95 + 96 + tracks = append(tracks, track) 97 + } 98 + 99 + return tracks, nil 100 + }
-204
db.go
··· 1 - package main 2 - 3 - import ( 4 - "arimelody.me/arimelody.me/api/v1/music" 5 - 6 - "fmt" 7 - "os" 8 - "time" 9 - 10 - "github.com/jmoiron/sqlx" 11 - _ "github.com/lib/pq" 12 - ) 13 - 14 - func PushArtist(db *sqlx.DB, artist music.Artist) { 15 - fmt.Printf("pushing artist [%s] to database...", artist.Name) 16 - 17 - db.MustExec("INSERT INTO artists (id, name, website) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name=$2, website=$3", 18 - &artist.Id, 19 - &artist.Name, 20 - &artist.Website, 21 - ) 22 - 23 - fmt.Printf("done!\n") 24 - } 25 - 26 - func PullAllArtists(db *sqlx.DB) ([]*music.Artist, error) { 27 - artists := []*music.Artist{} 28 - 29 - rows, err := db.Query("SELECT id, name, website FROM artists") 30 - if err != nil { 31 - return nil, err 32 - } 33 - 34 - for rows.Next() { 35 - var artist = music.Artist{} 36 - err = rows.Scan(&artist.Id, &artist.Name, &artist.Website) 37 - if err != nil { 38 - return nil, err 39 - } 40 - artists = append(artists, &artist) 41 - } 42 - 43 - return artists, nil 44 - } 45 - 46 - func PullArtist(db *sqlx.DB, artistID string) (music.Artist, error) { 47 - artist := music.Artist{} 48 - 49 - err := db.Get(&artist, "SELECT id, name, website FROM artists WHERE id=$1", artistID) 50 - if err != nil { 51 - return music.Artist{}, err 52 - } 53 - 54 - return artist, nil 55 - } 56 - 57 - func PushRelease(db *sqlx.DB, release music.MusicRelease) { 58 - fmt.Printf("pushing release [%s] to database...", release.Id) 59 - 60 - tx := db.MustBegin() 61 - tx.MustExec("INSERT INTO musicreleases (id, title, type, release_date, artwork, buyname, buylink) VALUES ($1, $2, $3, $4, $5, $6, $7) "+ 62 - "ON CONFLICT (id) DO UPDATE SET title=$2, type=$3, release_date=$4, artwork=$5, buyname=$6, buylink=$7", 63 - &release.Id, &release.Title, &release.Type, release.ReleaseDate.Format("2-Jan-2006"), &release.Artwork, &release.Buyname, &release.Buylink) 64 - 65 - for _, link := range release.Links { 66 - tx.MustExec("INSERT INTO musiclinks (release, name, url) VALUES ($1, $2, $3) ON CONFLICT (release, name) DO UPDATE SET url=$3", 67 - &release.Id, &link.Name, &link.Url) 68 - } 69 - for _, credit := range release.Credits { 70 - tx.MustExec("INSERT INTO musiccredits (release, artist, role, is_primary) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING", 71 - &release.Id, &credit.Artist.Id, &credit.Role, &credit.Primary) 72 - } 73 - for _, track := range release.Tracks { 74 - tx.MustExec("INSERT INTO musictracks (release, number, title, description, lyrics, preview_url) VALUES ($1, $2, $3, $4, $5, $6) "+ 75 - "ON CONFLICT (release, number) DO UPDATE SET title=$3, description=$4, lyrics=$5, preview_url=$6", 76 - &release.Id, &track.Number, &track.Title, &track.Description, &track.Lyrics, &track.PreviewUrl) 77 - } 78 - 79 - tx.Commit() 80 - 81 - fmt.Printf("done!\n") 82 - } 83 - 84 - func PullAllReleases(db *sqlx.DB) ([]*music.MusicRelease, error) { 85 - releases := []*music.MusicRelease{} 86 - 87 - rows, err := db.Query("SELECT id, title, type, release_date, artwork, buyname, buylink FROM musicreleases") 88 - if err != nil { 89 - return nil, err 90 - } 91 - 92 - for rows.Next() { 93 - var release = music.MusicRelease{} 94 - release.Credits = []music.MusicCredit{} 95 - release.Links = []music.MusicLink{} 96 - release.Tracks = []music.MusicTrack{} 97 - 98 - err = rows.Scan( 99 - &release.Id, 100 - &release.Title, 101 - &release.Type, 102 - &release.ReleaseDate, 103 - &release.Artwork, 104 - &release.Buyname, 105 - &release.Buylink) 106 - if err != nil { 107 - continue 108 - } 109 - 110 - // pull musiccredits for artist data 111 - credit_rows, err := db.Query("SELECT artist, role, is_primary FROM musiccredits WHERE release=$1", release.Id) 112 - if err != nil { 113 - fmt.Printf("error pulling credits for %s: %v\n", release.Id, err) 114 - continue 115 - } 116 - for credit_rows.Next() { 117 - var artistID string 118 - var credit = music.MusicCredit{} 119 - err = credit_rows.Scan( 120 - &artistID, 121 - &credit.Role, 122 - &credit.Primary) 123 - if err != nil { 124 - fmt.Printf("error pulling credit for %s: %v\n", release.Id, err) 125 - continue 126 - } 127 - artist, err := music.GetArtist(artistID) 128 - if err != nil { 129 - fmt.Printf("error pulling credit for %s: %v\n", release.Id, err) 130 - continue 131 - } 132 - credit.Artist = artist 133 - release.Credits = append(release.Credits, credit) 134 - } 135 - 136 - // pull musiclinks for link data 137 - link_rows, err := db.Query("SELECT name, url FROM musiclinks WHERE release=$1", release.Id); 138 - if err != nil { 139 - fmt.Printf("error pulling links for %s: %v\n", release.Id, err) 140 - continue 141 - } 142 - for link_rows.Next() { 143 - var link = music.MusicLink{} 144 - err = link_rows.Scan( 145 - &link.Name, 146 - &link.Url) 147 - if err != nil { 148 - fmt.Printf("error pulling link for %s: %v\n", release.Id, err) 149 - continue 150 - } 151 - release.Links = append(release.Links, link) 152 - } 153 - 154 - // pull musictracks for track data 155 - track_rows, err := db.Query("SELECT number, title, description, lyrics, preview_url FROM musictracks WHERE release=$1", release.Id); 156 - if err != nil { 157 - fmt.Printf("error pulling tracks for %s: %v\n", release.Id, err) 158 - continue 159 - } 160 - for track_rows.Next() { 161 - var track = music.MusicTrack{} 162 - err = track_rows.Scan( 163 - &track.Number, 164 - &track.Title, 165 - &track.Description, 166 - &track.Lyrics, 167 - &track.PreviewUrl) 168 - if err != nil { 169 - fmt.Printf("error pulling track for %s: %v\n", release.Id, err) 170 - continue 171 - } 172 - release.Tracks = append(release.Tracks, track) 173 - } 174 - 175 - releases = append(releases, &release) 176 - } 177 - 178 - return releases, nil 179 - } 180 - 181 - func PullRelease(db *sqlx.DB, releaseID string) (music.MusicRelease, error) { 182 - release := music.MusicRelease{} 183 - 184 - err := db.Get(&release, "SELECT id, title, type, release_date, artwork, buyname, buylink FROM musicreleases WHERE id=$1", releaseID) 185 - if err != nil { 186 - return music.MusicRelease{}, err 187 - } 188 - 189 - return release, nil 190 - } 191 - 192 - func InitDatabase() *sqlx.DB { 193 - db, err := sqlx.Connect("postgres", "user=arimelody dbname=arimelody password=fuckingpassword sslmode=disable") 194 - if err != nil { 195 - fmt.Fprintf(os.Stderr, "unable to create database connection pool: %v\n", err) 196 - os.Exit(1) 197 - } 198 - 199 - db.SetConnMaxLifetime(time.Minute * 3) 200 - db.SetMaxOpenConns(10) 201 - db.SetMaxIdleConns(10) 202 - 203 - return db 204 - }
+24
db/db.go
··· 1 + package db 2 + 3 + import ( 4 + "fmt" 5 + "os" 6 + "time" 7 + 8 + "github.com/jmoiron/sqlx" 9 + _ "github.com/lib/pq" 10 + ) 11 + 12 + func InitDatabase() *sqlx.DB { 13 + db, err := sqlx.Connect("postgres", "user=arimelody dbname=arimelody password=fuckingpassword sslmode=disable") 14 + if err != nil { 15 + fmt.Fprintf(os.Stderr, "unable to create database connection pool: %v\n", err) 16 + os.Exit(1) 17 + } 18 + 19 + db.SetConnMaxLifetime(time.Minute * 3) 20 + db.SetMaxOpenConns(10) 21 + db.SetMaxIdleConns(10) 22 + 23 + return db 24 + }
+45 -14
global/global.go
··· 3 3 import ( 4 4 "fmt" 5 5 "net/http" 6 + "os" 7 + "path/filepath" 8 + "html/template" 6 9 "strconv" 7 10 "time" 8 11 ) ··· 19 22 "js": "application/javascript", 20 23 } 21 24 22 - var LAST_MODIFIED = time.Now() 23 - 24 - func IsModified(req *http.Request, last_modified time.Time) bool { 25 - if len(req.Header["If-Modified-Since"]) == 0 || len(req.Header["If-Modified-Since"][0]) == 0 { 26 - return true 27 - } 28 - request_time, err := time.Parse(http.TimeFormat, req.Header["If-Modified-Since"][0]) 29 - if err != nil { 30 - return true 31 - } 32 - if request_time.Before(last_modified) { 33 - return true 34 - } 35 - return false 25 + func DefaultHeaders(next http.Handler) http.Handler { 26 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 27 + w.Header().Add("Server", "arimelody.me") 28 + w.Header().Add("Cache-Control", "max-age=2592000") 29 + }) 36 30 } 37 31 38 32 type LoggingResponseWriter struct { ··· 69 63 r.Header["User-Agent"][0]) 70 64 }) 71 65 } 66 + 67 + func ServeTemplate(page string, data any) http.Handler { 68 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 69 + lp_layout := filepath.Join("views", "layout.html") 70 + lp_header := filepath.Join("views", "header.html") 71 + lp_footer := filepath.Join("views", "footer.html") 72 + lp_prideflag := filepath.Join("views", "prideflag.html") 73 + fp := filepath.Join("views", filepath.Clean(page)) 74 + 75 + info, err := os.Stat(fp) 76 + if err != nil { 77 + if os.IsNotExist(err) { 78 + http.NotFound(w, r) 79 + return 80 + } 81 + } 82 + 83 + if info.IsDir() { 84 + http.NotFound(w, r) 85 + return 86 + } 87 + 88 + template, err := template.ParseFiles(lp_layout, lp_header, lp_footer, lp_prideflag, fp) 89 + if err != nil { 90 + fmt.Printf("Error parsing template files: %s\n", err) 91 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 92 + return 93 + } 94 + 95 + err = template.ExecuteTemplate(w, "layout.html", data) 96 + if err != nil { 97 + fmt.Printf("Error executing template: %s\n", err) 98 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 99 + return 100 + } 101 + }) 102 + }
+20 -68
main.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 - "html/template" 6 5 "log" 7 6 "net/http" 8 7 "os" ··· 10 9 11 10 "arimelody.me/arimelody.me/api/v1/admin" 12 11 "arimelody.me/arimelody.me/api/v1/music" 12 + "arimelody.me/arimelody.me/db" 13 13 "arimelody.me/arimelody.me/global" 14 14 ) 15 15 16 16 const DEFAULT_PORT int = 8080 17 17 18 18 func main() { 19 - db := InitDatabase() 19 + db := db.InitDatabase() 20 20 defer db.Close() 21 21 22 22 var err error 23 - music.Artists, err = PullAllArtists(db) 23 + music.Artists, err = music.PullAllArtists(db) 24 24 if err != nil { 25 25 fmt.Printf("Failed to pull artists from database: %v\n", err); 26 26 panic(1) 27 27 } 28 - music.Releases, err = PullAllReleases(db) 28 + fmt.Printf("%d artists loaded successfully.\n", len(music.Artists)) 29 + 30 + music.Releases, err = music.PullAllReleases(db) 29 31 if err != nil { 30 32 fmt.Printf("Failed to pull releases from database: %v\n", err); 31 33 panic(1) 32 34 } 35 + fmt.Printf("%d releases loaded successfully.\n", len(music.Releases)) 33 36 37 + mux := createServeMux() 38 + 39 + port := DEFAULT_PORT 40 + fmt.Printf("now serving at http://127.0.0.1:%d\n", port) 41 + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), mux)) 42 + } 43 + 44 + func createServeMux() *http.ServeMux { 34 45 mux := http.NewServeMux() 35 46 36 47 mux.Handle("/api/v1/admin/", global.HTTPLog(http.StripPrefix("/api/v1/admin", admin.Handler()))) 37 48 38 - mux.Handle("/music/", global.HTTPLog(http.StripPrefix("/music", musicGatewayHandler()))) 39 - mux.Handle("/music", global.HTTPLog(serveTemplate("music.html", music.Releases))) 49 + mux.Handle("/music/", global.HTTPLog(http.StripPrefix("/music", music.ServeGateway()))) 50 + mux.Handle("/music", global.HTTPLog(music.ServeCatalog())) 40 51 41 52 mux.Handle("/", global.HTTPLog(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 42 53 if r.URL.Path == "/" || r.URL.Path == "/index.html" { 43 - serveTemplate("index.html", nil).ServeHTTP(w, r) 54 + global.ServeTemplate("index.html", nil).ServeHTTP(w, r) 44 55 return 45 56 } 46 57 staticHandler().ServeHTTP(w, r) 47 58 }))) 48 - 49 - port := DEFAULT_PORT 50 - fmt.Printf("now serving at http://127.0.0.1:%d\n", port) 51 - log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), mux)) 59 + 60 + return mux 52 61 } 53 62 54 63 func staticHandler() http.Handler { ··· 71 80 http.FileServer(http.Dir("./public")).ServeHTTP(w, r) 72 81 }) 73 82 } 74 - 75 - func musicGatewayHandler() http.Handler { 76 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 77 - id := r.URL.Path[1:] 78 - release, err := music.GetRelease(id) 79 - if err != nil { 80 - http.NotFound(w, r) 81 - return 82 - } 83 - 84 - lrw := global.LoggingResponseWriter{w, http.StatusOK} 85 - 86 - serveTemplate("music-gateway.html", release).ServeHTTP(&lrw, r) 87 - 88 - if lrw.Code != http.StatusOK { 89 - fmt.Printf("Error loading music gateway for %s\n", id) 90 - return 91 - } 92 - }) 93 - } 94 - 95 - func serveTemplate(page string, data any) http.Handler { 96 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 97 - lp_layout := filepath.Join("views", "layout.html") 98 - lp_header := filepath.Join("views", "header.html") 99 - lp_footer := filepath.Join("views", "footer.html") 100 - lp_prideflag := filepath.Join("views", "prideflag.html") 101 - fp := filepath.Join("views", filepath.Clean(page)) 102 - 103 - info, err := os.Stat(fp) 104 - if err != nil { 105 - if os.IsNotExist(err) { 106 - http.NotFound(w, r) 107 - return 108 - } 109 - } 110 - 111 - if info.IsDir() { 112 - http.NotFound(w, r) 113 - return 114 - } 115 - 116 - template, err := template.ParseFiles(lp_layout, lp_header, lp_footer, lp_prideflag, fp) 117 - if err != nil { 118 - fmt.Printf("Error parsing template files: %s\n", err) 119 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 120 - return 121 - } 122 - 123 - err = template.ExecuteTemplate(w, "layout.html", data) 124 - if err != nil { 125 - fmt.Printf("Error executing template: %s\n", err) 126 - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 127 - return 128 - } 129 - }) 130 - }
+23 -5
schema.sql
··· 1 + -- 2 + -- Artists (should be applicable to all art) 3 + -- 1 4 CREATE TABLE IF NOT EXISTS artists ( 2 5 id text NOT NULL, 3 6 name text, ··· 5 8 ); 6 9 ALTER TABLE artists ADD CONSTRAINT artists_pk PRIMARY KEY (id); 7 10 11 + -- 12 + -- Music releases 13 + -- 8 14 CREATE TABLE IF NOT EXISTS musicreleases ( 9 15 id character varying(64) NOT NULL, 10 16 title text NOT NULL, 17 + description text, 11 18 type text, 12 19 release_date DATE NOT NULL, 13 20 artwork text, ··· 16 23 ); 17 24 ALTER TABLE musicreleases ADD CONSTRAINT musicreleases_pk PRIMARY KEY (id); 18 25 26 + -- 27 + -- Music links (external platform links under a release) 28 + -- 19 29 CREATE TABLE IF NOT EXISTS musiclinks ( 20 30 release character varying(64) NOT NULL, 21 31 name text NOT NULL, ··· 23 33 ); 24 34 ALTER TABLE musiclinks ADD CONSTRAINT musiclinks_pk PRIMARY KEY (release, name); 25 35 36 + -- 37 + -- Music credits (artist credits under a release) 38 + -- 26 39 CREATE TABLE IF NOT EXISTS musiccredits ( 27 40 release character varying(64) NOT NULL, 28 41 artist text NOT NULL, ··· 31 44 ); 32 45 ALTER TABLE musiccredits ADD CONSTRAINT musiccredits_pk PRIMARY KEY (release, artist); 33 46 47 + -- 48 + -- Music tracks (tracks under a release) 49 + -- 34 50 CREATE TABLE IF NOT EXISTS musictracks ( 35 51 release character varying(64) NOT NULL, 36 52 number integer NOT NULL, ··· 41 57 ); 42 58 ALTER TABLE musictracks ADD CONSTRAINT musictracks_pk PRIMARY KEY (release, number); 43 59 44 - -- foreign keys 60 + -- 61 + -- Foreign keys 62 + -- 45 63 46 - ALTER TABLE public.musiccredits ADD CONSTRAINT musiccredits_artist_fk FOREIGN KEY (artist) REFERENCES public.artists(id) ON DELETE CASCADE; 64 + ALTER TABLE public.musiccredits ADD CONSTRAINT IF NOT EXISTS musiccredits_artist_fk FOREIGN KEY (artist) REFERENCES public.artists(id) ON DELETE CASCADE; 47 65 48 - ALTER TABLE public.musiccredits ADD CONSTRAINT musiccredits_release_fk FOREIGN KEY (release) REFERENCES public.musicreleases(id) ON DELETE CASCADE; 66 + ALTER TABLE public.musiccredits ADD CONSTRAINT IF NOT EXISTS musiccredits_release_fk FOREIGN KEY (release) REFERENCES public.musicreleases(id) ON DELETE CASCADE; 49 67 50 - ALTER TABLE public.musiclinks ADD CONSTRAINT musiclinks_release_fk FOREIGN KEY (release) REFERENCES public.musicreleases(id) ON UPDATE CASCADE ON DELETE CASCADE; 68 + ALTER TABLE public.musiclinks ADD CONSTRAINT IF NOT EXISTS musiclinks_release_fk FOREIGN KEY (release) REFERENCES public.musicreleases(id) ON UPDATE CASCADE ON DELETE CASCADE; 51 69 52 - ALTER TABLE public.musictracks ADD CONSTRAINT musictracks_release_fk FOREIGN KEY (release) REFERENCES public.musicreleases(id) ON DELETE CASCADE; 70 + ALTER TABLE public.musictracks ADD CONSTRAINT IF NOT EXISTS musictracks_release_fk FOREIGN KEY (release) REFERENCES public.musicreleases(id) ON DELETE CASCADE;
+42 -42
views/music-gateway.html
··· 1 1 {{define "head"}} 2 - <title>{{.Title}} - {{.PrintPrimaryArtists false true}}</title> 3 - <link rel="icon" href="{{.ResolveArtwork}}"> 2 + <title>{{.GetTitle}} - {{.PrintArtists true true}}</title> 3 + <link rel="icon" type="image/png" href="{{.GetArtwork}}"> 4 4 5 - <meta name="description" content="Stream &quot;{{.Title}}&quot; by {{.PrintPrimaryArtists false true}} on all platforms!"> 6 - <meta name="author" content="{{.PrintPrimaryArtists false true}}"> 7 - <meta name="keywords" content="{{.PrintPrimaryArtists false false}}, music, {{.Title}}, {{.Id}}, {{.GetReleaseYear}}"> 5 + <meta name="description" content="Stream &quot;{{.GetTitle}}&quot; by {{.PrintArtists true true}} on all platforms!"> 6 + <meta name="author" content="{{.PrintArtists true true}}"> 7 + <meta name="keywords" content="{{.PrintArtists true false}}, music, {{.GetTitle}}, {{.GetID}}, {{.GetReleaseYear}}"> 8 8 9 - <meta property="og:url" content="https://arimelody.me/music/{{.Id}}"> 9 + <meta property="og:url" content="https://arimelody.me/music/{{.GetID}}"> 10 10 <meta property="og:type" content="website"> 11 11 <meta property="og:locale" content="en_IE"> 12 12 <meta property="og:site_name" content="ari melody music"> 13 - <meta property="og.Title" content="{{.Title}} - {{.PrintPrimaryArtists false true}}"> 14 - <meta property="og:description" content="Stream &quot;{{.Title}}&quot; by {{.PrintPrimaryArtists false true}} on all platforms!"> 15 - <meta property="og:image" content="https://arimelody.me{{.ResolveArtwork}}"> 13 + <meta property="og.Title" content="{{.GetTitle}} - {{.PrintArtists true true}}"> 14 + <meta property="og:description" content="Stream &quot;{{.GetTitle}}&quot; by {{.PrintArtists true true}} on all platforms!"> 15 + <meta property="og:image" content="https://arimelody.me{{.GetArtwork}}"> 16 16 17 17 <meta name="twitter:card" content="summary_large_image"> 18 18 <meta name="twitter:site" content="@funniduck"> 19 19 <meta name="twitter:creator" content="@funniduck"> 20 20 <meta property="twitter:domain" content="arimelody.me"> 21 - <meta property="twitter:url" content="https://arimelody.me/music/{{.Id}}"> 22 - <meta name="twitter.Title" content="{{.PrintPrimaryArtists false true}} - {{.Title}}"> 23 - <meta name="twitter:description" content="Stream &quot;{{.Title}}&quot; by {{.PrintPrimaryArtists false true}} on all platforms!"> 24 - <meta name="twitter:image" content="https://arimelody.me{{.ResolveArtwork}}"> 25 - <meta name="twitter:image:alt" content="Cover art for &quot;{{.Title}}&quot;"> 21 + <meta property="twitter:url" content="https://arimelody.me/music/{{.GetID}}"> 22 + <meta name="twitter.Title" content="{{.PrintArtists true true}} - {{.GetTitle}}"> 23 + <meta name="twitter:description" content="Stream &quot;{{.GetTitle}}&quot; by {{.PrintArtists true true}} on all platforms!"> 24 + <meta name="twitter:image" content="https://arimelody.me{{.GetArtwork}}"> 25 + <meta name="twitter:image:alt" content="Cover art for &quot;{{.GetTitle}}&quot;"> 26 26 27 27 <link rel="stylesheet" href="/style/main.css"> 28 28 <link rel="stylesheet" href="/style/music-gateway.css"> ··· 32 32 <main> 33 33 <script type="module" src="/script/music-gateway.js"></script> 34 34 35 - <div id="background" style="background-image: url({{.ResolveArtwork}})"></div> 35 + <div id="background" style="background-image: url({{.GetArtwork}})"></div> 36 36 37 37 <a href="/music" swap-url="/music" id="go-back" title="back to arimelody.me">&lt;</a> 38 38 <br><br> ··· 47 47 <div class="tilt-bottom"></div> 48 48 <div class="tilt-bottomleft"></div> 49 49 <div class="tilt-left"></div> 50 - <img id="artwork" src="{{.ResolveArtwork}}" alt="{{.Title}} artwork" width=240 height=240> 50 + <img id="artwork" src="{{.GetArtwork}}" alt="{{.GetTitle}} artwork" width=240 height=240> 51 51 </div> 52 52 <div id="vertical-line"></div> 53 53 <div id="info"> 54 54 <div id="overview"> 55 55 <div id="title-container"> 56 - <h1 id="title">{{.Title}}</h1> 56 + <h1 id="title">{{.GetTitle}}</h1> 57 57 <span id="year" title="{{.PrintReleaseDate}}">{{.GetReleaseYear}}</span> 58 58 </div> 59 - <p id="artist">{{.PrintPrimaryArtists false true}}</p> 60 - <p id="type" class="{{.ResolveType}}">{{.ResolveType}}</p> 59 + <p id="artist">{{.PrintArtists true true}}</p> 60 + <p id="type" class="{{.GetType}}">{{.GetType}}</p> 61 61 62 62 <ul id="links"> 63 - {{if .Buylink}} 63 + {{if .GetBuyLink}} 64 64 <li> 65 - <a href="{{.Buylink}}" class="buy">{{or .Buyname "buy"}}</a> 65 + <a href="{{.GetBuyLink}}" class="buy">{{or .GetBuyName "buy"}}</a> 66 66 </li> 67 67 {{end}} 68 68 69 - {{range .Links}} 69 + {{range .GetLinks}} 70 70 <li> 71 - <a class="{{.NormaliseName}}" href="{{.Url}}">{{.Name}}</a> 71 + <a class="{{.NormaliseName}}" href="{{.GetURL}}">{{.GetName}}</a> 72 72 </li> 73 73 {{end}} 74 74 </ul> 75 75 76 - {{if .Description}} 76 + {{if .GetDescription}} 77 77 <p id="description"> 78 - {{.Description}} 78 + {{.GetDescription}} 79 79 </p> 80 80 {{end}} 81 81 82 82 <button id="share">share</button> 83 83 </div> 84 84 85 - {{if .Credits}} 85 + {{if .GetCredits}} 86 86 <div id="credits"> 87 87 <h2>credits:</h2> 88 88 <ul> 89 - {{range .Credits}} 90 - {{$Artist := .ResolveArtist}} 91 - {{if $Artist.Website}} 92 - <li><strong><a href="{{$Artist.Website}}">{{$Artist.Name}}</a></strong>: {{.Role}}</li> 89 + {{range .GetCredits}} 90 + {{$Artist := .GetArtist}} 91 + {{if $Artist.GetWebsite}} 92 + <li><strong><a href="{{$Artist.GetWebsite}}">{{$Artist.GetName}}</a></strong>: {{.GetRole}}</li> 93 93 {{else}} 94 - <li><strong>{{$Artist.Name}}</strong>: {{.Role}}</li> 94 + <li><strong>{{$Artist.GetName}}</strong>: {{.GetRole}}</li> 95 95 {{end}} 96 96 {{end}} 97 97 </ul> ··· 99 99 {{end}} 100 100 101 101 {{if .IsSingle}} 102 - {{$Track := index .Tracks 0}} 103 - {{if $Track.Lyrics}} 102 + {{$Track := index .GetTracks 0}} 103 + {{if $Track.GetLyrics}} 104 104 <div id="lyrics"> 105 105 <h2>lyrics:</h2> 106 - <p>{{$Track.Lyrics}}</p> 106 + <p>{{$Track.GetLyrics}}</p> 107 107 </div> 108 108 {{end}} 109 109 {{else}} 110 110 <div id="tracks"> 111 111 <h2>tracks:</h2> 112 - {{range .Tracks}} 112 + {{range .GetTracks}} 113 113 <details> 114 - <summary class="album-track-title">{{.Title}}</summary> 115 - {{.Lyrics}} 114 + <summary class="album-track-title">{{.GetTitle}}</summary> 115 + {{.GetLyrics}} 116 116 </details> 117 117 {{end}} 118 118 </div> 119 119 {{end}} 120 120 </div> 121 121 122 - {{if or .Credits not .IsSingle}} 122 + {{if or .GetCredits not .IsSingle}} 123 123 <div id="extras"> 124 124 <ul> 125 125 <li><a href="#overview">overview</a></li> 126 126 127 - {{if .Credits}} 127 + {{if .GetCredits}} 128 128 <li><a href="#credits">credits</a></li> 129 129 {{end}} 130 130 131 131 {{if .IsSingle}} 132 - {{$Track := index .Tracks 0}} 133 - {{if $Track.Lyrics}} 132 + {{$Track := index .GetTracks 0}} 133 + {{if $Track.GetLyrics}} 134 134 <li><a href="#lyrics">lyrics</a></li> 135 135 {{end}} 136 136 {{else}} ··· 158 158 <!-- <% } else { %> --> 159 159 <!-- <div class="track-preview" id="preview-<%= data.id %>"> --> 160 160 <!-- <i class="fa-solid fa-play play"></i> --> 161 - <!-- <p>{{.Title}}</p> --> 161 + <!-- <p>{{.GetTitle}}</p> --> 162 162 <!-- <audio src="<%= file %>"></audio> --> 163 163 <!-- </div> --> 164 164 <!-- <% } %> -->
+8 -8
views/music.html
··· 25 25 </h1> 26 26 27 27 <div id="music-container"> 28 - {{range $Album := .}} 29 - <div class="music" id="{{$Album.Id}}" swap-url="/music/{{$Album.Id}}"> 28 + {{range $Release := .}} 29 + <div class="music" id="{{$Release.GetID}}" swap-url="/music/{{$Release.GetID}}"> 30 30 <div class="music-artwork"> 31 - <img src="{{$Album.ResolveArtwork}}" alt="{{$Album.Title}} artwork" width="128" loading="lazy"> 31 + <img src="{{$Release.GetArtwork}}" alt="{{$Release.GetTitle}} artwork" width="128" loading="lazy"> 32 32 </div> 33 33 <div class="music-details"> 34 - <a href="/music/{{$Album.Id}}"><h1 class="music-title">{{$Album.Title}}</h1></a> 35 - <h2 class="music-artist">{{$Album.PrintPrimaryArtists false true}}</h2> 36 - <h3 class="music-type-{{.ResolveType}}">{{$Album.ResolveType}}</h3> 34 + <a href="/music/{{$Release.GetID}}"><h1 class="music-title">{{$Release.GetTitle}}</h1></a> 35 + <h2 class="music-artist">{{$Release.PrintArtists true true}}</h2> 36 + <h3 class="music-type-{{$Release.GetType}}">{{$Release.GetType}}</h3> 37 37 <ul class="music-links"> 38 - {{range $Link := $Album.Links}} 38 + {{range $Link := $Release.GetLinks}} 39 39 <li> 40 - <a href="{{$Link.Url}}" class="link-button">{{$Link.Name}}</a> 40 + <a href="{{$Link.GetURL}}" class="link-button">{{$Link.GetName}}</a> 41 41 </li> 42 42 {{end}} 43 43 </ul>