HOLY REFACTOR GOOD GRIEF (also finally started some CRUD work)
Signed-off-by: ari melody <ari@arimelody.me>
This commit is contained in:
parent
1c310c9101
commit
442889340c
80 changed files with 1571 additions and 1330 deletions
|
@ -9,9 +9,9 @@ import (
|
|||
|
||||
type (
|
||||
Session struct {
|
||||
UserID string
|
||||
Token string
|
||||
Expires int64
|
||||
Token string
|
||||
UserID string
|
||||
Expires time.Time
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -28,11 +28,11 @@ var ADMIN_ID_DISCORD = func() string {
|
|||
|
||||
var sessions []*Session
|
||||
|
||||
func createSession(UserID string) Session {
|
||||
func createSession(username string, expires time.Time) Session {
|
||||
return Session{
|
||||
UserID: UserID,
|
||||
Token: string(generateToken()),
|
||||
Expires: time.Now().Add(24 * time.Hour).Unix(),
|
||||
UserID: username,
|
||||
Expires: expires,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
166
admin/http.go
166
admin/http.go
|
@ -12,71 +12,106 @@ import (
|
|||
|
||||
"arimelody.me/arimelody.me/discord"
|
||||
"arimelody.me/arimelody.me/global"
|
||||
musicModel "arimelody.me/arimelody.me/music/model"
|
||||
)
|
||||
|
||||
func Handler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.Handle("/login", LoginHandler())
|
||||
mux.Handle("/logout", MustAuthorise(LogoutHandler()))
|
||||
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("hello /admin!"))
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
session := GetSession(r)
|
||||
if session == nil {
|
||||
http.Redirect(w, r, "/admin/login", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
type IndexData struct {
|
||||
Releases []musicModel.Release
|
||||
Artists []musicModel.Artist
|
||||
}
|
||||
|
||||
serveTemplate("index.html", IndexData{
|
||||
Releases: global.Releases,
|
||||
Artists: global.Artists,
|
||||
}).ServeHTTP(w, r)
|
||||
}))
|
||||
mux.Handle("/callback", global.HTTPLog(OAuthCallbackHandler()))
|
||||
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) {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if strings.HasPrefix(auth, "Bearer ") {
|
||||
auth = auth[7:]
|
||||
} else {
|
||||
cookie, err := r.Cookie("token")
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
auth = cookie.Value
|
||||
}
|
||||
|
||||
var session *Session
|
||||
for _, s := range sessions {
|
||||
if s.Expires < time.Now().Unix() {
|
||||
// expired session. remove it from the list!
|
||||
new_sessions := []*Session{}
|
||||
for _, ns := range sessions {
|
||||
if ns.Token == s.Token {
|
||||
continue
|
||||
}
|
||||
new_sessions = append(new_sessions, ns)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if s.Token == auth {
|
||||
session = s
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
session := GetSession(r)
|
||||
if session == nil {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), "role", "admin")
|
||||
ctx := context.WithValue(r.Context(), "session", session)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func GetSession(r *http.Request) *Session {
|
||||
// TODO: remove later- this bypasses auth!
|
||||
return &Session{}
|
||||
|
||||
var token = ""
|
||||
// is the session token in context?
|
||||
var ctx_session = r.Context().Value("session")
|
||||
if ctx_session != nil {
|
||||
token = ctx_session.(string)
|
||||
}
|
||||
// okay, is it in the auth header?
|
||||
if token == "" {
|
||||
if strings.HasPrefix(r.Header.Get("Authorization"), "Bearer ") {
|
||||
token = r.Header.Get("Authorization")[7:]
|
||||
}
|
||||
}
|
||||
// finally, is it in the cookie?
|
||||
if token == "" {
|
||||
cookie, err := r.Cookie("token")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
token = cookie.Value
|
||||
}
|
||||
|
||||
var session *Session = nil
|
||||
for _, s := range sessions {
|
||||
if s.Expires.Before(time.Now()) {
|
||||
// expired session. remove it from the list!
|
||||
new_sessions := []*Session{}
|
||||
for _, ns := range sessions {
|
||||
if ns.Token == s.Token {
|
||||
continue
|
||||
}
|
||||
new_sessions = append(new_sessions, ns)
|
||||
}
|
||||
sessions = new_sessions
|
||||
continue
|
||||
}
|
||||
|
||||
if s.Token == token {
|
||||
session = s
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
func LoginHandler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if ADMIN_ID_DISCORD == "" {
|
||||
if discord.CREDENTIALS_PROVIDED && ADMIN_ID_DISCORD == "" {
|
||||
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
@ -84,7 +119,7 @@ func LoginHandler() http.Handler {
|
|||
code := r.URL.Query().Get("code")
|
||||
|
||||
if code == "" {
|
||||
http.Redirect(w, r, discord.REDIRECT_URI, http.StatusTemporaryRedirect)
|
||||
serveTemplate("login.html", discord.REDIRECT_URI).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -109,7 +144,7 @@ func LoginHandler() http.Handler {
|
|||
}
|
||||
|
||||
// login success!
|
||||
session := createSession(discord_user.Username)
|
||||
session := createSession(discord_user.Username, time.Now().Add(24 * time.Hour))
|
||||
sessions = append(sessions, &session)
|
||||
|
||||
cookie := http.Cookie{}
|
||||
|
@ -122,12 +157,25 @@ func LoginHandler() http.Handler {
|
|||
http.SetCookie(w, &cookie)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(session.Token))
|
||||
w.Header().Add("Content-Type", "text/html")
|
||||
w.Write([]byte(
|
||||
"<!DOCTYPE html><html><head>"+
|
||||
"<meta http-equiv=\"refresh\" content=\"5;url=/admin/\" />"+
|
||||
"</head><body>"+
|
||||
"Logged in successfully. "+
|
||||
"You should be redirected to <a href=\"/admin/\">/admin/</a> in 5 seconds."+
|
||||
"</body></html>"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func LogoutHandler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
token := r.Context().Value("token").(string)
|
||||
|
||||
if token == "" {
|
||||
|
@ -145,31 +193,19 @@ func LogoutHandler() http.Handler {
|
|||
}(token)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
w.Write([]byte(
|
||||
"<meta http-equiv=\"refresh\" content=\"5;url=/\" />"+
|
||||
"Logged out successfully. "+
|
||||
"You should be redirected to <a href=\"/\">/</a> in 5 seconds."),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func OAuthCallbackHandler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func VerifyHandler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// this is an authorised endpoint, so you *must* supply a valid token
|
||||
// before accessing this route.
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
})
|
||||
}
|
||||
|
||||
func ServeTemplate(page string, data any) http.Handler {
|
||||
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_layout := filepath.Join("views", "admin", "layout.html")
|
||||
lp_prideflag := filepath.Join("views", "prideflag.html")
|
||||
fp := filepath.Join("views", filepath.Clean(page))
|
||||
fp := filepath.Join("views", "admin", filepath.Clean(page))
|
||||
|
||||
info, err := os.Stat(fp)
|
||||
if err != nil {
|
||||
|
@ -184,7 +220,7 @@ func ServeTemplate(page string, data any) http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
template, err := template.ParseFiles(lp_layout, lp_header, lp_footer, lp_prideflag, fp)
|
||||
template, err := template.ParseFiles(lp_layout, lp_prideflag, fp)
|
||||
if err != nil {
|
||||
fmt.Printf("Error parsing template files: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
|
187
admin/static/admin.css
Normal file
187
admin/static/admin.css
Normal file
|
@ -0,0 +1,187 @@
|
|||
@import url("/style/prideflag.css");
|
||||
@import url("/font/inter/inter.css");
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: "Inter", sans-serif;
|
||||
font-size: 16px;
|
||||
|
||||
color: #303030;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
header {
|
||||
width: min(720px, calc(100% - 2em));
|
||||
height: 2em;
|
||||
margin: 1em auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
background: #f8f8f8;
|
||||
border-radius: .5em;
|
||||
border: 1px solid #808080;
|
||||
}
|
||||
header .icon {
|
||||
height: 100%;
|
||||
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
header a {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
|
||||
margin: 0px;
|
||||
padding: 0 1em;
|
||||
|
||||
display: flex;
|
||||
|
||||
line-height: 2em;
|
||||
text-decoration: none;
|
||||
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
header a:hover {
|
||||
background: #00000010;
|
||||
}
|
||||
|
||||
main {
|
||||
width: min(720px, calc(100% - 2em));
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin: 0 0 .5em 0;
|
||||
}
|
||||
|
||||
.card h3,
|
||||
.card p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.release {
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
|
||||
border-radius: .5em;
|
||||
background: #f8f8f8f8;
|
||||
border: 1px solid #808080;
|
||||
}
|
||||
|
||||
.release-artwork {
|
||||
width: 96px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.release-artwork img {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.latest-release .release-info {
|
||||
width: 300px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.release-title small {
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.release-links {
|
||||
margin: .5em 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
list-style: none;
|
||||
flex-wrap: wrap;
|
||||
gap: .5em;
|
||||
}
|
||||
|
||||
.release-links li {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.release-links a {
|
||||
padding: .5em;
|
||||
display: block;
|
||||
|
||||
border-radius: .5em;
|
||||
text-decoration: none;
|
||||
color: #f0f0f0;
|
||||
background: #303030;
|
||||
text-align: center;
|
||||
|
||||
transition: color .1s, background .1s;
|
||||
}
|
||||
|
||||
.release-links a:hover {
|
||||
color: #303030;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.release-actions {
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
.release-actions a {
|
||||
margin-right: .3em;
|
||||
padding: .3em .5em;
|
||||
display: inline-block;
|
||||
|
||||
border-radius: .3em;
|
||||
background: #e0e0e0;
|
||||
|
||||
transition: color .1s, background .1s;
|
||||
}
|
||||
|
||||
.release-actions a:hover {
|
||||
color: #303030;
|
||||
background: #f0f0f0;
|
||||
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.artist {
|
||||
padding: .5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: .5em;
|
||||
|
||||
border-radius: .5em;
|
||||
background: #f8f8f8f8;
|
||||
border: 1px solid #808080;
|
||||
}
|
||||
|
||||
.artist:hover {
|
||||
text-decoration: hover;
|
||||
}
|
||||
|
||||
.artist-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
object-fit: cover;
|
||||
border-radius: 100%;
|
||||
}
|
0
admin/static/admin.js
Normal file
0
admin/static/admin.js
Normal file
Loading…
Add table
Add a link
Reference in a new issue