FEAT: Init Discovery Service
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
// Package api holds the request and response bodies of the HTTP API — the
|
||||
// wire contract, kept apart from the handlers that serve it. The swaggo
|
||||
// annotations on the handlers reference these types by name
|
||||
// (api.HealthResponse and so on), so renaming one changes the generated spec.
|
||||
package api
|
||||
|
||||
// HealthResponse is the body of GET /health.
|
||||
type HealthResponse struct {
|
||||
Status string `json:"status" example:"ok"`
|
||||
}
|
||||
|
||||
// ProblemDetails is an error body in the RFC 9457 "Problem Details for HTTP
|
||||
// APIs" format. Type stays "about:blank" — the value RFC 9457 defines for
|
||||
// problems with no dedicated documentation URI. Title is a short summary that
|
||||
// stays identical for every occurrence of the same problem, so clients can
|
||||
// branch on it; Detail explains this particular occurrence and is the only
|
||||
// member that varies with request data.
|
||||
type ProblemDetails struct {
|
||||
Type string `json:"type" example:"about:blank"`
|
||||
Title string `json:"title" example:"Something Went Wrong Loading The Catalogue"`
|
||||
Status int `json:"status" example:"500"`
|
||||
Detail string `json:"detail,omitempty" example:"The catalogue could not be read from the database. This is a server-side fault and the request was not processed; retrying in a few moments may succeed."`
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func CreateDBConnection(connectionString string) *sql.DB {
|
||||
sqlDB, err := sql.Open("postgres", connectionString)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("db: cannot open connection: %w", err))
|
||||
}
|
||||
|
||||
return sqlDB
|
||||
}
|
||||
|
||||
func CloseConnection(SQLDB *sql.DB) {
|
||||
err := SQLDB.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("Error Closing DB Connection: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertSuccessfulConnection(ctx context.Context, SQLDB *sql.DB) {
|
||||
if err := SQLDB.PingContext(ctx); err != nil {
|
||||
panic(fmt.Errorf("db: cannot connect: %w", err))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationsFS embed.FS
|
||||
|
||||
func Migrate(sqlDB *sql.DB) error {
|
||||
driver, err := postgres.WithInstance(sqlDB, &postgres.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("db: creating migration driver: %w", err)
|
||||
}
|
||||
|
||||
source, err := iofs.New(migrationsFS, "migrations")
|
||||
if err != nil {
|
||||
return fmt.Errorf("db: loading embedded migrations: %w", err)
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithInstance("iofs", source, "postgres", driver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("db: initializing migrator: %w", err)
|
||||
}
|
||||
|
||||
if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return fmt.Errorf("db: applying migrations: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Nothing to undo: 0001 creates no objects.
|
||||
SELECT 1;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Discovery's schema starts here. The service owns its own database — the
|
||||
-- `discovery` role and same-named database provisioned by newServiceDatabase
|
||||
-- in infrastructure/main.go — and shares no tables with cms, so nothing is
|
||||
-- created yet. The file exists because internal/db/migrate.go embeds
|
||||
-- migrations/*.sql: with no migration at all the package would not compile.
|
||||
SELECT 1;
|
||||
@@ -0,0 +1,7 @@
|
||||
// Package repositories holds every SQL statement the service runs. A
|
||||
// repository is a struct carrying the *sql.DB opened by db.CreateDBConnection
|
||||
// and is assigned to a package-level var in main, the way cms wires VideoRepo
|
||||
// and CatagoriesRepo, so handlers call through something substitutable.
|
||||
//
|
||||
// Empty until discovery has tables to read.
|
||||
package repositories
|
||||
@@ -0,0 +1,41 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"thamanyah/discovery/internal/api"
|
||||
)
|
||||
|
||||
// Health is the liveness probe the ALB target group polls.
|
||||
//
|
||||
// @Summary Health check
|
||||
// @Description Reports that the service is up and serving. Used as the ALB target group health check; it does not verify the database connection.
|
||||
// @Tags system
|
||||
// @Produce json
|
||||
// @Success 200 {object} api.HealthResponse
|
||||
// @Router /health [get]
|
||||
func Health(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, api.HealthResponse{Status: "ok"})
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, body any) {
|
||||
writeJSONContent(w, "application/json", status, body)
|
||||
}
|
||||
|
||||
func writeProblem(w http.ResponseWriter, status int, title, detail string) {
|
||||
writeJSONContent(w, "application/problem+json", status, api.ProblemDetails{
|
||||
Type: "about:blank",
|
||||
Title: title,
|
||||
Status: status,
|
||||
Detail: detail,
|
||||
})
|
||||
}
|
||||
|
||||
func writeJSONContent(w http.ResponseWriter, contentType string, status int, body any) {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.WriteHeader(status)
|
||||
if err := json.NewEncoder(w).Encode(body); err != nil {
|
||||
log.Printf("Something Went Wrong Writing The Response: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Package models holds the domain types the rest of the service passes
|
||||
// around, the way cms holds Video and Category: plain Go structs with no JSON
|
||||
// tags and no SQL, so that renaming a field here never moves the published
|
||||
// API (that lives in internal/api) and never rewrites a query (that lives in
|
||||
// internal/db/repositories).
|
||||
//
|
||||
// Empty until discovery has a domain to model.
|
||||
package models
|
||||
Reference in New Issue
Block a user