feat: initialize warpbox.dev project structure and backend

Initialize the repository with the core Go backend architecture and a frontend mockup for warpbox.dev, a self-hosted file-sharing application.

- Set up Go backend modules for configuration, HTTP server, middleware, handlers, and templates.
- Add local development scripts, environment templates, and basic project configuration.
- Include a React-based frontend mockup under the docs directory.
This commit is contained in:
2026-05-25 15:36:49 +03:00
parent 84e5aee87c
commit 9b8ef16474
129 changed files with 19863 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
package handlers
import (
"log/slog"
"net/http"
"warpbox.dev/backend/libs/config"
"warpbox.dev/backend/libs/services"
"warpbox.dev/backend/libs/web"
)
type App struct {
cfg config.Config
logger *slog.Logger
renderer *web.Renderer
uploadService *services.UploadService
}
func NewApp(cfg config.Config, logger *slog.Logger, renderer *web.Renderer, uploadService *services.UploadService) *App {
return &App{
cfg: cfg,
logger: logger,
renderer: renderer,
uploadService: uploadService,
}
}
func (a *App) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /", a.Home)
mux.HandleFunc("GET /healthz", a.Health)
mux.HandleFunc("GET /api/v1/health", a.Health)
mux.HandleFunc("POST /api/v1/upload", a.UploadPlaceholder)
mux.Handle("GET /static/", a.Static())
}

View File

@@ -0,0 +1,20 @@
package handlers
import (
"net/http"
"time"
"warpbox.dev/backend/libs/helpers"
)
type healthResponse struct {
Status string `json:"status"`
Time string `json:"time"`
}
func (a *App) Health(w http.ResponseWriter, r *http.Request) {
helpers.WriteJSON(w, http.StatusOK, healthResponse{
Status: "ok",
Time: time.Now().UTC().Format(time.RFC3339),
})
}

View File

@@ -0,0 +1,21 @@
package handlers
import (
"net/http"
"warpbox.dev/backend/libs/web"
)
type homeData struct {
MaxUploadSize string
}
func (a *App) Home(w http.ResponseWriter, r *http.Request) {
a.renderer.Render(w, http.StatusOK, "home.gohtml", web.PageData{
Title: "Upload your files",
Description: "Upload and share files through a self-hosted Warpbox instance.",
Data: homeData{
MaxUploadSize: a.uploadService.MaxUploadSizeLabel(),
},
})
}

View File

@@ -0,0 +1,31 @@
package handlers
import (
"net/http"
"path/filepath"
"strings"
)
func (a *App) Static() http.Handler {
fileServer := http.StripPrefix("/static/", http.FileServer(http.Dir(a.cfg.StaticDir)))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
setStaticCacheHeaders(w, r.URL.Path)
fileServer.ServeHTTP(w, r)
})
}
func setStaticCacheHeaders(w http.ResponseWriter, path string) {
ext := strings.ToLower(filepath.Ext(path))
switch ext {
case ".avif", ".gif", ".ico", ".jpg", ".jpeg", ".png", ".svg", ".webp",
".mp4", ".m4v", ".mov", ".webm", ".mp3", ".ogg",
".eot", ".otf", ".ttf", ".woff", ".woff2":
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
case ".css", ".js":
w.Header().Set("Cache-Control", "public, max-age=86400")
default:
w.Header().Set("Cache-Control", "public, max-age=3600")
}
}

View File

@@ -0,0 +1,26 @@
package handlers
import (
"net/http/httptest"
"testing"
)
func TestSetStaticCacheHeaders(t *testing.T) {
tests := map[string]string{
"/static/css/app.css": "public, max-age=86400",
"/static/js/app.js": "public, max-age=86400",
"/static/img/preview.webp": "public, max-age=31536000, immutable",
"/static/fonts/ui.woff2": "public, max-age=31536000, immutable",
"/static/videos/intro.mp4": "public, max-age=31536000, immutable",
"/static/uploads/file.data": "public, max-age=3600",
}
for path, want := range tests {
response := httptest.NewRecorder()
setStaticCacheHeaders(response, path)
if got := response.Header().Get("Cache-Control"); got != want {
t.Fatalf("Cache-Control for %s = %q, want %q", path, got, want)
}
}
}

View File

@@ -0,0 +1,19 @@
package handlers
import (
"net/http"
"warpbox.dev/backend/libs/helpers"
)
type uploadPlaceholderResponse struct {
Message string `json:"message"`
MaxUploadSize string `json:"maxUploadSize"`
}
func (a *App) UploadPlaceholder(w http.ResponseWriter, r *http.Request) {
helpers.WriteJSON(w, http.StatusNotImplemented, uploadPlaceholderResponse{
Message: "upload storage is not implemented yet; backend base is ready for the upload pipeline",
MaxUploadSize: a.uploadService.MaxUploadSizeLabel(),
})
}