Foxnouns.NET/migration-tools/avatar-proxy/main.go

104 lines
2.8 KiB
Go
Raw Normal View History

2025-04-17 16:05:26 +02:00
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)
}
}
}