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
11 changes: 9 additions & 2 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ name: Docker Build (Reusable)

on:
workflow_call:
secrets:
DOCKERHUB_USERNAME:
description: Docker Hub username for docker/login-action (required when inputs.push is true)
required: false
DOCKERHUB_TOKEN:
description: Docker Hub token/password for docker/login-action (required when inputs.push is true)
required: false
inputs:
image:
description: Fully qualified image name, e.g. ghcr.io/org/repo/service
Expand Down Expand Up @@ -80,8 +87,8 @@ jobs:
- name: Build${{ inputs.push && ' & push' || '' }}
uses: docker/build-push-action@v6
with:
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }}
context: ${{ github.workspace }}/${{ inputs.context }}
file: ${{ github.workspace }}/${{ inputs.dockerfile }}
platforms: linux/amd64
push: ${{ inputs.push }}
tags: ${{ steps.meta.outputs.tags }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docker-merrymaker-go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ concurrency:
jobs:
docker:
uses: ./.github/workflows/docker-build.yml
secrets: inherit
with:
image: target/merrymaker
tag_prefix: merrymaker-go-
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docker-puppeteer-worker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ concurrency:
jobs:
docker:
uses: ./.github/workflows/docker-build.yml
secrets: inherit
with:
image: target/merrymaker
tag_prefix: puppeteer-worker-
Expand Down
4 changes: 4 additions & 0 deletions services/merrymaker-go/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ bin
/merrymaker
/merrymaker-prod

# Ensure the Go main package directory is never excluded
!cmd/merrymaker/
!cmd/merrymaker/**

# Frontend dev-only deps
frontend/node_modules
frontend/vendor
Expand Down
3 changes: 2 additions & 1 deletion services/merrymaker-go/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ frontend/static/*
!frontend/static/.gitkeep
bin
node_modules
merrymaker
/merrymaker
/merrymaker-prod
docs/*.md

# Built CSS generated by bun from styles/index.css
Expand Down
125 changes: 125 additions & 0 deletions services/merrymaker-go/cmd/merrymaker/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package main

import (
"context"
"database/sql"
"errors"
"fmt"
"log/slog"
"os"

"github.com/redis/go-redis/v9"
"github.com/target/mmk-ui-api/config"
"github.com/target/mmk-ui-api/internal/bootstrap"
)

func main() {
ctx := context.Background()
logger := bootstrap.InitLogger()
if err := run(ctx, logger); err != nil {
logger.ErrorContext(ctx, "fatal error", "error", err)
os.Exit(1) //nolint:forbidigo // Main entrypoint should exit with non-zero status on fatal errors.
}
}

func run(ctx context.Context, logger *slog.Logger) error {
cfg, err := bootstrap.LoadConfig()
if err != nil {
return err
}

// Log startup info
logStartupInfo(ctx, logger, &cfg)

cfgPtr := &cfg

// Validate configuration
if err = bootstrap.ValidateServiceConfig(cfgPtr); err != nil {
return err
}

// Initialize infrastructure
db, redisClient, err := initInfrastructure(ctx, &cfg, logger)
if err != nil {
return err
}
defer func() {
if cerr := db.Close(); cerr != nil {
logger.ErrorContext(ctx, "close database failed", "error", cerr)
}
}()
if redisClient != nil {
defer func() {
if cerr := redisClient.Close(); cerr != nil {
logger.ErrorContext(ctx, "close redis failed", "error", cerr)
}
}()
}

// Run migrations if enabled
if cfg.Postgres.RunMigrationsOnStart {
if err = bootstrap.RunMigrations(ctx, db, logger); err != nil {
return err
}
} else {
logger.InfoContext(ctx, "skipping database migrations on startup", "reason", "disabled via config")
}

// Initialize and run services
services := bootstrap.NewServices(&bootstrap.ServiceDeps{
Config: cfgPtr,
DB: db,
RedisClient: redisClient,
Logger: logger,
})

return bootstrap.RunServicesWithShutdown(&bootstrap.ServiceOrchestrationConfig{
Config: cfgPtr,
Services: services,
DB: db,
RedisClient: redisClient,
Logger: logger,
})
}

func logStartupInfo(ctx context.Context, logger *slog.Logger, cfg *config.AppConfig) {
enabledServices := bootstrap.GetEnabledServices(cfg)
logger.InfoContext(ctx, "starting merrymaker service",
"db_host", cfg.Postgres.Host,
"db_port", cfg.Postgres.Port,
"db_name", cfg.Postgres.Name,
"enabled_services", enabledServices)
}

// initInfrastructure connects shared dependencies used by the service runtime.
//
//nolint:ireturn // returning redis.UniversalClient keeps sentinel/cluster support flexible.
func initInfrastructure(
ctx context.Context,
cfg *config.AppConfig,
logger *slog.Logger,
) (*sql.DB, redis.UniversalClient, error) {
db, err := bootstrap.ConnectDB(bootstrap.DatabaseConfig{
DBConfig: cfg.Postgres,
RedisConfig: cfg.Redis,
Logger: logger,
})
if err != nil {
return nil, nil, fmt.Errorf("connect db: %w", err)
}

redisClient, err := bootstrap.ConnectRedis(bootstrap.DatabaseConfig{
DBConfig: cfg.Postgres,
RedisConfig: cfg.Redis,
Logger: logger,
})
if err != nil {
if cerr := db.Close(); cerr != nil {
logger.ErrorContext(ctx, "close database after redis connect failure", "error", cerr)
return nil, nil, fmt.Errorf("connect redis: %w", errors.Join(err, fmt.Errorf("close database: %w", cerr)))
}
return nil, nil, fmt.Errorf("connect redis: %w", err)
}

return db, redisClient, nil
}
Loading