diff --git a/.air.toml b/.air.toml index 070166a..f8a1acf 100644 --- a/.air.toml +++ b/.air.toml @@ -7,7 +7,7 @@ tmp_dir = "tmp" bin = "./tmp/main" cmd = "go build -o ./tmp/main ." delay = 1000 - exclude_dir = ["admin/static", "admin\\static", "public", "uploads", "test", "db", "res"] + exclude_dir = ["admin/static", "public", "uploads", "test", "db"] exclude_file = [] exclude_regex = ["_test.go"] exclude_unchanged = false diff --git a/.gitignore b/.gitignore index cccde2b..781b36e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,4 @@ db/ tmp/ test/ uploads/ -docker-compose*.yml -!docker-compose.example.yml -config*.toml +docker-compose-test.yml diff --git a/README.md b/README.md index df0c351..7ac1702 100644 --- a/README.md +++ b/README.md @@ -4,36 +4,25 @@ home to your local SPACEGIRL! 💫 --- -built up from the initial [static](https://git.arimelody.me/ari/arimelody.me-static) -branch, this powerful, server-side rendered version comes complete with live -updates, powered by a new database and handy admin panel! +built up from the initial [static](https://git.arimelody.me/ari/arimelody.me-static) branch, this powerful, server-side rendered version comes complete with live updates, powered by a new database and super handy admin panel! -the admin panel currently facilitates live updating of my music discography, -though i plan to expand it towards art portfolio and blog posts in the future. -if all goes well, i'd like to later separate these components into their own -library for others to use in their own sites. exciting stuff! +the admin panel currently facilitates live updating of my music discography, though i plan to expand it towards art portfolio and blog posts in the future. if all goes well, i'd like to later separate these components into their own library for others to use in their own sites. exciting stuff! ## build -- `git clone` this repo, and `cd` into it. -- `go build -o arimelody-web .` +easy! just `git clone` this repo and `go build` from the root. `arimelody-web(.exe)` should be generated. ## running -the server should be run once to generate a default `config.toml` file. -configure as needed. note that a valid DB connection is required, and the admin -panel will be disabled without valid discord app credentials (this can however -be bypassed by running the server with `-adminBypass`). +the webserver depends on some environment variables (don't worry about forgetting some; it'll be sure to bug you about them): -the configuration may be overridden using environment variables in the format -`ARIMELODY__`. for example, `db.host` in the config may -be overridden with `ARIMELODY_DB_HOST`. +- `HTTP_DOMAIN`: the domain the webserver will use for generating oauth redirect URIs (default `https://arimelody.me`) +- `DISCORD_ADMIN`[^1]: the user ID of your discord account (discord auth is intended to be temporary, and will be replaced with its own auth system later) +- `DISCORD_CLIENT`[^1]: the client ID of your discord OAuth application. +- `DISCORD_SECRET`[^1]: the client secret of your discord OAuth application. -the location of the configuration file can also be overridden with -`ARIMELODY_CONFIG`. +[^1]: not required, but the admin panel will be **disabled** if these are not provided. -## database +the webserver requires a database to run. in this case, postgres. -the server requires a postgres database to run. you can use the -[schema.sql](schema.sql) provided in this repo to generate the required tables. -automatic schema building/migration may come in a future update. +the [docker compose script](docker-compose.yml) contains the basic requirements to get you up and running, though it does not currently initialise the schema on first run. you'll need to `docker compose exec -it arimelody.me-db-1` to access the database container while it's running, run `psql -U arimelody` to get a postgres shell, and copy/paste the contents of [schema.sql](schema.sql) to initialise the database. i'll build an automated initialisation script later ;p diff --git a/admin/admin.go b/admin/admin.go index a12e85b..d2bf80c 100644 --- a/admin/admin.go +++ b/admin/admin.go @@ -3,6 +3,7 @@ package admin import ( "fmt" "math/rand" + "os" "time" "arimelody-web/global" @@ -27,12 +28,20 @@ var ADMIN_BYPASS = func() bool { return false }() +var ADMIN_ID_DISCORD = func() string { + id := os.Getenv("DISCORD_ADMIN") + if id == "" { + // fmt.Printf("WARN: Discord admin ID (DISCORD_ADMIN) was not provided.\n") + } + return id +}() + var sessions []*Session -func createSession(userID string, expires time.Time) Session { +func createSession(username string, expires time.Time) Session { return Session{ Token: string(generateToken()), - UserID: userID, + UserID: username, Expires: expires, } } diff --git a/admin/http.go b/admin/http.go index b69342f..da3539a 100644 --- a/admin/http.go +++ b/admin/http.go @@ -8,13 +8,11 @@ import ( "path/filepath" "strings" "time" - "encoding/json" + "arimelody-web/discord" "arimelody-web/global" "arimelody-web/controller" "arimelody-web/model" - - "golang.org/x/crypto/bcrypt" ) type loginData struct { @@ -151,57 +149,52 @@ func GetSession(r *http.Request) *Session { func LoginHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet { - err := pages["login"].Execute(w, nil) - if err != nil { - fmt.Printf("Error rendering admin login page: %s\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } + if !discord.CREDENTIALS_PROVIDED || ADMIN_ID_DISCORD == "" { + http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) return } - if r.Method != http.MethodPost { - http.NotFound(w, r); + fmt.Println(discord.CLIENT_ID) + fmt.Println(discord.API_ENDPOINT) + fmt.Println(discord.REDIRECT_URI) + + code := r.URL.Query().Get("code") + + if code == "" { + pages["login"].Execute(w, loginData{DiscordURI: discord.REDIRECT_URI}) return } - type LoginRequest struct { - Username string `json:"username"` - Password string `json:"password"` - TOTP string `json:"totp"` - } - - data := LoginRequest{} - err := json.NewDecoder(r.Body).Decode(&data) + auth_token, err := discord.GetOAuthTokenFromCode(code) if err != nil { - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + fmt.Printf("Failed to retrieve discord access token: %s\n", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - account, err := controller.GetAccount(global.DB, data.Username) + discord_user, err := discord.GetDiscordUserFromAuth(auth_token) if err != nil { - http.Error(w, "No account exists with this username and password.", http.StatusBadRequest) + fmt.Printf("Failed to retrieve discord user information: %s\n", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - err = bcrypt.CompareHashAndPassword(account.Password, []byte(data.Password)) - if err != nil { - http.Error(w, "No account exists with this username and password.", http.StatusBadRequest) + if discord_user.ID != ADMIN_ID_DISCORD { + // TODO: unauthorized user; revoke the token + fmt.Printf("Unauthorized login attempted: %s\n", discord_user.ID) + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } - // TODO: check TOTP - // login success! - session := createSession(account.ID, time.Now().Add(24 * time.Hour)) + session := createSession(discord_user.Username, time.Now().Add(24 * time.Hour)) sessions = append(sessions, &session) cookie := http.Cookie{} cookie.Name = "token" cookie.Value = session.Token cookie.Expires = time.Now().Add(24 * time.Hour) - if strings.HasPrefix(global.Config.BaseUrl, "https") { + if strings.HasPrefix(global.HTTP_DOMAIN, "https") { cookie.Secure = true } cookie.HttpOnly = true diff --git a/api/artist.go b/api/artist.go index c793e23..9c88bc1 100644 --- a/api/artist.go +++ b/api/artist.go @@ -27,9 +27,7 @@ func ServeAllArtists() http.Handler { } w.Header().Add("Content-Type", "application/json") - encoder := json.NewEncoder(w) - encoder.SetIndent("", "\t") - err = encoder.Encode(artists) + err = json.NewEncoder(w).Encode(artists) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } @@ -76,9 +74,7 @@ func ServeArtist(artist *model.Artist) http.Handler { } w.Header().Add("Content-Type", "application/json") - encoder := json.NewEncoder(w) - encoder.SetIndent("", "\t") - err = encoder.Encode(artistJSON{ + err = json.NewEncoder(w).Encode(artistJSON{ Artist: artist, Credits: credits, }) diff --git a/api/release.go b/api/release.go index 2288153..e13bc93 100644 --- a/api/release.go +++ b/api/release.go @@ -104,9 +104,7 @@ func ServeRelease(release *model.Release) http.Handler { } w.Header().Add("Content-Type", "application/json") - encoder := json.NewEncoder(w) - encoder.SetIndent("", "\t") - err := encoder.Encode(response) + err := json.NewEncoder(w).Encode(response) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -157,9 +155,7 @@ func ServeCatalog() http.Handler { } w.Header().Add("Content-Type", "application/json") - encoder := json.NewEncoder(w) - encoder.SetIndent("", "\t") - err = encoder.Encode(catalog) + err = json.NewEncoder(w).Encode(catalog) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -208,9 +204,7 @@ func CreateRelease() http.Handler { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - encoder := json.NewEncoder(w) - encoder.SetIndent("", "\t") - err = encoder.Encode(release) + err = json.NewEncoder(w).Encode(release) if err != nil { fmt.Printf("WARN: Release %s created, but failed to send JSON response: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/api/track.go b/api/track.go index f6d5578..71a67e9 100644 --- a/api/track.go +++ b/api/track.go @@ -40,9 +40,7 @@ func ServeAllTracks() http.Handler { } w.Header().Add("Content-Type", "application/json") - encoder := json.NewEncoder(w) - encoder.SetIndent("", "\t") - err = encoder.Encode(tracks) + err = json.NewEncoder(w).Encode(tracks) if err != nil { fmt.Printf("FATAL: Failed to serve all tracks: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -64,9 +62,7 @@ func ServeTrack(track *model.Track) http.Handler { } w.Header().Add("Content-Type", "application/json") - encoder := json.NewEncoder(w) - encoder.SetIndent("", "\t") - err = encoder.Encode(Track{ track, releases }) + err = json.NewEncoder(w).Encode(Track{ track, releases }) if err != nil { fmt.Printf("FATAL: Failed to serve track %s: %s\n", track.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -132,9 +128,7 @@ func UpdateTrack(track *model.Track) http.Handler { } w.Header().Add("Content-Type", "application/json") - encoder := json.NewEncoder(w) - encoder.SetIndent("", "\t") - err = encoder.Encode(track) + err = json.NewEncoder(w).Encode(track) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } diff --git a/api/uploads.go b/api/uploads.go index 6b1c496..f2fe297 100644 --- a/api/uploads.go +++ b/api/uploads.go @@ -1,7 +1,6 @@ package api import ( - "arimelody-web/global" "bufio" "encoding/base64" "errors" @@ -16,7 +15,6 @@ func HandleImageUpload(data *string, directory string, filename string) (string, header := split[0] imageData, err := base64.StdEncoding.DecodeString(split[1]) ext, _ := strings.CutPrefix(header, "data:image/") - directory = filepath.Join(global.Config.DataDirectory, directory) switch ext { case "png": diff --git a/bundle.sh b/bundle.sh deleted file mode 100755 index 4e7068b..0000000 --- a/bundle.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -# simple script to pack up arimelody.me for production distribution - -if [ ! -f arimelody-web ]; then - echo "[FATAL] ./arimelody-web not found! please run \`go build -o arimelody-web\` first." - exit 1 -fi - -tar czvf arimelody-web.tar.gz arimelody-web admin/components/ admin/views/ admin/static/ views/ public/ diff --git a/discord/discord.go b/discord/discord.go index 8952c85..e517418 100644 --- a/discord/discord.go +++ b/discord/discord.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "os" "strings" "arimelody-web/global" @@ -15,7 +16,7 @@ const API_ENDPOINT = "https://discord.com/api/v10" var CREDENTIALS_PROVIDED = true var CLIENT_ID = func() string { - id := global.Config.Discord.ClientID + id := os.Getenv("DISCORD_CLIENT") if id == "" { // fmt.Printf("WARN: Discord client ID (DISCORD_CLIENT) was not provided.\n") CREDENTIALS_PROVIDED = false @@ -23,14 +24,14 @@ var CLIENT_ID = func() string { return id }() var CLIENT_SECRET = func() string { - secret := global.Config.Discord.Secret + secret := os.Getenv("DISCORD_SECRET") if secret == "" { // fmt.Printf("WARN: Discord secret (DISCORD_SECRET) was not provided.\n") CREDENTIALS_PROVIDED = false } return secret }() -var OAUTH_CALLBACK_URI = fmt.Sprintf("%s/admin/login", global.Config.BaseUrl) +var OAUTH_CALLBACK_URI = fmt.Sprintf("%s/admin/login", global.HTTP_DOMAIN) var REDIRECT_URI = fmt.Sprintf("https://discord.com/oauth2/authorize?client_id=%s&response_type=code&redirect_uri=%s&scope=identify", CLIENT_ID, OAUTH_CALLBACK_URI) type ( diff --git a/docker-compose.example.yml b/docker-compose.example.yml deleted file mode 100644 index 7833201..0000000 --- a/docker-compose.example.yml +++ /dev/null @@ -1,23 +0,0 @@ -services: - web: - image: docker.arimelody.me/arimelody.me:latest - build: . - ports: - - 8080:8080 - volumes: - - ./uploads:/app/uploads - - ./config.toml:/app/config.toml - environment: - ARIMELODY_CONFIG: config.toml - db: - image: postgres:16.1-alpine3.18 - volumes: - - arimelody-db:/var/lib/postgresql/data - environment: - POSTGRES_DB: # your database name here! - POSTGRES_USER: # your database user here! - POSTGRES_PASSWORD: # your database password here! - -volumes: - arimelody-db: - external: true diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..69f9247 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +services: + web: + image: docker.arimelody.me/arimelody.me:latest + build: . + ports: + - 8080:8080 + volumes: + - ./uploads:/app/uploads + environment: + HTTP_DOMAIN: "https://arimelody.me" + ARIMELODY_DB_HOST: db + DISCORD_ADMIN: # your discord user ID. + DISCORD_CLIENT: # your discord OAuth client ID. + DISCORD_SECRET: # your discord OAuth secret. + depends_on: + - db + restart: unless-stopped + db: + image: postgres:16.1-alpine3.18 + volumes: + - ./db:/var/lib/postgresql/data + environment: + POSTGRES_DB: arimelody + POSTGRES_USER: arimelody + POSTGRES_PASSWORD: fuckingpassword + restart: unless-stopped diff --git a/global/config.go b/global/config.go deleted file mode 100644 index d8c9f3d..0000000 --- a/global/config.go +++ /dev/null @@ -1,121 +0,0 @@ -package global - -import ( - "errors" - "fmt" - "os" - "strconv" - "strings" - - "github.com/jmoiron/sqlx" - "github.com/pelletier/go-toml/v2" -) - -type ( - dbConfig struct { - Host string `toml:"host"` - Name string `toml:"name"` - User string `toml:"user"` - Pass string `toml:"pass"` - } - - discordConfig struct { - AdminID string `toml:"admin_id" comment:"NOTE: admin_id to be deprecated in favour of local accounts and SSO."` - ClientID string `toml:"client_id"` - Secret string `toml:"secret"` - } - - config struct { - BaseUrl string `toml:"base_url" comment:"Used for OAuth redirects."` - Port int64 `toml:"port"` - DataDirectory string `toml:"data_dir"` - DB dbConfig `toml:"db"` - Discord discordConfig `toml:"discord"` - } -) - -var Config = func() config { - configFile := os.Getenv("ARIMELODY_CONFIG") - if configFile == "" { - configFile = "config.toml" - } - - config := config{ - BaseUrl: "https://arimelody.me", - Port: 8080, - } - - data, err := os.ReadFile(configFile) - if err != nil { - configOut, _ := toml.Marshal(&config) - os.WriteFile(configFile, configOut, os.ModePerm) - fmt.Printf( - "A default config.toml has been created. " + - "Please configure before running again!\n") - os.Exit(0) - } - - err = toml.Unmarshal([]byte(data), &config) - if err != nil { - fmt.Fprintf(os.Stderr, "FATAL: Failed to parse configuration file: %s\n", err.Error()) - os.Exit(1) - } - - err = handleConfigOverrides(&config) - if err != nil { - fmt.Fprintf(os.Stderr, "FATAL: Failed to parse environment variable %s\n", err.Error()) - os.Exit(1) - } - - return config -}() - -func handleConfigOverrides(config *config) error { - var err error - - if env, has := os.LookupEnv("ARIMELODY_BASE_URL"); has { config.BaseUrl = env } - if env, has := os.LookupEnv("ARIMELODY_PORT"); has { - config.Port, err = strconv.ParseInt(env, 10, 0) - if err != nil { return errors.New("ARIMELODY_PORT: " + err.Error()) } - } - if env, has := os.LookupEnv("ARIMELODY_DATA_DIR"); has { config.DataDirectory = env } - - if env, has := os.LookupEnv("ARIMELODY_DB_HOST"); has { config.DB.Host = env } - if env, has := os.LookupEnv("ARIMELODY_DB_NAME"); has { config.DB.Name = env } - if env, has := os.LookupEnv("ARIMELODY_DB_USER"); has { config.DB.User = env } - if env, has := os.LookupEnv("ARIMELODY_DB_PASS"); has { config.DB.Pass = env } - - if env, has := os.LookupEnv("ARIMELODY_DISCORD_ADMIN_ID"); has { config.Discord.AdminID = env } - if env, has := os.LookupEnv("ARIMELODY_DISCORD_CLIENT_ID"); has { config.Discord.ClientID = env } - if env, has := os.LookupEnv("ARIMELODY_DISCORD_SECRET"); has { config.Discord.Secret = env } - - return nil -} - -var Args = func() map[string]string { - args := map[string]string{} - - index := 0 - for index < len(os.Args[1:]) { - arg := os.Args[index + 1] - if !strings.HasPrefix(arg, "-") { - fmt.Printf("FATAL: Parameters must follow an argument (%s).\n", arg) - os.Exit(1) - } - - if index + 3 > len(os.Args) || strings.HasPrefix(os.Args[index + 2], "-") { - args[arg[1:]] = "true" - index += 1 - continue - } - - val := os.Args[index + 2] - args[arg[1:]] = val - // fmt.Printf("%s: %s\n", arg[1:], val) - index += 2 - } - - return args -}() - -var DB *sqlx.DB diff --git a/global/data.go b/global/data.go new file mode 100644 index 0000000..11c2814 --- /dev/null +++ b/global/data.go @@ -0,0 +1,54 @@ +package global + +import ( + "fmt" + "os" + "strings" + + "github.com/jmoiron/sqlx" +) + +var Args = func() map[string]string { + args := map[string]string{} + + index := 0 + for index < len(os.Args[1:]) { + arg := os.Args[index + 1] + if !strings.HasPrefix(arg, "-") { + fmt.Printf("FATAL: Parameters must follow an argument (%s).\n", arg) + os.Exit(1) + } + + if index + 3 > len(os.Args) || strings.HasPrefix(os.Args[index + 2], "-") { + args[arg[1:]] = "true" + index += 1 + continue + } + + val := os.Args[index + 2] + args[arg[1:]] = val + // fmt.Printf("%s: %s\n", arg[1:], val) + index += 2 + } + + return args +}() + +var HTTP_DOMAIN = func() string { + domain := os.Getenv("HTTP_DOMAIN") + if domain == "" { + return "https://arimelody.me" + } + return domain +}() + +var APP_SECRET = func() string { + secret := os.Getenv("ARIMELODY_SECRET") + if secret == "" { + fmt.Fprintln(os.Stderr, "FATAL: ARIMELODY_SECRET was not provided. Cannot continue.") + os.Exit(1) + } + return secret +}() + +var DB *sqlx.DB diff --git a/global/funcs.go b/global/funcs.go index c0462db..33198ab 100644 --- a/global/funcs.go +++ b/global/funcs.go @@ -1,57 +1,18 @@ package global import ( - "fmt" - "math/rand" - "net/http" - "strconv" - "time" + "fmt" + "net/http" + "strconv" + "time" - "arimelody-web/colour" + "arimelody-web/colour" ) -var PoweredByStrings = []string{ - "nerd rage", - "estrogen", - "your mother", - "awesome powers beyond comprehension", - "jared", - "the weight of my sins", - "the arc reactor", - "AA batteries", - "15 euro solar panel from ebay", - "magnets, how do they work", - "a fax machine", - "dell optiplex", - "a trans girl's nintendo wii", - "BASS", - "electricity, duh", - "seven hamsters in a big wheel", - "girls", - "mzungu hosting", - "golang", - "the state of the world right now", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)", - "the good folks at aperture science", - "free2play CDs", - "aridoodle", - "the love of creating", - "not for the sake of art; not for the sake of money; we like painting naked people", - "30 billion dollars in VC funding", -} - func DefaultHeaders(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Server", "arimelody.me") - w.Header().Add("Do-Not-Stab", "1") - w.Header().Add("X-Clacks-Overhead", "GNU Terry Pratchett") - w.Header().Add("X-Hacker", "spare me please") - w.Header().Add("X-Robots-TXT", "'; DROP TABLE pages;") - w.Header().Add("X-Thinking-With", "Portals") - w.Header().Add( - "X-Powered-By", - PoweredByStrings[rand.Intn(len(PoweredByStrings))], - ) + w.Header().Add("Cache-Control", "max-age=2592000") next.ServeHTTP(w, r) }) } diff --git a/go.mod b/go.mod index 73a1cf1..bcb2e16 100644 --- a/go.mod +++ b/go.mod @@ -8,4 +8,3 @@ require ( ) require golang.org/x/crypto v0.27.0 // indirect -require github.com/pelletier/go-toml/v2 v2.2.3 // indirect diff --git a/go.sum b/go.sum index 35e1b20..352b928 100644 --- a/go.sum +++ b/go.sum @@ -10,5 +10,3 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= diff --git a/main.go b/main.go index 7962869..552a83d 100644 --- a/main.go +++ b/main.go @@ -20,42 +20,15 @@ import ( _ "github.com/lib/pq" ) -const DEFAULT_PORT int64 = 8080 +const DEFAULT_PORT int = 8080 func main() { // initialise database connection - if env := os.Getenv("ARIMELODY_DB_HOST"); env != "" { global.Config.DB.Host = env } - if env := os.Getenv("ARIMELODY_DB_NAME"); env != "" { global.Config.DB.Name = env } - if env := os.Getenv("ARIMELODY_DB_USER"); env != "" { global.Config.DB.User = env } - if env := os.Getenv("ARIMELODY_DB_PASS"); env != "" { global.Config.DB.Pass = env } - if global.Config.DB.Host == "" { - fmt.Fprintf(os.Stderr, "FATAL: db.host not provided! Exiting...\n") - os.Exit(1) - } - if global.Config.DB.Name == "" { - fmt.Fprintf(os.Stderr, "FATAL: db.name not provided! Exiting...\n") - os.Exit(1) - } - if global.Config.DB.User == "" { - fmt.Fprintf(os.Stderr, "FATAL: db.user not provided! Exiting...\n") - os.Exit(1) - } - if global.Config.DB.Pass == "" { - fmt.Fprintf(os.Stderr, "FATAL: db.pass not provided! Exiting...\n") - os.Exit(1) - } + var dbHost = os.Getenv("ARIMELODY_DB_HOST") + if dbHost == "" { dbHost = "127.0.0.1" } var err error - global.DB, err = sqlx.Connect( - "postgres", - fmt.Sprintf( - "host=%s user=%s dbname=%s password='%s' sslmode=disable", - global.Config.DB.Host, - global.Config.DB.User, - global.Config.DB.Name, - global.Config.DB.Pass, - ), - ) + global.DB, err = initDB("postgres", "host=" + dbHost + " user=arimelody dbname=arimelody password=fuckingpassword sslmode=disable") if err != nil { fmt.Fprintf(os.Stderr, "FATAL: Unable to initialise database: %v\n", err) os.Exit(1) @@ -102,11 +75,9 @@ func main() { // start the web server! mux := createServeMux() - fmt.Printf("Now serving at http://127.0.0.1:%d\n", global.Config.Port) - log.Fatal( - http.ListenAndServe(fmt.Sprintf(":%d", global.Config.Port), - global.HTTPLog(global.DefaultHeaders(mux)), - )) + port := DEFAULT_PORT + fmt.Printf("Now serving at http://127.0.0.1:%d\n", port) + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), global.HTTPLog(mux))) } func initDB(driverName string, dataSourceName string) (*sqlx.DB, error) { @@ -240,13 +211,8 @@ func createServeMux() *http.ServeMux { mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler())) mux.Handle("/api/", http.StripPrefix("/api", api.Handler())) mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler())) - mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler(filepath.Join(global.Config.DataDirectory, "uploads")))) + mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler("uploads"))) mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodHead { - w.WriteHeader(http.StatusOK) - return - } - if r.URL.Path == "/" || r.URL.Path == "/index.html" { err := templates.Pages["index"].Execute(w, nil) if err != nil { diff --git a/public/img/buttons/aikoyori.gif b/public/img/buttons/aikoyori.gif deleted file mode 100644 index e62ef23..0000000 Binary files a/public/img/buttons/aikoyori.gif and /dev/null differ diff --git a/public/img/buttons/ioletsgo.gif b/public/img/buttons/ioletsgo.gif deleted file mode 100644 index 8edb51c..0000000 Binary files a/public/img/buttons/ioletsgo.gif and /dev/null differ diff --git a/public/img/buttons/ipg.png b/public/img/buttons/ipg.png deleted file mode 100644 index e64f0ab..0000000 Binary files a/public/img/buttons/ipg.png and /dev/null differ diff --git a/public/img/buttons/isabelroses.gif b/public/img/buttons/isabelroses.gif deleted file mode 100644 index 51e9a53..0000000 Binary files a/public/img/buttons/isabelroses.gif and /dev/null differ diff --git a/public/img/buttons/itzzen.png b/public/img/buttons/itzzen.png new file mode 100644 index 0000000..d9dc2ea Binary files /dev/null and b/public/img/buttons/itzzen.png differ diff --git a/public/img/buttons/notnite.png b/public/img/buttons/notnite.png deleted file mode 100644 index 6c51283..0000000 Binary files a/public/img/buttons/notnite.png and /dev/null differ diff --git a/public/img/buttons/retr0id_now.gif b/public/img/buttons/retr0id_now.gif deleted file mode 100644 index 7b16126..0000000 Binary files a/public/img/buttons/retr0id_now.gif and /dev/null differ diff --git a/public/img/buttons/stardust.png b/public/img/buttons/stardust.png deleted file mode 100644 index b64fae6..0000000 Binary files a/public/img/buttons/stardust.png and /dev/null differ diff --git a/public/img/buttons/xenia.png b/public/img/buttons/xenia.png deleted file mode 100644 index 9be1a1f..0000000 Binary files a/public/img/buttons/xenia.png and /dev/null differ diff --git a/public/style/index.css b/public/style/index.css index 6e04a37..b283985 100644 --- a/public/style/index.css +++ b/public/style/index.css @@ -107,7 +107,7 @@ ul.links li { } ul.links li a { - padding: .4em .5em; + padding: .2em .5em; border: 1px solid var(--links); color: var(--links); border-radius: 2px; diff --git a/schema.sql b/schema.sql index b5d92a3..746511a 100644 --- a/schema.sql +++ b/schema.sql @@ -1,61 +1,18 @@ -CREATE SCHEMA arimelody AUTHORIZATION arimelody; - --- --- Acounts --- -CREATE TABLE arimelody.account ( - id uuid DEFAULT gen_random_uuid(), - username text NOT NULL UNIQUE, - password text NOT NULL, - email text, - avatar_url text -); -ALTER TABLE arimelody.account ADD CONSTRAINT account_pk PRIMARY KEY (id); - --- --- Privilege --- -CREATE TABLE arimelody.privilege ( - account uuid NOT NULL, - privilege text NOT NULL -); -ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_pk PRIMARY KEY (account, privilege); - --- --- TOTP --- -CREATE TABLE arimelody.totp ( - account uuid NOT NULL, - name text NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); -ALTER TABLE arimelody.totp ADD CONSTRAINT totp_pk PRIMARY KEY (account, name); - --- --- Invites --- -CREATE TABLE arimelody.invite ( - code text NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP NOT NULL -); -ALTER TABLE arimelody.invite ADD CONSTRAINT invite_pk PRIMARY KEY (code); - -- -- Artists (should be applicable to all art) -- -CREATE TABLE arimelody.artist ( +CREATE TABLE public.artist ( id character varying(64), name text NOT NULL, website text, avatar text ); -ALTER TABLE arimelody.artist ADD CONSTRAINT artist_pk PRIMARY KEY (id); +ALTER TABLE public.artist ADD CONSTRAINT artist_pk PRIMARY KEY (id); -- -- Music releases -- -CREATE TABLE arimelody.musicrelease ( +CREATE TABLE public.musicrelease ( id character varying(64) NOT NULL, visible bool DEFAULT false, title text NOT NULL, @@ -68,59 +25,56 @@ CREATE TABLE arimelody.musicrelease ( copyright text, copyrightURL text ); -ALTER TABLE arimelody.musicrelease ADD CONSTRAINT musicrelease_pk PRIMARY KEY (id); +ALTER TABLE public.musicrelease ADD CONSTRAINT musicrelease_pk PRIMARY KEY (id); -- -- Music links (external platform links under a release) -- -CREATE TABLE arimelody.musiclink ( +CREATE TABLE public.musiclink ( release character varying(64) NOT NULL, name text NOT NULL, url text NOT NULL ); -ALTER TABLE arimelody.musiclink ADD CONSTRAINT musiclink_pk PRIMARY KEY (release, name); +ALTER TABLE public.musiclink ADD CONSTRAINT musiclink_pk PRIMARY KEY (release, name); -- -- Music credits (artist credits under a release) -- -CREATE TABLE arimelody.musiccredit ( +CREATE TABLE public.musiccredit ( release character varying(64) NOT NULL, artist character varying(64) NOT NULL, role text NOT NULL, is_primary boolean DEFAULT false ); -ALTER TABLE arimelody.musiccredit ADD CONSTRAINT musiccredit_pk PRIMARY KEY (release, artist); +ALTER TABLE public.musiccredit ADD CONSTRAINT musiccredit_pk PRIMARY KEY (release, artist); -- -- Music tracks (tracks under a release) -- -CREATE TABLE arimelody.musictrack ( +CREATE TABLE public.musictrack ( id uuid DEFAULT gen_random_uuid(), title text NOT NULL, description text, lyrics text, preview_url text ); -ALTER TABLE arimelody.musictrack ADD CONSTRAINT musictrack_pk PRIMARY KEY (id); +ALTER TABLE public.musictrack ADD CONSTRAINT musictrack_pk PRIMARY KEY (id); -- -- Music release/track pairs -- -CREATE TABLE arimelody.musicreleasetrack ( +CREATE TABLE public.musicreleasetrack ( release character varying(64) NOT NULL, track uuid NOT NULL, number integer NOT NULL ); -ALTER TABLE arimelody.musicreleasetrack ADD CONSTRAINT musicreleasetrack_pk PRIMARY KEY (release, track); +ALTER TABLE public.musicreleasetrack ADD CONSTRAINT musicreleasetrack_pk PRIMARY KEY (release, track); -- -- Foreign keys -- -ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE; -ALTER TABLE arimelody.totp ADD CONSTRAINT totp_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE; - -ALTER TABLE arimelody.musiccredit ADD CONSTRAINT musiccredit_artist_fk FOREIGN KEY (artist) REFERENCES artist(id) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE arimelody.musiccredit ADD CONSTRAINT musiccredit_release_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON DELETE CASCADE; -ALTER TABLE arimelody.musiclink ADD CONSTRAINT musiclink_release_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON UPDATE CASCADE ON DELETE CASCADE; -ALTER TABLE arimelody.musicreleasetrack ADD CONSTRAINT music_pair_trackref_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON DELETE CASCADE; -ALTER TABLE arimelody.musicreleasetrack ADD CONSTRAINT music_pair_releaseref_fk FOREIGN KEY (track) REFERENCES musictrack(id) ON DELETE CASCADE; +ALTER TABLE public.musiccredit ADD CONSTRAINT musiccredit_artist_fk FOREIGN KEY (artist) REFERENCES public.artist(id) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE public.musiccredit ADD CONSTRAINT musiccredit_release_fk FOREIGN KEY (release) REFERENCES public.musicrelease(id) ON DELETE CASCADE; +ALTER TABLE public.musiclink ADD CONSTRAINT musiclink_release_fk FOREIGN KEY (release) REFERENCES public.musicrelease(id) ON UPDATE CASCADE ON DELETE CASCADE; +ALTER TABLE public.musicreleasetrack ADD CONSTRAINT music_pair_trackref_fk FOREIGN KEY (release) REFERENCES public.musicrelease(id) ON DELETE CASCADE; +ALTER TABLE public.musicreleasetrack ADD CONSTRAINT music_pair_releaseref_fk FOREIGN KEY (track) REFERENCES public.musictrack(id) ON DELETE CASCADE; diff --git a/views/index.html b/views/index.html index 6a598c9..f54fbd5 100644 --- a/views/index.html +++ b/views/index.html @@ -129,17 +129,12 @@ OpenTerminal -
  • - - Silver.js - -

  • - ## cool critters + ## cool people