Compare commits

..

No commits in common. "8bf8e819aa561401d297834b095b03f003f248ef" and "ace8f803a402f7a1c2d9401db7c460f375d78dfe" have entirely different histories.

55 changed files with 310 additions and 371 deletions

View file

@ -2,10 +2,10 @@ EXEC = arimelody-web
.PHONY: $(EXEC)
build:
$(EXEC):
GOOS=linux GOARCH=amd64 go build -o $(EXEC)
bundle: build
bundle: $(EXEC)
tar czf $(EXEC).tar.gz $(EXEC) admin/components/ admin/views/ admin/static/ views/ public/ schema-migration/
clean:

View file

@ -1,18 +1,17 @@
package admin
import (
"database/sql"
"fmt"
"net/http"
"net/url"
"os"
"database/sql"
"fmt"
"net/http"
"net/url"
"os"
"arimelody-web/admin/templates"
"arimelody-web/controller"
"arimelody-web/log"
"arimelody-web/model"
"arimelody-web/controller"
"arimelody-web/log"
"arimelody-web/model"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/bcrypt"
)
func accountHandler(app *model.AppState) http.Handler {
@ -65,7 +64,7 @@ func accountIndexHandler(app *model.AppState) http.Handler {
session.Message = sessionMessage
session.Error = sessionError
err = templates.AccountTemplate.Execute(w, accountResponse{
err = accountTemplate.Execute(w, accountResponse{
Session: session,
TOTPs: totps,
})
@ -185,7 +184,7 @@ func totpSetupHandler(app *model.AppState) http.Handler {
session := r.Context().Value("session").(*model.Session)
err := templates.TOTPSetupTemplate.Execute(w, totpSetupData{ Session: session })
err := totpSetupTemplate.Execute(w, totpSetupData{ Session: session })
if err != nil {
fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -222,7 +221,7 @@ func totpSetupHandler(app *model.AppState) http.Handler {
if err != nil {
fmt.Printf("WARN: Failed to create TOTP method: %s\n", err)
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
err := templates.TOTPSetupTemplate.Execute(w, totpConfirmData{ Session: session })
err := totpSetupTemplate.Execute(w, totpConfirmData{ Session: session })
if err != nil {
fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -236,7 +235,7 @@ func totpSetupHandler(app *model.AppState) http.Handler {
fmt.Fprintf(os.Stderr, "WARN: Failed to generate TOTP QR code: %v\n", err)
}
err = templates.TOTPConfirmTemplate.Execute(w, totpConfirmData{
err = totpConfirmTemplate.Execute(w, totpConfirmData{
Session: session,
TOTP: &totp,
NameEscaped: url.PathEscape(totp.Name),
@ -297,7 +296,7 @@ func totpConfirmHandler(app *model.AppState) http.Handler {
confirmCodeOffset := controller.GenerateTOTP(totp.Secret, 1)
if code != confirmCodeOffset {
session.Error = sql.NullString{ Valid: true, String: "Incorrect TOTP code. Please try again." }
err = templates.TOTPConfirmTemplate.Execute(w, totpConfirmData{
err = totpConfirmTemplate.Execute(w, totpConfirmData{
Session: session,
TOTP: totp,
NameEscaped: url.PathEscape(totp.Name),

View file

@ -1,13 +1,12 @@
package admin
import (
"fmt"
"net/http"
"strings"
"fmt"
"net/http"
"strings"
"arimelody-web/admin/templates"
"arimelody-web/controller"
"arimelody-web/model"
"arimelody-web/model"
"arimelody-web/controller"
)
func serveArtist(app *model.AppState) http.Handler {
@ -40,7 +39,7 @@ func serveArtist(app *model.AppState) http.Handler {
session := r.Context().Value("session").(*model.Session)
err = templates.EditArtistTemplate.Execute(w, ArtistResponse{
err = artistTemplate.Execute(w, ArtistResponse{
Session: session,
Artist: artist,
Credits: credits,

View file

@ -1,24 +1,20 @@
package admin
import (
"context"
"database/sql"
"embed"
"fmt"
"mime"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
"context"
"database/sql"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"arimelody-web/admin/templates"
"arimelody-web/controller"
"arimelody-web/log"
"arimelody-web/model"
"arimelody-web/controller"
"arimelody-web/log"
"arimelody-web/model"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/bcrypt"
)
func Handler(app *model.AppState) http.Handler {
@ -95,7 +91,7 @@ func AdminIndexHandler(app *model.AppState) http.Handler {
Tracks []*model.Track
}
err = templates.IndexTemplate.Execute(w, IndexData{
err = indexTemplate.Execute(w, IndexData{
Session: session,
Releases: releases,
Artists: artists,
@ -124,7 +120,7 @@ func registerAccountHandler(app *model.AppState) http.Handler {
}
render := func() {
err := templates.RegisterTemplate.Execute(w, registerData{ Session: session })
err := registerTemplate.Execute(w, registerData{ Session: session })
if err != nil {
fmt.Printf("WARN: Error rendering create account page: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -234,7 +230,7 @@ func loginHandler(app *model.AppState) http.Handler {
}
render := func() {
err := templates.LoginTemplate.Execute(w, loginData{ Session: session })
err := loginTemplate.Execute(w, loginData{ Session: session })
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Error rendering admin login page: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -350,7 +346,7 @@ func loginTOTPHandler(app *model.AppState) http.Handler {
}
render := func() {
err := templates.LoginTOTPTemplate.Execute(w, loginTOTPData{ Session: session })
err := loginTOTPTemplate.Execute(w, loginTOTPData{ Session: session })
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to render login TOTP page: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -444,7 +440,7 @@ func logoutHandler(app *model.AppState) http.Handler {
Path: "/",
})
err = templates.LogoutTemplate.Execute(w, nil)
err = logoutTemplate.Execute(w, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to render logout page: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -464,21 +460,24 @@ func requireAccount(next http.Handler) http.HandlerFunc {
})
}
//go:embed "static"
var staticFS embed.FS
func staticHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
file, err := staticFS.ReadFile(filepath.Join("static", filepath.Clean(r.URL.Path)))
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
}
w.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(r.URL.Path)))
w.WriteHeader(http.StatusOK)
w.Write(file)
http.FileServer(http.Dir(filepath.Join("admin", "static"))).ServeHTTP(w, r)
})
}

View file

@ -1,13 +1,12 @@
package admin
import (
"arimelody-web/admin/templates"
"arimelody-web/log"
"arimelody-web/model"
"fmt"
"net/http"
"os"
"strings"
"arimelody-web/log"
"arimelody-web/model"
"fmt"
"net/http"
"os"
"strings"
)
func logsHandler(app *model.AppState) http.Handler {
@ -55,7 +54,7 @@ func logsHandler(app *model.AppState) http.Handler {
Logs []*log.Log
}
err = templates.LogsTemplate.Execute(w, LogsResponse{
err = logsTemplate.Execute(w, LogsResponse{
Session: session,
Logs: logs,
})

View file

@ -1,13 +1,12 @@
package admin
import (
"fmt"
"net/http"
"strings"
"fmt"
"net/http"
"strings"
"arimelody-web/admin/templates"
"arimelody-web/controller"
"arimelody-web/model"
"arimelody-web/controller"
"arimelody-web/model"
)
func serveRelease(app *model.AppState) http.Handler {
@ -61,7 +60,7 @@ func serveRelease(app *model.AppState) http.Handler {
Release *model.Release
}
err = templates.EditReleaseTemplate.Execute(w, ReleaseResponse{
err = releaseTemplate.Execute(w, ReleaseResponse{
Session: session,
Release: release,
})
@ -75,7 +74,7 @@ func serveRelease(app *model.AppState) http.Handler {
func serveEditCredits(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
err := templates.EditCreditsTemplate.Execute(w, release)
err := editCreditsTemplate.Execute(w, release)
if err != nil {
fmt.Printf("Error rendering edit credits component for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -98,7 +97,7 @@ func serveAddCredit(app *model.AppState, release *model.Release) http.Handler {
}
w.Header().Set("Content-Type", "text/html")
err = templates.AddCreditTemplate.Execute(w, response{
err = addCreditTemplate.Execute(w, response{
ReleaseID: release.ID,
Artists: artists,
})
@ -124,7 +123,7 @@ func serveNewCredit(app *model.AppState) http.Handler {
}
w.Header().Set("Content-Type", "text/html")
err = templates.NewCreditTemplate.Execute(w, artist)
err = newCreditTemplate.Execute(w, artist)
if err != nil {
fmt.Printf("Error rendering new credit component for %s: %s\n", artist.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -135,7 +134,7 @@ func serveNewCredit(app *model.AppState) http.Handler {
func serveEditLinks(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
err := templates.EditCreditsTemplate.Execute(w, release)
err := editLinksTemplate.Execute(w, release)
if err != nil {
fmt.Printf("Error rendering edit links component for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -152,7 +151,7 @@ func serveEditTracks(release *model.Release) http.Handler {
Add func(a int, b int) int
}
err := templates.EditTracksTemplate.Execute(w, editTracksData{
err := editTracksTemplate.Execute(w, editTracksData{
Release: release,
Add: func(a, b int) int { return a + b },
})
@ -178,7 +177,7 @@ func serveAddTrack(app *model.AppState, release *model.Release) http.Handler {
}
w.Header().Set("Content-Type", "text/html")
err = templates.AddTrackTemplate.Execute(w, response{
err = addTrackTemplate.Execute(w, response{
ReleaseID: release.ID,
Tracks: tracks,
})
@ -186,6 +185,7 @@ func serveAddTrack(app *model.AppState, release *model.Release) http.Handler {
fmt.Printf("Error rendering add tracks component for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
return
})
}
@ -204,10 +204,11 @@ func serveNewTrack(app *model.AppState) http.Handler {
}
w.Header().Set("Content-Type", "text/html")
err = templates.NewTrackTemplate.Execute(w, track)
err = newTrackTemplate.Execute(w, track)
if err != nil {
fmt.Printf("Error rendering new track component for %s: %s\n", track.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
return
})
}

125
admin/templates.go Normal file
View file

@ -0,0 +1,125 @@
package admin
import (
"arimelody-web/log"
"fmt"
"html/template"
"path/filepath"
"strings"
"time"
)
var indexTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "components", "release", "release-list-item.html"),
filepath.Join("admin", "views", "index.html"),
))
var loginTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "login.html"),
))
var loginTOTPTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "login-totp.html"),
))
var registerTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "register.html"),
))
var logoutTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "logout.html"),
))
var accountTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "edit-account.html"),
))
var totpSetupTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "totp-setup.html"),
))
var totpConfirmTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "totp-confirm.html"),
))
var logsTemplate = template.Must(template.New("layout.html").Funcs(template.FuncMap{
"parseLevel": func(level log.LogLevel) string {
switch level {
case log.LEVEL_INFO:
return "INFO"
case log.LEVEL_WARN:
return "WARN"
}
return fmt.Sprintf("%d?", level)
},
"titleCase": func(logType string) string {
runes := []rune(logType)
for i, r := range runes {
if (i == 0 || runes[i - 1] == ' ') && r >= 'a' && r <= 'z' {
runes[i] = r + ('A' - 'a')
}
}
return string(runes)
},
"lower": func(str string) string { return strings.ToLower(str) },
"prettyTime": func(t time.Time) string {
// return t.Format("2006-01-02 15:04:05")
// return t.Format("15:04:05, 2 Jan 2006")
return t.Format("02 Jan 2006, 15:04:05")
},
}).ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "logs.html"),
))
var releaseTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "edit-release.html"),
))
var artistTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "edit-artist.html"),
))
var trackTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "components", "release", "release-list-item.html"),
filepath.Join("admin", "views", "edit-track.html"),
))
var editCreditsTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "credits", "editcredits.html"),
))
var addCreditTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "credits", "addcredit.html"),
))
var newCreditTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "credits", "newcredit.html"),
))
var editLinksTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "links", "editlinks.html"),
))
var editTracksTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "tracks", "edittracks.html"),
))
var addTrackTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "tracks", "addtrack.html"),
))
var newTrackTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "tracks", "newtrack.html"),
))

