104 lines
2.8 KiB
Go
104 lines
2.8 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"strconv"
|
||
|
|
||
|
"github.com/go-chi/chi/v5"
|
||
|
"github.com/jackc/pgx/v5"
|
||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||
|
)
|
||
|
|
||
|
type Proxy struct {
|
||
|
OldAvatarBase string `json:"old_avatar_base"`
|
||
|
NewAvatarBase string `json:"new_avatar_base"`
|
||
|
FlagBase string `json:"flag_base"`
|
||
|
|
||
|
Port int `json:"port"`
|
||
|
Database string `json:"database"`
|
||
|
|
||
|
db *pgxpool.Pool
|
||
|
}
|
||
|
|
||
|
type EntityWithAvatar struct {
|
||
|
ID uint64
|
||
|
LegacyID string
|
||
|
Avatar string
|
||
|
AvatarMigrated bool
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
b, err := os.ReadFile("config.json")
|
||
|
if err != nil {
|
||
|
log.Fatalln("error reading config:", err)
|
||
|
}
|
||
|
|
||
|
p := &Proxy{}
|
||
|
err = json.Unmarshal(b, p)
|
||
|
if err != nil {
|
||
|
log.Fatalln("error parsing config:", err)
|
||
|
}
|
||
|
|
||
|
p.db, err = pgxpool.New(context.Background(), p.Database)
|
||
|
if err != nil {
|
||
|
log.Fatalln("error connecting to database:", err)
|
||
|
}
|
||
|
|
||
|
r := chi.NewRouter()
|
||
|
|
||
|
r.HandleFunc(`/flags/{hash:[\da-f]+}.webp`, func(w http.ResponseWriter, r *http.Request) {
|
||
|
http.Redirect(w, r, fmt.Sprintf("%s%s", p.FlagBase, r.URL.Path), http.StatusTemporaryRedirect)
|
||
|
})
|
||
|
r.Get(`/members/{id:[\d]+}/avatars/{hash:[\da-f]+}.webp`, p.proxyHandler("member"))
|
||
|
r.Get(`/users/{id:[\d]+}/avatars/{hash:[\da-f]+}.webp`, p.proxyHandler("user"))
|
||
|
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||
|
w.WriteHeader(http.StatusNotFound)
|
||
|
w.Write([]byte("file not found"))
|
||
|
})
|
||
|
|
||
|
log.Printf("serving on port %v", p.Port)
|
||
|
|
||
|
err = http.ListenAndServe(":"+strconv.Itoa(p.Port), r)
|
||
|
if err != nil {
|
||
|
log.Fatalf("listening on port %v: %v", p.Port, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (p *Proxy) proxyHandler(avatarType string) func(http.ResponseWriter, *http.Request) {
|
||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
id := chi.URLParam(r, "id")
|
||
|
hash := chi.URLParam(r, "hash")
|
||
|
|
||
|
var e EntityWithAvatar
|
||
|
// don't do this normally, kids. avatarType can only be "user" or "member" so it's fine here but this is a BAD idea otherwise.
|
||
|
err := p.db.QueryRow(
|
||
|
r.Context(), "SELECT id, legacy_id, avatar, avatar_migrated FROM "+avatarType+"s WHERE id = $1 AND avatar = $2",
|
||
|
id, hash,
|
||
|
).Scan(&e.ID, &e.LegacyID, &e.Avatar, &e.AvatarMigrated)
|
||
|
if err != nil {
|
||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||
|
w.WriteHeader(http.StatusNotFound)
|
||
|
w.Write([]byte("avatar not found"))
|
||
|
return
|
||
|
}
|
||
|
|
||
|
log.Printf("error getting avatar for %s %s: %v", avatarType, id, err)
|
||
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
w.Write([]byte("internal server error"))
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if e.AvatarMigrated {
|
||
|
http.Redirect(w, r, fmt.Sprintf("%s/%ss/%d/avatars/%s.webp", p.NewAvatarBase, avatarType, e.ID, e.Avatar), http.StatusTemporaryRedirect)
|
||
|
} else {
|
||
|
http.Redirect(w, r, fmt.Sprintf("%s/%ss/%s/%s.webp", p.OldAvatarBase, avatarType, e.LegacyID, e.Avatar), http.StatusTemporaryRedirect)
|
||
|
}
|
||
|
}
|
||
|
}
|