MORE REFACTORING!! + some improvements
Signed-off-by: ari melody <ari@arimelody.me>
This commit is contained in:
parent
151b2d8fd9
commit
cba791deba
47
admin/admin.go
Normal file
47
admin/admin.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -3,45 +3,17 @@ package admin
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
// "strings"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/discord"
|
"arimelody.me/arimelody.me/discord"
|
||||||
"arimelody.me/arimelody.me/global"
|
"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 {
|
func Handler() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
@ -53,18 +25,13 @@ func Handler() http.Handler {
|
||||||
mux.Handle("/login", global.HTTPLog(LoginHandler()))
|
mux.Handle("/login", global.HTTPLog(LoginHandler()))
|
||||||
mux.Handle("/verify", global.HTTPLog(MustAuthorise(VerifyHandler())))
|
mux.Handle("/verify", global.HTTPLog(MustAuthorise(VerifyHandler())))
|
||||||
mux.Handle("/logout", global.HTTPLog(MustAuthorise(LogoutHandler())))
|
mux.Handle("/logout", global.HTTPLog(MustAuthorise(LogoutHandler())))
|
||||||
|
mux.Handle("/static", global.HTTPLog(MustAuthorise(staticHandler())))
|
||||||
|
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustAuthorise(next http.Handler) http.Handler {
|
func MustAuthorise(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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")
|
auth := r.Header.Get("Authorization")
|
||||||
if strings.HasPrefix(auth, "Bearer ") {
|
if strings.HasPrefix(auth, "Bearer ") {
|
||||||
auth = auth[7:]
|
auth = auth[7:]
|
||||||
|
@ -104,7 +71,6 @@ func MustAuthorise(next http.Handler) http.Handler {
|
||||||
|
|
||||||
ctx := context.WithValue(r.Context(), "role", "admin")
|
ctx := context.WithValue(r.Context(), "role", "admin")
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
*/
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +109,7 @@ func LoginHandler() http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// login success!
|
// login success!
|
||||||
session := CreateSession(discord_user.Username)
|
session := createSession(discord_user.Username)
|
||||||
sessions = append(sessions, &session)
|
sessions = append(sessions, &session)
|
||||||
|
|
||||||
cookie := http.Cookie{}
|
cookie := http.Cookie{}
|
||||||
|
@ -197,12 +163,60 @@ func VerifyHandler() http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateToken() string {
|
func ServeTemplate(page string, data any) http.Handler {
|
||||||
var token []byte
|
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++ {
|
info, err := os.Stat(fp)
|
||||||
token = append(token, TOKEN_CHARS[rand.Intn(len(TOKEN_CHARS))])
|
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)
|
||||||
|
})
|
||||||
}
|
}
|
11
colour/colour.go
Normal file
11
colour/colour.go
Normal file
|
@ -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"
|
|
@ -6,19 +6,40 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const API_ENDPOINT = "https://discord.com/api/v10"
|
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
|
var CLIENT_ID = func() string {
|
||||||
// the rules by doing this at all
|
envvar := os.Getenv("DISCORD_CLIENT_ID")
|
||||||
const CLIENT_SECRET = "JUEZnixhN7BxmLIHmbECiKETMP85VT0E"
|
if envvar == "" {
|
||||||
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"
|
fmt.Printf("DISCORD_CLIENT_ID was not provided. Admin login will be unavailable.\n")
|
||||||
|
}
|
||||||
// TODO: change before prod
|
return envvar
|
||||||
const MY_REDIRECT_URI = "http://127.0.0.1:8080/api/v1/admin/login"
|
}()
|
||||||
|
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 (
|
type (
|
||||||
AccessTokenResponse struct {
|
AccessTokenResponse struct {
|
||||||
|
@ -63,7 +84,7 @@ func GetOAuthTokenFromCode(code string) (string, error) {
|
||||||
"client_secret": {CLIENT_SECRET},
|
"client_secret": {CLIENT_SECRET},
|
||||||
"grant_type": {"authorization_code"},
|
"grant_type": {"authorization_code"},
|
||||||
"code": {code},
|
"code": {code},
|
||||||
"redirect_uri": {MY_REDIRECT_URI},
|
"redirect_uri": {OAUTH_CALLBACK_URI},
|
||||||
}.Encode()))
|
}.Encode()))
|
||||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
|
|
@ -8,19 +8,9 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
|
||||||
|
|
||||||
var MimeTypes = map[string]string{
|
"arimelody.me/arimelody.me/colour"
|
||||||
"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",
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultHeaders(next http.Handler) http.Handler {
|
func DefaultHeaders(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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)
|
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),
|
after.Format(time.UnixDate),
|
||||||
r.Method,
|
r.Method,
|
||||||
r.URL.Path,
|
r.URL.Path,
|
||||||
|
codeColour,
|
||||||
lrw.Code,
|
lrw.Code,
|
||||||
|
colour.Reset,
|
||||||
elapsed,
|
elapsed,
|
||||||
r.Header["User-Agent"][0])
|
r.Header["User-Agent"][0])
|
||||||
})
|
})
|
||||||
|
|
6
main.go
6
main.go
|
@ -7,8 +7,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/api/v1/admin"
|
"arimelody.me/arimelody.me/admin"
|
||||||
"arimelody.me/arimelody.me/api/v1/music"
|
"arimelody.me/arimelody.me/music"
|
||||||
"arimelody.me/arimelody.me/db"
|
"arimelody.me/arimelody.me/db"
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
)
|
)
|
||||||
|
@ -44,7 +44,7 @@ func main() {
|
||||||
func createServeMux() *http.ServeMux {
|
func createServeMux() *http.ServeMux {
|
||||||
mux := http.NewServeMux()
|
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/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())))
|
mux.Handle("/api/v1/music/", global.HTTPLog(http.StripPrefix("/api/v1/music", music.ServeRelease())))
|
||||||
|
|
|
@ -6,11 +6,19 @@ import (
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Credit struct {
|
type (
|
||||||
|
Credit struct {
|
||||||
Artist *Artist `json:"artist"`
|
Artist *Artist `json:"artist"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Primary bool `json:"primary"`
|
Primary bool `json:"primary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PostCreditBody struct {
|
||||||
|
Artist string `json:"artist"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
Primary bool `json:"primary"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// GETTERS
|
// GETTERS
|
||||||
|
|
|
@ -7,19 +7,10 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/api/v1/admin"
|
"arimelody.me/arimelody.me/admin"
|
||||||
"arimelody.me/arimelody.me/global"
|
"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
|
// HTTP HANDLER METHODS
|
||||||
|
|
||||||
func ServeCatalog() http.Handler {
|
func ServeCatalog() http.Handler {
|
||||||
|
@ -33,7 +24,7 @@ func ServeCatalog() http.Handler {
|
||||||
releases = append(releases, release)
|
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")
|
fp := filepath.Join("data", "music-artwork", releaseID + ".png")
|
||||||
fmt.Println(fp)
|
|
||||||
info, err := os.Stat(fp)
|
info, err := os.Stat(fp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/api/v1/admin"
|
"arimelody.me/arimelody.me/admin"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,7 +21,8 @@ const (
|
||||||
Compilation ReleaseType = "Compilation"
|
Compilation ReleaseType = "Compilation"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Release struct {
|
type (
|
||||||
|
Release struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
@ -33,7 +34,22 @@ type Release struct {
|
||||||
Links []Link `json:"links"`
|
Links []Link `json:"links"`
|
||||||
Credits []Credit `json:"credits"`
|
Credits []Credit `json:"credits"`
|
||||||
Tracks []Track `json:"tracks"`
|
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;
|
var Releases []Release;
|
||||||
|
|
||||||
|
@ -337,7 +353,7 @@ func (release Release) DeleteFromDB(db *sqlx.DB) error {
|
||||||
func PullAllReleases(db *sqlx.DB) ([]Release, error) {
|
func PullAllReleases(db *sqlx.DB) ([]Release, error) {
|
||||||
releases := []Release{}
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -423,19 +439,50 @@ func PostRelease() http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var release Release
|
var data PostReleaseBody
|
||||||
err := json.NewDecoder(r.Body).Decode(&release)
|
err := json.NewDecoder(r.Body).Decode(&data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if GetRelease(release.ID) != nil {
|
if GetRelease(data.ID) != nil {
|
||||||
http.Error(w, fmt.Sprintf("Release %s already exists", release.ID), http.StatusBadRequest)
|
http.Error(w, fmt.Sprintf("Release %s already exists", data.ID), http.StatusBadRequest)
|
||||||
return
|
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)
|
jsonBytes, err := json.Marshal(release)
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
|
@ -7,6 +7,6 @@ document.addEventListener("click", event => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
hamburger.addEventListener("click", event => {
|
hamburger.addEventListener("click", () => {
|
||||||
header_links.classList.toggle("open");
|
header_links.classList.toggle("open");
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import "./main.js";
|
import "./main.js";
|
||||||
|
|
||||||
document.addEventListener("swap", () => {
|
document.querySelectorAll("div.music").forEach(container => {
|
||||||
document.querySelectorAll("h1.music-title").forEach(element => {
|
const link = container.querySelector(".music-title a").href
|
||||||
element.href = "";
|
|
||||||
|
container.addEventListener("click", event => {
|
||||||
|
if (event.target.href) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
location = link;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
61
schema.sql
61
schema.sql
|
@ -1,17 +1,18 @@
|
||||||
--
|
--
|
||||||
-- Artists (should be applicable to all art)
|
-- Artists (should be applicable to all art)
|
||||||
--
|
--
|
||||||
CREATE TABLE IF NOT EXISTS artists (
|
CREATE TABLE artist (
|
||||||
id text NOT NULL,
|
id uuid DEFAULT gen_random_uuid(),
|
||||||
name text,
|
name text NOT NULL,
|
||||||
website text
|
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
|
-- Music releases
|
||||||
--
|
--
|
||||||
CREATE TABLE IF NOT EXISTS musicreleases (
|
CREATE TABLE musicrelease (
|
||||||
id character varying(64) NOT NULL,
|
id character varying(64) NOT NULL,
|
||||||
title text NOT NULL,
|
title text NOT NULL,
|
||||||
description text,
|
description text,
|
||||||
|
@ -21,50 +22,56 @@ CREATE TABLE IF NOT EXISTS musicreleases (
|
||||||
buyname text,
|
buyname text,
|
||||||
buylink 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)
|
-- Music links (external platform links under a release)
|
||||||
--
|
--
|
||||||
CREATE TABLE IF NOT EXISTS musiclinks (
|
CREATE TABLE musiclink (
|
||||||
release character varying(64) NOT NULL,
|
release character varying(64) NOT NULL,
|
||||||
name text 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)
|
-- Music credits (artist credits under a release)
|
||||||
--
|
--
|
||||||
CREATE TABLE IF NOT EXISTS musiccredits (
|
CREATE TABLE musiccredit (
|
||||||
release character varying(64) NOT NULL,
|
release character varying(64) NOT NULL,
|
||||||
artist text NOT NULL,
|
artist uuid NOT NULL,
|
||||||
role text,
|
role text NOT NULL,
|
||||||
is_primary boolean
|
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)
|
-- Music tracks (tracks under a release)
|
||||||
--
|
--
|
||||||
CREATE TABLE IF NOT EXISTS musictracks (
|
CREATE TABLE musictrack (
|
||||||
release character varying(64) NOT NULL,
|
id uuid DEFAULT gen_random_uuid(),
|
||||||
number integer NOT NULL,
|
|
||||||
title text NOT NULL,
|
title text NOT NULL,
|
||||||
description text,
|
description text,
|
||||||
lyrics text,
|
lyrics text,
|
||||||
preview_url 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
|
-- Foreign keys
|
||||||
--
|
--
|
||||||
|
ALTER TABLE musiccredit ADD CONSTRAINT musiccredit_artist_fk FOREIGN KEY (artist) REFERENCES artist(id) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
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 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 musiccredits ADD CONSTRAINT IF NOT EXISTS musiccredits_release_fk FOREIGN KEY (release) REFERENCES musicreleases(id) 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;
|
||||||
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;
|
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
<h2>tracks:</h2>
|
<h2>tracks:</h2>
|
||||||
{{range $i, $track := .GetTracks}}
|
{{range $i, $track := .GetTracks}}
|
||||||
<details>
|
<details>
|
||||||
<summary class="album-track-title">{{$i}}. {{$track.GetTitle}}</summary>
|
<summary class="album-track-title">{{$track.GetNumber}}. {{$track.GetTitle}}</summary>
|
||||||
{{$track.GetLyrics}}
|
{{$track.GetLyrics}}
|
||||||
</details>
|
</details>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -31,7 +31,11 @@
|
||||||
<img src="{{$Release.GetArtwork}}" alt="{{$Release.GetTitle}} artwork" width="128" loading="lazy">
|
<img src="{{$Release.GetArtwork}}" alt="{{$Release.GetTitle}} artwork" width="128" loading="lazy">
|
||||||
</div>
|
</div>
|
||||||
<div class="music-details">
|
<div class="music-details">
|
||||||
<a href="/music/{{$Release.GetID}}"><h1 class="music-title">{{$Release.GetTitle}}</h1></a>
|
<h1 class="music-title">
|
||||||
|
<a href="/music/{{$Release.GetID}}">
|
||||||
|
{{$Release.GetTitle}}
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
<h2 class="music-artist">{{$Release.PrintArtists true true}}</h2>
|
<h2 class="music-artist">{{$Release.PrintArtists true true}}</h2>
|
||||||
<h3 class="music-type-{{$Release.GetType}}">{{$Release.GetType}}</h3>
|
<h3 class="music-type-{{$Release.GetType}}">{{$Release.GetType}}</h3>
|
||||||
<ul class="music-links">
|
<ul class="music-links">
|
||||||
|
|
Loading…
Reference in a new issue