Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions cmd/acmcsuf-api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import (
"context"
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"

"github.com/acmcsufoss/api.acmcsuf.com/internal/api"
"github.com/acmcsufoss/api.acmcsuf.com/internal/api/logging"
)

var Version = "dev"
Expand All @@ -24,20 +24,21 @@ func main() {
os.Exit(0)
}

logger := logging.NewLogger()

// =================== Goroutine management ===================
log.SetPrefix("[SERVER] ")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-signalChan
log.Println("\x1b[32mShutting down the server...\x1b[0m")
logger.Info("\x1b[32mShutting down the server...\x1b[0m")
// when cancel is called, it sends a "done" signal to ctx
cancel()
}()

// =================== Start the server ===================
api.Run(ctx)
api.Run(ctx, logger)
}
1 change: 0 additions & 1 deletion internal/api/handlers/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ func (h *EventsHandler) UpdateEvent(c *gin.Context) {
var params dbmodels.UpdateEventParams
id := c.Param("id")

fmt.Println("UPDATE:", id)
if err := c.ShouldBindJSON(&params); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid request body. " + err.Error(),
Expand Down
43 changes: 43 additions & 0 deletions internal/api/logging/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package logging

import (
"fmt"
"log/slog"
"os"
"time"

"github.com/gin-gonic/gin"
)

// NewLogger returns the default server logger.
func NewLogger() *slog.Logger {
opts := &slog.HandlerOptions{AddSource: true}
return slog.New(slog.NewJSONHandler(os.Stdout, opts)).With("component", "SERVER")
}

// Fatal logs a message at Error level then exits with code 1.
func Fatal(logger *slog.Logger, msg string, args ...any) {
logger.Error(msg, args...)
os.Exit(1)
}

// Fatal logs a formatted message at Error level then exits with code 1.
func Fatalf(logger *slog.Logger, format string, args ...any) {
logger.Error("fatal", "msg", fmt.Sprintf(format, args...))
os.Exit(1)
}

// RequestLogger returns a gin middleware that logs each request using the provided logger.
func RequestLogger(logger *slog.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
logger.Info("request",
"method", c.Request.Method,
"path", c.Request.URL.Path,
"status", c.Writer.Status(),
"latency_ms", time.Since(start).Milliseconds(),
"client_ip", c.ClientIP(),
)
}
}
21 changes: 11 additions & 10 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"context"
"database/sql"
"fmt"
"log"
"log/slog"

"github.com/gin-gonic/gin"
"github.com/golang-migrate/migrate/v4"
Expand All @@ -16,46 +16,47 @@ import (

"github.com/acmcsufoss/api.acmcsuf.com/internal/api/config"
"github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels"
"github.com/acmcsufoss/api.acmcsuf.com/internal/api/logging"
mw "github.com/acmcsufoss/api.acmcsuf.com/internal/api/middleware"
"github.com/acmcsufoss/api.acmcsuf.com/internal/api/routes"
"github.com/acmcsufoss/api.acmcsuf.com/internal/api/services"
)

// Run initializes the database, services, and router, then starts the server.
// It waits for the context to be canceled to initiate a graceful shutdown.
func Run(ctx context.Context) {
func Run(ctx context.Context, logger *slog.Logger) {
cfg := config.Load()

db, closer, err := NewDB(ctx, cfg.DatabaseURL)
if err != nil {
log.Fatal(err)
logging.Fatal(logger, "could not open database", "error", err)
}
defer closer()

// Apply db migrations
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
if err != nil {
log.Fatalf("could not create sqlite3 driver: %v\n", err)
logging.Fatal(logger, "could not create sqlite3 driver", "error", err)
}
m, err := migrate.NewWithDatabaseInstance(
"file://sql/migrations",
"sqlite3",
driver,
)
if err != nil {
log.Fatalf("could not create migration instance: %v\n", err)
logging.Fatal(logger, "could not create migration instance", "error", err)
}
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
log.Fatalf("could not run db migrations: %v\n", err)
logging.Fatal(logger, "could not run db migrations", "error", err)
}

// Now we init services & gin router, and then start the server
queries := dbmodels.New(db)
eventsService := services.NewEventsService(queries)
announcementService := services.NewAnnouncementService(queries)
boardService := services.NewBoardService(queries, db)
router := gin.Default()
router.Use(mw.Cors(), mw.Ratelimiter())
router := gin.New()
router.Use(logging.RequestLogger(logger), gin.Recovery(), mw.Cors(), mw.Ratelimiter())

router.SetTrustedProxies(cfg.TrustedProxies)
routes.SetupRoot(router)
Expand All @@ -69,14 +70,14 @@ func Run(ctx context.Context) {
}

if err := router.Run(serverAddr); err != nil {
log.Fatalf("Failed to start server: %v", err)
logging.Fatal(logger, "failed to start server", "error", err)
}
}()

// This is a blocking call that prevents the function from finishing until the signal
// is received.
<-ctx.Done()
log.Println("\x1b[32mServer shut down.\x1b[0m")
logger.Info("server shut down")
}

func NewDB(ctx context.Context, url string) (*sql.DB, func(), error) {
Expand Down
15 changes: 2 additions & 13 deletions internal/api/services/announcement.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"

"github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels"
"log"
)

type AnnouncementServicer interface {
Expand Down Expand Up @@ -64,19 +63,9 @@ func (s *AnnouncementService) List(ctx context.Context,
func (s *AnnouncementService) Update(ctx context.Context, uuid string,
params dbmodels.UpdateAnnouncementParams) error {

err := s.q.UpdateAnnouncement(ctx, params)
if err != nil {
log.Printf("Error updating announcement with UUID %s: %v", uuid, err)
return err
}
return nil
return s.q.UpdateAnnouncement(ctx, params)
}

func (s *AnnouncementService) Delete(ctx context.Context, uuid string) error {
err := s.q.DeleteAnnouncement(ctx, uuid)
if err != nil {
log.Printf("Error deleting announcement with UUID %s: %v", uuid, err)
return err
}
return nil
return s.q.DeleteAnnouncement(ctx, uuid)
}
Loading