MORE REFACTORING!! + some improvements

Signed-off-by: ari melody <ari@arimelody.me>
This commit is contained in:
ari melody 2024-08-02 00:53:19 +01:00
parent 151b2d8fd9
commit cba791deba
17 changed files with 376 additions and 223 deletions

47
admin/admin.go Normal file
View 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)
}

View file

@ -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)
})
}

11
colour/colour.go Normal file
View 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"

View file

@ -6,19 +6,40 @@ import (
"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")

View file

@ -8,19 +8,9 @@ import (
"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])
})

View file

@ -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())))

View file

@ -6,12 +6,20 @@ import (
"github.com/jmoiron/sqlx"
)
type Credit struct {
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
func (credit Credit) GetArtist() Artist {

View file

@ -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) {

View file

@ -8,7 +8,7 @@ import (
"strings"
"time"
"arimelody.me/arimelody.me/api/v1/admin"
"arimelody.me/arimelody.me/admin"
"github.com/jmoiron/sqlx"
)
@ -21,7 +21,8 @@ const (
Compilation ReleaseType = "Compilation"
)
type Release struct {
type (
Release struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
@ -35,6 +36,21 @@ type Release struct {
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;
// GETTERS
@ -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")

View file

@ -7,6 +7,6 @@ document.addEventListener("click", event => {
}
});
hamburger.addEventListener("click", event => {
hamburger.addEventListener("click", () => {
header_links.classList.toggle("open");
});

View file

@ -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;
});
});

View file

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

View file

@ -111,7 +111,7 @@
<h2>tracks:</h2>
{{range $i, $track := .GetTracks}}
<details>
<summary class="album-track-title">{{$i}}. {{$track.GetTitle}}</summary>
<summary class="album-track-title">{{$track.GetNumber}}. {{$track.GetTitle}}</summary>
{{$track.GetLyrics}}
</details>
{{end}}

View file

@ -31,7 +31,11 @@
<img src="{{$Release.GetArtwork}}" alt="{{$Release.GetTitle}} artwork" width="128" loading="lazy">
</div>
<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>
<h3 class="music-type-{{$Release.GetType}}">{{$Release.GetType}}</h3>
<ul class="music-links">