This commit is contained in:
2026-05-23 17:17:56 -07:00
commit 448f2e33ef
135 changed files with 11817 additions and 0 deletions
+199
View File
@@ -0,0 +1,199 @@
package main
import (
"context"
"gifuu/routes"
"gifuu/tools"
"net"
"net/http"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
)
func main() {
time.Local = time.UTC
// Startup Services
var stopCtx, stop = context.WithCancel(context.Background())
var stopWg sync.WaitGroup
var syncWg sync.WaitGroup
tools.LoggerInit.Log(tools.INFO, "Starting Services")
for _, fn := range []func(stop context.Context, await *sync.WaitGroup){
tools.SetupDatabase,
tools.SetupModel,
} {
syncWg.Add(1)
go func() {
defer syncWg.Done()
fn(stopCtx, &stopWg)
}()
}
syncWg.Wait()
go StartupHTTP(stopCtx, &stopWg)
// Await Shutdown Signal
cancel := make(chan os.Signal, 1)
signal.Notify(cancel, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-cancel
stop()
// Begin Shutdown Process
tools.LoggerInit.Log(tools.WARN, "Shutting Down!")
timeout, finish := context.WithTimeout(context.Background(), tools.TIMEOUT_CONTEXT)
defer finish()
go func() {
<-timeout.Done()
if timeout.Err() == context.DeadlineExceeded {
tools.LoggerInit.Log(tools.FATAL, "Shutdown Deadline Exceeded")
return
}
}()
stopWg.Wait()
os.Exit(0)
}
func SetupMux() http.HandlerFunc {
var (
mux = http.NewServeMux()
limitPublic = tools.NewRatelimiter("PUBLIC", 900, 5*time.Minute)
limitDelete = tools.NewRatelimiter("DELETE", 100, 5*time.Minute)
limitStart = tools.NewRatelimiter("START", 100, 5*time.Minute)
limitCreate = tools.NewRatelimiter("CREATE", 50, 5*time.Minute)
limitModerators = tools.NewRatelimiter("MOD", 300, 5*time.Minute)
gatekeepModerator = tools.NewGatekeeper(true)
powExpensive = tools.NewChallenger(20)
powNormal = tools.NewChallenger(18)
)
// General
mux.Handle("/uploads", tools.MethodHandler{
http.MethodPost: tools.Chain(routes.POST_Uploads, limitCreate, powExpensive),
})
mux.Handle("/tags/popular", tools.MethodHandler{
http.MethodGet: tools.Chain(routes.GET_Tags_Popular, limitPublic),
})
mux.Handle("/tags/autocomplete", tools.MethodHandler{
http.MethodGet: tools.Chain(routes.GET_Tags_Autocomplete, limitPublic),
})
mux.Handle("/art/latest", tools.MethodHandler{
http.MethodGet: tools.Chain(routes.GET_Art_Latest, limitPublic),
})
mux.Handle("/art/search", tools.MethodHandler{
http.MethodGet: tools.Chain(routes.GET_Art_Search, limitPublic),
})
mux.Handle("/art/{id}", tools.MethodHandler{
http.MethodGet: tools.Chain(routes.GET_Art_ID, limitPublic),
http.MethodDelete: tools.Chain(routes.DELETE_Art_ID, limitDelete),
})
mux.Handle("/art/{id}/report", tools.MethodHandler{
http.MethodPost: tools.Chain(routes.POST_Art_ID_Reports, limitCreate, powNormal),
})
mux.Handle("/metadata/{id}", tools.MethodHandler{
http.MethodGet: tools.Chain(routes.GET_Metadata_ID, limitPublic),
})
// Moderation
mux.Handle("/moderation/art/{id}", tools.MethodHandler{
http.MethodDelete: tools.Chain(routes.DELETE_Moderation_Art_ID, limitModerators, gatekeepModerator),
})
// mux.Handle("/moderation/art/{id}/rating", tools.MethodHandler{
// // TODO: Sets the items moderation to 100% hiding it from the public
// http.MethodPatch: tools.Chain(routes.PATCH_Moderation_Art_ID_Rating, limitModerators, gatekeepModerator),
// })
// mux.Handle("/moderation/art/{id}/bypass", tools.MethodHandler{
// // TODO: Disables Reporting for the item by setting the 'flag_bypass' flag
// http.MethodPatch: tools.Chain(routes.PATCH_Moderation_Art_ID_Bypass, limitModerators, gatekeepModerator),
// })
// mux.Handle("/moderation/art/{id}/reports", tools.MethodHandler{
// // TODO: Get Latest Reports for an Item
// http.MethodGet: tools.Chain(routes.GET_Moderation_Art_ID_Reports, limitModerators, gatekeepModerator),
// // TODO: Delete all Reports for an Item
// http.MethodDelete: tools.Chain(routes.DELETE_Moderation_Art_ID_Reports, limitModerators, gatekeepModerator),
// })
// // TODO: Get the Latest Reports across Site
// mux.Handle("/moderation/latest", tools.MethodHandler{
// http.MethodGet: tools.Chain(routes.GET_Moderation_Latest, limitModerators, gatekeepModerator),
// })
// Other
mux.Handle("/challenge", tools.MethodHandler{
http.MethodGet: tools.Chain(routes.GET_Challenge, limitStart),
})
mux.Handle("/limits", tools.MethodHandler{
http.MethodGet: tools.Chain(routes.GET_Limits, limitPublic),
})
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tools.SendClientError(w, r, tools.ERROR_UNKNOWN_ENDPOINT)
})
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Inject CORS
w.Header().Set("Access-Control-Allow-Origin", "*")
if r.Method == http.MethodOptions {
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Challenge-Nonce, X-Challenge-Counter")
w.WriteHeader(http.StatusNoContent)
return
}
mux.ServeHTTP(w, r)
})
}
func StartupHTTP(stop context.Context, await *sync.WaitGroup) {
var listener net.Listener
var err error
if strings.HasPrefix(tools.HTTP_ADDRESS, "unix/") {
path := strings.TrimPrefix(tools.HTTP_ADDRESS, "unix/")
os.Remove(path)
listener, err = net.Listen("unix", path)
if err == nil {
os.Chmod(path, 0660)
}
} else {
listener, err = net.Listen("tcp", tools.HTTP_ADDRESS)
}
if err != nil {
tools.LoggerHTTP.Log(tools.FATAL, "Listen failed: %s", err)
return
}
svr := http.Server{
Handler: SetupMux(),
MaxHeaderBytes: 4096,
IdleTimeout: 10 * time.Second,
ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
}
// Shutdown Logic
await.Add(1)
go func() {
defer await.Done()
<-stop.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), tools.TIMEOUT_SHUTDOWN)
defer cancel()
if err := svr.Shutdown(shutdownCtx); err != nil {
tools.LoggerHTTP.Log(tools.ERROR, "Shutdown error: %s", err)
}
tools.LoggerHTTP.Log(tools.INFO, "Closed")
}()
tools.LoggerHTTP.Log(tools.INFO, "Listening @ %s", tools.HTTP_ADDRESS)
if err := svr.Serve(listener); err != http.ErrServerClosed {
tools.LoggerHTTP.Log(tools.FATAL, "Startup Failed: %s", err)
return
}
}