View file

@ -1,128 +0,0 @@
package templates
import (
"arimelody-web/log"
"fmt"
"html/template"
"strings"
"time"
_ "embed"
)
//go:embed "html/layout.html"
var layoutHTML string
//go:embed "html/prideflag.html"
var prideflagHTML string
//go:embed "html/index.html"
var indexHTML string
//go:embed "html/register.html"
var registerHTML string
//go:embed "html/login.html"
var loginHTML string
//go:embed "html/login-totp.html"
var loginTotpHTML string
//go:embed "html/totp-confirm.html"
var totpConfirmHTML string
//go:embed "html/totp-setup.html"
var totpSetupHTML string
//go:embed "html/logout.html"
var logoutHTML string
//go:embed "html/logs.html"
var logsHTML string
//go:embed "html/edit-account.html"
var editAccountHTML string
//go:embed "html/edit-artist.html"
var editArtistHTML string
//go:embed "html/edit-release.html"
var editReleaseHTML string
//go:embed "html/edit-track.html"
var editTrackHTML string
//go:embed "html/components/credits/newcredit.html"
var componentNewCreditHTML string
//go:embed "html/components/credits/addcredit.html"
var componentAddCreditHTML string
//go:embed "html/components/credits/editcredits.html"
var componentEditCreditsHTML string
//go:embed "html/components/links/editlinks.html"
var componentEditLinksHTML string
//go:embed "html/components/release/release-list-item.html"
var componentReleaseListItemHTML string
//go:embed "html/components/tracks/newtrack.html"
var componentNewTrackHTML string
//go:embed "html/components/tracks/addtrack.html"
var componentAddTrackHTML string
//go:embed "html/components/tracks/edittracks.html"
var componentEditTracksHTML string
var BaseTemplate = template.Must(template.New("base").Parse(
strings.Join([]string{ layoutHTML, prideflagHTML }, "\n"),
))
var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
strings.Join([]string{
indexHTML,
componentReleaseListItemHTML,
}, "\n"),
))
var LoginTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(loginHTML))
var LoginTOTPTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(loginTotpHTML))
var RegisterTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(registerHTML))
var LogoutTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(logoutHTML))
var AccountTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editAccountHTML))
var TOTPSetupTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(totpSetupHTML))
var TOTPConfirmTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(totpConfirmHTML))
var LogsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Funcs(template.FuncMap{
"parseLevel": func(level log.LogLevel) string {
switch level {
case log.LEVEL_INFO:
return "INFO"
case log.LEVEL_WARN:
return "WARN"
}
return fmt.Sprintf("%d?", level)
},
"titleCase": func(logType string) string {
runes := []rune(logType)
for i, r := range runes {
if (i == 0 || runes[i - 1] == ' ') && r >= 'a' && r <= 'z' {
runes[i] = r + ('A' - 'a')
}
}
return string(runes)
},
"lower": func(str string) string { return strings.ToLower(str) },
"prettyTime": func(t time.Time) string {
// return t.Format("2006-01-02 15:04:05")
// return t.Format("15:04:05, 2 Jan 2006")
return t.Format("02 Jan 2006, 15:04:05")
},
}).Parse(logsHTML))
var EditReleaseTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editReleaseHTML))
var EditArtistTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editArtistHTML))
var EditTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
strings.Join([]string{
editTrackHTML,
componentReleaseListItemHTML,
}, "\n"),
))
var EditCreditsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditCreditsHTML))
var AddCreditTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentAddCreditHTML))
var NewCreditTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentNewCreditHTML))
var EditLinksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditLinksHTML))
var EditTracksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditTracksHTML))
var AddTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentAddTrackHTML))
var NewTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentNewTrackHTML))

