131 lines
4.0 KiB
Go
131 lines
4.0 KiB
Go
package dev
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"database/sql"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/example/sndit/backend/internal/auth"
|
|
"github.com/example/sndit/backend/internal/storage"
|
|
)
|
|
|
|
type Config struct {
|
|
DBDriver string
|
|
StorageEndpoint string
|
|
StorageBucket string
|
|
StorageUseSSL bool
|
|
CORSOrigins string
|
|
CookieSecure bool
|
|
}
|
|
|
|
type Handler struct {
|
|
db *sql.DB
|
|
storage storage.Storage
|
|
auth *auth.Service
|
|
config Config
|
|
}
|
|
|
|
func NewHandler(db *sql.DB, objectStorage storage.Storage, authService *auth.Service, config Config) *Handler {
|
|
return &Handler{db: db, storage: objectStorage, auth: authService, config: config}
|
|
}
|
|
|
|
func (h *Handler) RegisterRoutes(mux *http.ServeMux, require func(http.Handler) http.Handler) {
|
|
mux.Handle("GET /api/dev/diagnostics", require(http.HandlerFunc(h.Diagnostics)))
|
|
mux.Handle("POST /api/dev/storage-check", require(http.HandlerFunc(h.StorageCheck)))
|
|
}
|
|
|
|
func (h *Handler) Diagnostics(w http.ResponseWriter, r *http.Request) {
|
|
user, _ := auth.UserFromContext(r.Context())
|
|
databaseError := ""
|
|
databaseContext, cancel := context.WithTimeout(r.Context(), 2*time.Second)
|
|
if err := h.db.PingContext(databaseContext); err != nil {
|
|
databaseError = err.Error()
|
|
}
|
|
cancel()
|
|
|
|
storageError := ""
|
|
storageContext, storageCancel := context.WithTimeout(r.Context(), 3*time.Second)
|
|
if err := h.storage.EnsureBucket(storageContext); err != nil {
|
|
storageError = err.Error()
|
|
}
|
|
storageCancel()
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"environment": "development",
|
|
"now": time.Now().UTC().Format(time.RFC3339),
|
|
"user": map[string]string{
|
|
"id": user.ID.String(),
|
|
"email": user.Email,
|
|
"name": user.Name,
|
|
},
|
|
"database": map[string]any{
|
|
"driver": h.config.DBDriver,
|
|
"connected": databaseError == "",
|
|
"error": databaseError,
|
|
},
|
|
"storage": map[string]any{
|
|
"provider": "MinIO / S3-compatible",
|
|
"endpoint": h.config.StorageEndpoint,
|
|
"bucket": h.config.StorageBucket,
|
|
"secure": h.config.StorageUseSSL,
|
|
"reachable": storageError == "",
|
|
"error": storageError,
|
|
},
|
|
"http": map[string]any{
|
|
"corsOrigins": splitOrigins(h.config.CORSOrigins),
|
|
"cookieSecure": h.config.CookieSecure,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (h *Handler) StorageCheck(w http.ResponseWriter, r *http.Request) {
|
|
if err := h.storage.EnsureBucket(r.Context()); err != nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, map[string]any{"ok": false, "step": "bucket", "error": err.Error()})
|
|
return
|
|
}
|
|
var randomBytes [12]byte
|
|
if _, err := rand.Read(randomBytes[:]); err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]any{"ok": false, "step": "random", "error": err.Error()})
|
|
return
|
|
}
|
|
key := fmt.Sprintf("dev/diagnostics/%s.txt", hex.EncodeToString(randomBytes[:]))
|
|
contents := "northline storage check " + time.Now().UTC().Format(time.RFC3339Nano)
|
|
if err := h.storage.Put(r.Context(), key, strings.NewReader(contents), int64(len(contents)), "text/plain"); err != nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, map[string]any{"ok": false, "step": "put", "error": err.Error()})
|
|
return
|
|
}
|
|
info, statErr := h.storage.Stat(r.Context(), key)
|
|
deleteErr := h.storage.Delete(r.Context(), key)
|
|
if statErr != nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, map[string]any{"ok": false, "step": "stat", "error": statErr.Error()})
|
|
return
|
|
}
|
|
if deleteErr != nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, map[string]any{"ok": false, "step": "delete", "error": deleteErr.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "bytes": info.Size, "contentType": info.ContentType})
|
|
}
|
|
|
|
func splitOrigins(value string) []string {
|
|
result := make([]string, 0)
|
|
for _, origin := range strings.Split(value, ",") {
|
|
if trimmed := strings.TrimSpace(origin); trimmed != "" {
|
|
result = append(result, trimmed)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, value any) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(value)
|
|
}
|