Skip to content
Merged
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
20 changes: 9 additions & 11 deletions database/seeder/importer/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,18 @@ import (

const defaultSQLDumpPath = "./storage/sql/dump.sql"

var (
environment *env.Environment
sentryHub *portal.Sentry
)
func main() {
defer sentry.Flush(2 * time.Second)

func init() {
secrets := kernel.Ignite("./.env", portal.GetDefaultValidator())
validate := portal.GetDefaultValidator()
environment, err := kernel.Ignite("./.env", validate)
if err != nil {
cli.Errorln(fmt.Errorf("ignite environment: %w", err).Error())
os.Exit(1)
}

environment = secrets
sentryHub = kernel.NewSentry(environment)
}
sentryHub := kernel.NewSentry(environment)

func main() {
if err := run(defaultSQLDumpPath, environment, sentryHub); err != nil {
cli.Errorln(err.Error())
os.Exit(1)
Expand All @@ -52,7 +51,6 @@ func run(filePath string, environment *env.Environment, sentryHub *portal.Sentry
dbConnection := kernel.NewDbConnection(environment)
logs := kernel.NewLogs(environment)

defer sentry.Flush(2 * time.Second)
defer logs.Close()
defer dbConnection.Close()
defer kernel.RecoverWithSentry(sentryHub)
Expand Down
67 changes: 42 additions & 25 deletions database/seeder/main.go
Original file line number Diff line number Diff line change
@@ -1,53 +1,70 @@
package main

import (
"errors"
"fmt"
"os"
"sync"
"time"

"github.com/getsentry/sentry-go"
"github.com/oullin/database"
"github.com/oullin/database/seeder/seeds"
"github.com/oullin/metal/env"
"github.com/oullin/metal/kernel"
"github.com/oullin/pkg/cli"
"github.com/oullin/pkg/portal"
)

var sentryHub *portal.Sentry
var environment *env.Environment

func init() {
secrets := kernel.Ignite("./.env", portal.GetDefaultValidator())
func main() {
defer sentry.Flush(2 * time.Second)

environment = secrets
sentryHub = kernel.NewSentry(environment)
if err := run(); err != nil {
sentry.CurrentHub().CaptureException(err)
cli.Errorln(err.Error())
sentry.Flush(2 * time.Second)
os.Exit(1)
}
}

func main() {
func run() error {
cli.ClearScreen()

validate := portal.GetDefaultValidator()
environment, err := kernel.Ignite("./.env", validate)
if err != nil {
return fmt.Errorf("ignite environment: %w", err)
}

hub := kernel.NewSentry(environment)

defer kernel.RecoverWithSentry(hub)

dbConnection := kernel.NewDbConnection(environment)
logs := kernel.NewLogs(environment)
if dbConnection == nil {
return errors.New("database connection is nil")
}
defer dbConnection.Close()

defer sentry.Flush(2 * time.Second)
logs := kernel.NewLogs(environment)
if logs == nil {
return errors.New("logs driver is nil")
}
defer logs.Close()
defer (*dbConnection).Close()
defer kernel.RecoverWithSentry(sentryHub)

// [1] --- Create the Seeder Runner.
seeder := seeds.NewSeeder(dbConnection, environment)
if seeder == nil {
return errors.New("seeder is nil")
}

// [2] --- Truncate the db.
if err := seeder.TruncateDB(); err != nil {
panic(err)
} else {
cli.Successln("db Truncated successfully ...")
time.Sleep(2 * time.Second)
return fmt.Errorf("truncate database: %w", err)
}

// [3] --- Seed users and posts sequentially because the below seeders depend on them.
UserA, UserB := seeder.SeedUsers()
posts := seeder.SeedPosts(UserA, UserB)
cli.Successln("db Truncated successfully ...")
time.Sleep(2 * time.Second)

userA, userB := seeder.SeedUsers()
posts := seeder.SeedPosts(userA, userB)

categoriesChan := make(chan []database.Category)
tagsChan := make(chan []database.Tag)
Expand All @@ -66,11 +83,9 @@ func main() {
tagsChan <- seeder.SeedTags()
}()

// [4] Use channels to concurrently seed categories and tags since they are main dependencies.
categories := <-categoriesChan
tags := <-tagsChan

// [5] Use a WaitGroup to run independent seeding tasks concurrently.
var wg sync.WaitGroup
wg.Add(6)

Expand Down Expand Up @@ -106,7 +121,7 @@ func main() {
defer wg.Done()

cli.Warningln("Seeding views ...")
seeder.SeedPostViews(posts, UserA, UserB)
seeder.SeedPostViews(posts, userA, userB)
}()

go func() {
Expand All @@ -122,4 +137,6 @@ func main() {
wg.Wait()

cli.Magentaln("db seeded as expected ....")

return nil
}
4 changes: 1 addition & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,14 @@ services:

# A dedicated service for running one-off Go commands
api-runner:
container_name: runner
restart: no
env_file:
- ./.env
build:
context: .
dockerfile: ./docker/dockerfile-api
target: builder
image: api-api-runner
volumes:
- .:/app
- go_mod_cache:/go/pkg/mod
Expand Down Expand Up @@ -116,7 +116,6 @@ services:
user: root
security_opt:
- apparmor:unconfined
image: api-api
env_file:
- .env
volumes:
Expand All @@ -137,7 +136,6 @@ services:
- APP_GROUP=${ENV_DOCKER_USER_GROUP}
- APP_DIR=/app
- BINARY_NAME=oullin_api
container_name: oullin_api
restart: unless-stopped
secrets:
- pg_username
Expand Down
2 changes: 1 addition & 1 deletion docker/dockerfile-api
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ ARG BINARY_NAME=oullin_api
ARG GO_VERSION=1.25.3
ARG GO_IMAGE_VARIANT=alpine3.22
ARG GO_TOOLCHAIN=go1.25.3
ARG GOLANG_ALPINE_DIGEST=sha256:c3dc5d5e8cf34ccb2172fb8d1aa399aa13cd8b60d27bba891d18e3b436a0c5f6
ARG GOLANG_ALPINE_DIGEST=sha256:aee43c3ccbf24fdffb7295693b6e33b21e01baec1b2a55acc351fde345e9ec34
ARG ALPINE_VERSION=3.22
ARG ALPINE_DIGEST=sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1

Expand Down
104 changes: 47 additions & 57 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,95 +1,85 @@
package main

import (
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"time"

"github.com/getsentry/sentry-go"
_ "github.com/lib/pq"
"github.com/oullin/metal/kernel"
"github.com/oullin/pkg/endpoint"
"github.com/oullin/pkg/portal"
"github.com/rs/cors"
)

var app *kernel.App
func main() {
defer sentry.Flush(2 * time.Second)

func init() {
validate := portal.GetDefaultValidator()
secrets := kernel.Ignite("./.env", validate)
application, err := kernel.NewApp(secrets, validate)
if err := run(); err != nil {
sentry.CurrentHub().CaptureException(err)
if !sentry.Flush(2 * time.Second) {
slog.Warn("sentry flush timed out after capture")
}
slog.Error("server exited with error", "error", err)
os.Exit(1)
}
}

func run() error {
validate := portal.GetDefaultValidator()
secrets, err := kernel.Ignite("./.env", validate)
if err != nil {
panic(fmt.Sprintf("init: Error creating application: %s", err))
return fmt.Errorf("ignite environment: %w", err)
}

app = application
}
app, err := kernel.NewApp(secrets, validate)
if err != nil {
return fmt.Errorf("create application: %w", err)
}

func main() {
defer sentry.Flush(2 * time.Second)
defer app.CloseDB()
defer app.CloseLogs()
defer app.Recover()

app.Boot()

// --- Testing
if err := app.GetDB().Ping(); err != nil {
slog.Error("database ping failed", "error", err)
return fmt.Errorf("database ping failed: %w", err)
}
slog.Info("Starting new server on :" + app.GetEnv().Network.HttpPort)
// ---

if err := http.ListenAndServe(app.GetEnv().Network.GetHostURL(), serverHandler()); err != nil {
sentry.CurrentHub().CaptureException(err)
slog.Error("Error starting server", "error", err)
panic("Error starting server." + err.Error())
env := app.GetEnv()
if env == nil {
return errors.New("application environment is nil")
}
}
addr := env.Network.GetHostURL()

func serverHandler() http.Handler {
mux := app.GetMux()
if mux == nil {
return http.NotFoundHandler()
var wrap func(http.Handler) http.Handler
if sentry := app.GetSentry(); sentry != nil && sentry.Handler != nil {
wrap = sentry.Handler.Handle
}

var handler http.Handler = mux

if !app.IsProduction() { // Caddy handles CORS.
localhost := app.GetEnv().Network.GetHostURL()

headers := []string{
"Accept",
"Authorization",
"Content-Type",
"X-CSRF-Token",
"User-Agent",
"X-API-Key",
"X-API-Username",
"X-API-Signature",
"X-API-Timestamp",
"X-API-Nonce",
"X-Request-ID",
"If-None-Match",
"X-API-Intended-Origin", //new
}

c := cors.New(cors.Options{
AllowedOrigins: []string{localhost, "http://localhost:5173"},
AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodOptions},
AllowedHeaders: headers,
AllowCredentials: true,
Debug: true,
})

handler = c.Handler(handler)
handler := endpoint.NewServerHandler(endpoint.ServerHandlerConfig{
Mux: app.GetMux(),
IsProduction: app.IsProduction(),
DevHost: addr,
Wrap: wrap,
})

server := &http.Server{
Addr: addr,
Handler: handler,
ReadTimeout: 15 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}

if sentry := app.GetSentry(); sentry != nil && sentry.Handler != nil {
handler = sentry.Handler.Handle(handler)
if err := endpoint.RunServer(addr, server); err != nil {
return fmt.Errorf("serve http: %w", err)
}

return handler
return nil
}
Loading
Loading