View file

@ -1,13 +1,12 @@
package admin
import (
"fmt"
"net/http"
"strings"
"fmt"
"net/http"
"strings"
"arimelody-web/admin/templates"
"arimelody-web/controller"
"arimelody-web/model"
"arimelody-web/model"
"arimelody-web/controller"
)
func serveTrack(app *model.AppState) http.Handler {
@ -40,7 +39,7 @@ func serveTrack(app *model.AppState) http.Handler {
session := r.Context().Value("session").(*model.Session)
err = templates.EditTrackTemplate.Execute(w, TrackResponse{
err = trackTemplate.Execute(w, TrackResponse{
Session: session,
Track: track,
Releases: releases,

View file

@ -2,6 +2,7 @@ package api
import (
"context"
"errors"
"fmt"
"net/http"
"os"
@ -172,7 +173,7 @@ func getSession(app *model.AppState, r *http.Request) (*model.Session, error) {
// check cookies first
sessionCookie, err := r.Cookie(model.COOKIE_TOKEN)
if err != nil && err != http.ErrNoCookie {
return nil, fmt.Errorf("Failed to retrieve session cookie: %v\n", err)
return nil, errors.New(fmt.Sprintf("Failed to retrieve session cookie: %v\n", err))
}
if sessionCookie != nil {
token = sessionCookie.Value
@ -187,7 +188,7 @@ func getSession(app *model.AppState, r *http.Request) (*model.Session, error) {
session, err := controller.GetSession(app.DB, token)
if err != nil && !strings.Contains(err.Error(), "no rows") {
return nil, fmt.Errorf("Failed to retrieve session: %v\n", err)
return nil, errors.New(fmt.Sprintf("Failed to retrieve session: %v\n", err))
}
if session != nil {

View file

@ -50,7 +50,7 @@ func HandleImageUpload(app *model.AppState, data *string, directory string, file
return "", nil
}
app.Log.Info(log.TYPE_FILES, "\"%s\" created.", imagePath)
app.Log.Info(log.TYPE_FILES, "\"%s/%s.%s\" created.", directory, filename, ext)
return filename, nil
}

View file

@ -5,7 +5,7 @@ import "math/rand"
func GenerateAlnumString(length int) []byte {
const CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
res := []byte{}
for range length {
for i := 0; i < length; i++ {
res = append(res, CHARS[rand.Intn(len(CHARS))])
}
return res

View file

@ -1,12 +1,11 @@
package controller
import (
"embed"
"fmt"
"os"
"time"
"fmt"
"os"
"time"
"github.com/jmoiron/sqlx"
"github.com/jmoiron/sqlx"
)
const DB_VERSION int = 4
@ -56,13 +55,10 @@ func CheckDBVersionAndMigrate(db *sqlx.DB) {
fmt.Printf("Database schema up to date.\n")
}
//go:embed "schema-migration"
var schemaFS embed.FS
func ApplyMigration(db *sqlx.DB, scriptFile string) {
fmt.Printf("Applying schema migration %s...\n", scriptFile)
bytes, err := schemaFS.ReadFile("schema-migration/" + scriptFile + ".sql")
bytes, err := os.ReadFile("schema-migration/" + scriptFile + ".sql")
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to open schema file \"%s\": %v\n", scriptFile, err)
os.Exit(1)

View file

@ -2,8 +2,8 @@ package controller
import (
"encoding/base64"
// "image"
// "image/color"
"image"
"image/color"
"github.com/skip2/go-qrcode"
)
@ -18,35 +18,36 @@ func GenerateQRCode(data string) (string, error) {
}
// vvv DEPRECATED vvv
// const margin = 4
//
// type QRCodeECCLevel int64
// const (
// LOW QRCodeECCLevel = iota
// MEDIUM
// QUARTILE
// HIGH
// )
//
// func drawLargeAlignmentSquare(x int, y int, img *image.Gray) {
// for yi := range 7 {
// for xi := range 7 {
// if (xi == 0 || xi == 6) || (yi == 0 || yi == 6) {
// img.Set(x + xi, y + yi, color.Black)
// } else if (xi > 1 && xi < 5) && (yi > 1 && yi < 5) {
// img.Set(x + xi, y + yi, color.Black)
// }
// }
// }
// }
//
// func drawSmallAlignmentSquare(x int, y int, img *image.Gray) {
// for yi := range 5 {
// for xi := range 5 {
// if (xi == 0 || xi == 4) || (yi == 0 || yi == 4) {
// img.Set(x + xi, y + yi, color.Black)
// }
// }
// }
// img.Set(x + 2, y + 2, color.Black)
// }
const margin = 4
type QRCodeECCLevel int64
const (
LOW QRCodeECCLevel = iota
MEDIUM
QUARTILE
HIGH
)
func drawLargeAlignmentSquare(x int, y int, img *image.Gray) {
for yi := range 7 {
for xi := range 7 {
if (xi == 0 || xi == 6) || (yi == 0 || yi == 6) {
img.Set(x + xi, y + yi, color.Black)
} else if (xi > 1 && xi < 5) && (yi > 1 && yi < 5) {
img.Set(x + xi, y + yi, color.Black)
}
}
}
}
func drawSmallAlignmentSquare(x int, y int, img *image.Gray) {
for yi := range 5 {
for xi := range 5 {
if (xi == 0 || xi == 4) || (yi == 0 || yi == 4) {
img.Set(x + xi, y + yi, color.Black)
}
}
}
img.Set(x + 2, y + 2, color.Black)
}

View file

@ -1,6 +1,7 @@
package controller
import (
"errors"
"fmt"
"arimelody-web/model"
@ -20,7 +21,7 @@ func GetRelease(db *sqlx.DB, id string, full bool) (*model.Release, error) {
// get credits
credits, err := GetReleaseCredits(db, id)
if err != nil {
return nil, fmt.Errorf("Credits: %s", err)
return nil, errors.New(fmt.Sprintf("Credits: %s", err))
}
for _, credit := range credits {
release.Credits = append(release.Credits, credit)
@ -29,7 +30,7 @@ func GetRelease(db *sqlx.DB, id string, full bool) (*model.Release, error) {
// get tracks
tracks, err := GetReleaseTracks(db, id)
if err != nil {
return nil, fmt.Errorf("Tracks: %s", err)
return nil, errors.New(fmt.Sprintf("Tracks: %s", err))
}
for _, track := range tracks {
release.Tracks = append(release.Tracks, track)
@ -38,7 +39,7 @@ func GetRelease(db *sqlx.DB, id string, full bool) (*model.Release, error) {
// get links
links, err := GetReleaseLinks(db, id)
if err != nil {
return nil, fmt.Errorf("Links: %s", err)
return nil, errors.New(fmt.Sprintf("Links: %s", err))
}
for _, link := range links {
release.Links = append(release.Links, link)
@ -70,7 +71,7 @@ func GetAllReleases(db *sqlx.DB, onlyVisible bool, limit int, full bool) ([]*mod
// get credits
credits, err := GetReleaseCredits(db, release.ID)
if err != nil {
return nil, fmt.Errorf("Credits: %s", err)
return nil, errors.New(fmt.Sprintf("Credits: %s", err))
}
for _, credit := range credits {
release.Credits = append(release.Credits, credit)
@ -80,7 +81,7 @@ func GetAllReleases(db *sqlx.DB, onlyVisible bool, limit int, full bool) ([]*mod
// get tracks
tracks, err := GetReleaseTracks(db, release.ID)
if err != nil {
return nil, fmt.Errorf("Tracks: %s", err)
return nil, errors.New(fmt.Sprintf("Tracks: %s", err))
}
for _, track := range tracks {
release.Tracks = append(release.Tracks, track)
@ -89,7 +90,7 @@ func GetAllReleases(db *sqlx.DB, onlyVisible bool, limit int, full bool) ([]*mod
// get links
links, err := GetReleaseLinks(db, release.ID)
if err != nil {
return nil, fmt.Errorf("Links: %s", err)
return nil, errors.New(fmt.Sprintf("Links: %s", err))
}
for _, link := range links {
release.Links = append(release.Links, link)

View file

@ -2,6 +2,7 @@ package controller
import (
"database/sql"
"errors"
"fmt"
"net/http"
"strings"
@ -18,7 +19,7 @@ const TOKEN_LEN = 64
func GetSessionFromRequest(app *model.AppState, r *http.Request) (*model.Session, error) {
sessionCookie, err := r.Cookie(model.COOKIE_TOKEN)
if err != nil && err != http.ErrNoCookie {
return nil, fmt.Errorf("Failed to retrieve session cookie: %v", err)
return nil, errors.New(fmt.Sprintf("Failed to retrieve session cookie: %v", err))
}
var session *model.Session
@ -28,7 +29,7 @@ func GetSessionFromRequest(app *model.AppState, r *http.Request) (*model.Session
session, err = GetSession(app.DB, sessionCookie.Value)
if err != nil && !strings.Contains(err.Error(), "no rows") {
return nil, fmt.Errorf("Failed to retrieve session: %v", err)
return nil, errors.New(fmt.Sprintf("Failed to retrieve session: %v", err))
}
if session != nil {

55
main.go
View file

@ -1,33 +1,32 @@
package main
import (
"bufio"
"embed"
"errors"
"fmt"
stdLog "log"
"math"
"math/rand"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"bufio"
"errors"
"fmt"
stdLog "log"
"math"
"math/rand"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"arimelody-web/admin"
"arimelody-web/api"
"arimelody-web/colour"
"arimelody-web/controller"
"arimelody-web/cursor"
"arimelody-web/log"
"arimelody-web/model"
"arimelody-web/view"
"arimelody-web/admin"
"arimelody-web/api"
"arimelody-web/colour"
"arimelody-web/controller"
"arimelody-web/cursor"
"arimelody-web/log"
"arimelody-web/model"
"arimelody-web/view"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
"golang.org/x/crypto/bcrypt"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
"golang.org/x/crypto/bcrypt"
)
// used for database migrations
@ -36,16 +35,12 @@ const DB_VERSION = 1
const DEFAULT_PORT int64 = 8080
const HRT_DATE int64 = 1756478697
//go:embed "public"
var publicFS embed.FS
func main() {
fmt.Printf("made with <3 by ari melody\n\n")
app := model.AppState{
Config: controller.GetConfig(),
Twitch: nil,
PublicFS: publicFS,
}
// initialise database connection
@ -531,7 +526,7 @@ func createServeMux(app *model.AppState) *http.ServeMux {
mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(app)))
mux.Handle("/api/", http.StripPrefix("/api", api.Handler(app)))
mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler(app)))
mux.Handle("/uploads/", http.StripPrefix("/uploads", view.ServeFiles(filepath.Join(app.Config.DataDirectory, "uploads"))))
mux.Handle("/uploads/", http.StripPrefix("/uploads", view.StaticHandler(filepath.Join(app.Config.DataDirectory, "uploads"))))
mux.Handle("/cursor-ws", cursor.Handler(app))
mux.Handle("/", view.IndexHandler(app))

View file

@ -1,11 +1,9 @@
package model
import (
"embed"
"github.com/jmoiron/sqlx"
"github.com/jmoiron/sqlx"
"arimelody-web/log"
"arimelody-web/log"
)
type (
@ -45,6 +43,5 @@ type (
Config Config
Log log.Logger
Twitch *TwitchState
PublicFS embed.FS
}
)

View file

@ -1,21 +0,0 @@
{{define "prideflag"}}
<a href="https://github.com/arimelody/prideflag" target="_blank" id="prideflag">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" hx-preserve="true">
<path id="red" d="M120,80 L100,100 L120,120 Z" style="fill:#d20605"/>
<path id="orange" d="M120,80 V40 L80,80 L100,100 Z" style="fill:#ef9c00"/>
<path id="yellow" d="M120,40 V0 L60,60 L80,80 Z" style="fill:#e5fe02"/>
<path id="green" d="M120,0 H80 L40,40 L60,60 Z" style="fill:#09be01"/>
<path id="blue" d="M80,0 H40 L20,20 L40,40 Z" style="fill:#081a9a"/>
<path id="purple" d="M40,0 H0 L20,20 Z" style="fill:#76008a"/>
<rect id="black" x="60" width="60" height="60" style="fill:#010101"/>
<rect id="brown" x="70" width="50" height="50" style="fill:#603814"/>
<rect id="lightblue" x="80" width="40" height="40" style="fill:#73d6ed"/>
<rect id="pink" x="90" width="30" height="30" style="fill:#ffafc8"/>
<rect id="white" x="100" width="20" height="20" style="fill:#fff"/>
<rect id="intyellow" x="110" width="10" height="10" style="fill:#fed800"/>
<circle id="intpurple" cx="120" cy="0" r="5" stroke="#7601ad" stroke-width="2" fill="none"/>
</svg>
</a>
{{end}}

View file

@ -1,36 +1,28 @@
package templates
import (
_ "embed"
"html/template"
"strings"
"html/template"
"path/filepath"
)
//go:embed "html/layout.html"
var layoutHTML string
//go:embed "html/header.html"
var headerHTML string
//go:embed "html/footer.html"
var footerHTML string
//go:embed "html/prideflag.html"
var prideflagHTML string
//go:embed "html/index.html"
var indexHTML string
//go:embed "html/music.html"
var musicHTML string
//go:embed "html/music-gateway.html"
var musicGatewayHTML string
// //go:embed "html/404.html"
// var error404HTML string
var BaseTemplate = template.Must(template.New("base").Parse(
strings.Join([]string{
layoutHTML,
headerHTML,
footerHTML,
prideflagHTML,
}, "\n"),
var IndexTemplate = template.Must(template.ParseFiles(
filepath.Join("views", "layout.html"),
filepath.Join("views", "header.html"),
filepath.Join("views", "footer.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("views", "index.html"),
))
var MusicTemplate = template.Must(template.ParseFiles(
filepath.Join("views", "layout.html"),
filepath.Join("views", "header.html"),
filepath.Join("views", "footer.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("views", "music.html"),
))
var MusicGatewayTemplate = template.Must(template.ParseFiles(
filepath.Join("views", "layout.html"),
filepath.Join("views", "header.html"),
filepath.Join("views", "footer.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("views", "music-gateway.html"),
))
var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(indexHTML))
var MusicTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(musicHTML))
var MusicGatewayTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(musicGatewayHTML))

View file

@ -40,6 +40,6 @@ func IndexHandler(app *model.AppState) http.Handler {
return
}
ServeEmbedFS(app.PublicFS, "public").ServeHTTP(w, r)
StaticHandler("public").ServeHTTP(w, r)
})
}

View file

@ -1,31 +1,13 @@
package view
import (
"embed"
"errors"
"mime"
"net/http"
"os"
"path"
"path/filepath"
)
func ServeEmbedFS(fs embed.FS, dir string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
file, err := fs.ReadFile(filepath.Join(dir, filepath.Clean(r.URL.Path)))
if err != nil {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(r.URL.Path)))
w.WriteHeader(http.StatusOK)
w.Write(file)
})
}
func ServeFiles(directory string) http.Handler {
func StaticHandler(directory string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
info, err := os.Stat(filepath.Join(directory, filepath.Clean(r.URL.Path)))
@ -46,3 +28,4 @@ func ServeFiles(directory string) http.Handler {
http.FileServer(http.Dir(directory)).ServeHTTP(w, r)
})
}