Files
vikingowl 580b9d5e3c feat: add admin panel, market submissions, and email notifications
- Admin CRUD endpoints for markets with role-based middleware
- Anonymous market submission with Cloudflare Turnstile verification
- SMTP email notifications on new submissions (LogSender fallback)
- Market status workflow (pending/approved/rejected) with admin notes
- Nullable location column for submissions without coordinates
- CLI tool for promoting users to admin role
- Slug generation package extracted from seed
- Rate limiting on submission endpoint (3/hour per IP)
- Mailpit added to docker-compose for local email testing
2026-02-27 11:03:44 +01:00

77 lines
1.6 KiB
Go

package main
import (
"context"
"fmt"
"log/slog"
"os"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"marktvogt.de/backend/internal/config"
"marktvogt.de/backend/internal/database"
)
func main() {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
})))
if len(os.Args) < 3 {
fmt.Fprintln(os.Stderr, "usage: admin promote <email>")
os.Exit(1)
}
command := os.Args[1]
if command != "promote" {
fmt.Fprintf(os.Stderr, "unknown command: %s\n", command)
os.Exit(1)
}
email := os.Args[2]
if err := run(email); err != nil {
slog.Error("admin command failed", "error", err)
os.Exit(1)
}
}
func run(email string) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
pool, err := connectDB(ctx)
if err != nil {
return err
}
defer pool.Close()
tag, err := pool.Exec(ctx, "UPDATE users SET role = 'admin' WHERE email = $1 AND deleted_at IS NULL", email)
if err != nil {
return fmt.Errorf("updating user role: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("no active user found with email %q", email)
}
slog.Info("promoted user to admin", "email", email)
return nil
}
func connectDB(ctx context.Context) (*pgxpool.Pool, error) {
if dbURL := os.Getenv("DATABASE_URL"); dbURL != "" {
pool, err := pgxpool.New(ctx, dbURL)
if err != nil {
return nil, fmt.Errorf("connect via DATABASE_URL: %w", err)
}
return pool, nil
}
cfg, err := config.Load()
if err != nil {
return nil, err
}
return database.NewPostgres(ctx, cfg.DB)
}