FEAT: Init Discovery Service
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
name: Build, Push and Deploy Discovery
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "discovery/**"
|
||||||
|
- ".gitea/workflows/discovery-deploy.yml"
|
||||||
|
|
||||||
|
env:
|
||||||
|
AWS_REGION: us-east-1
|
||||||
|
ECR_REPOSITORY: discovery
|
||||||
|
ECS_CLUSTER_ARN: arn:aws:ecs:us-east-1:887083795404:cluster/app-cluster-384acc4
|
||||||
|
DISCOVERY_ECS_SERVICE_ARN: arn:aws:ecs:us-east-1:887083795404:service/app-cluster-384acc4/discovery-service-52aa4f6
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-push-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
|
with:
|
||||||
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
aws-region: ${{ env.AWS_REGION }}
|
||||||
|
|
||||||
|
- name: Log in to Amazon ECR
|
||||||
|
id: login-ecr
|
||||||
|
uses: aws-actions/amazon-ecr-login@v2
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./discovery
|
||||||
|
file: ./discovery/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:latest
|
||||||
|
${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ gitea.sha }}
|
||||||
|
|
||||||
|
- name: Install AWS CLI
|
||||||
|
run: |
|
||||||
|
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||||
|
unzip -q awscliv2.zip
|
||||||
|
sudo ./aws/install
|
||||||
|
rm -rf awscliv2.zip aws
|
||||||
|
|
||||||
|
- name: Deploy to ECS
|
||||||
|
run: |
|
||||||
|
aws ecs update-service \
|
||||||
|
--cluster "${{ env.ECS_CLUSTER_ARN }}" \
|
||||||
|
--service "${{ env.DISCOVERY_ECS_SERVICE_ARN }}" \
|
||||||
|
--force-new-deployment \
|
||||||
|
--region "${{ env.AWS_REGION }}"
|
||||||
@@ -7,15 +7,22 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── cms/ Go service: video ingestion/CMS (implemented)
|
├── cms/ Go service: video ingestion/CMS (implemented)
|
||||||
├── discovery/ Go service: empty placeholder, no code yet
|
├── discovery/ Go service: catalogue discovery/read side (skeleton)
|
||||||
├── infrastructure/ Pulumi (Go) program provisioning all AWS resources
|
├── infrastructure/ Pulumi (Go) program provisioning all AWS resources
|
||||||
├── tests/ Go module: Gherkin/gobdd scenarios run against a live cms
|
├── tests/ Go module: Gherkin/gobdd scenarios run against a live cms
|
||||||
└── .gitea/workflows/ Gitea Actions CI/CD pipelines
|
└── .gitea/workflows/ Gitea Actions CI/CD pipelines
|
||||||
```
|
```
|
||||||
|
|
||||||
`discovery` is provisioned in infra (its own ECR repo, ECS service, ALB,
|
`discovery` is a skeleton, not a feature: it mirrors `cms`'s layout, boot
|
||||||
Postgres database/role) but has no application code yet — don't assume it's
|
sequence and Postgres wiring, and serves `GET /health` plus the Swagger UI, but
|
||||||
deployable.
|
it has no domain yet — `internal/models` and `internal/db/repositories` are
|
||||||
|
package doc comments, and migration `0001_init` creates no tables (it exists
|
||||||
|
only because `internal/db/migrate.go` embeds `migrations/*.sql`, which will not
|
||||||
|
compile against an empty directory). It has no `internal/services` and no
|
||||||
|
`internal/consumers`: those are AWS-only in `cms`, and `discovery` has no task
|
||||||
|
role, so it reaches nothing but its own database. It is deployable — its ECR
|
||||||
|
repo, ECS service and ALB have always existed in infra, and it now has an image
|
||||||
|
to put in them.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
@@ -29,6 +36,28 @@ go build ./...
|
|||||||
go vet ./...
|
go vet ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### discovery (Go 1.25, module `thamanyah/discovery`)
|
||||||
|
|
||||||
|
Same commands as `cms`, and the same two entry paths (`runServer`,
|
||||||
|
`runMigrate`) in `discovery/main.go` — only the port and the dependencies
|
||||||
|
differ. Note the module path carries no `/v2`: unlike `cms`, this module has
|
||||||
|
never had a v1.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd discovery
|
||||||
|
go run . # serves on :8080 (requires DB_* env vars only — no S3/MediaConvert)
|
||||||
|
go run . migrate # applies pending DB migrations, then exits (no HTTP server)
|
||||||
|
go build ./...
|
||||||
|
go vet ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Its OpenAPI spec is generated into `discovery/docs` by the same `swag init`
|
||||||
|
invocation as `cms`, run from `discovery/`. In `docker-compose.yml` it is a
|
||||||
|
service of its own on `127.0.0.1:8080`, wired to the `discovery` database and
|
||||||
|
role LocalStack provisions — and, like `cms`, started in server mode only, so a
|
||||||
|
freshly created local database needs `docker exec discovery ./discovery migrate`
|
||||||
|
once.
|
||||||
|
|
||||||
`cms`, `discovery` and `infrastructure` hold no test files of their own. The
|
`cms`, `discovery` and `infrastructure` hold no test files of their own. The
|
||||||
only tests in the repo are the black-box BDD scenarios in `tests/` — see below.
|
only tests in the repo are the black-box BDD scenarios in `tests/` — see below.
|
||||||
|
|
||||||
@@ -246,7 +275,7 @@ on boot if any required var is empty.
|
|||||||
service's ECR repo, CloudWatch log group, task definition, ECS service,
|
service's ECR repo, CloudWatch log group, task definition, ECS service,
|
||||||
and ALB. `taskRole` is optional (nil = no AWS identity beyond the shared
|
and ALB. `taskRole` is optional (nil = no AWS identity beyond the shared
|
||||||
execution role); `extraEnv` appends container env vars beyond the DB_* set;
|
execution role); `extraEnv` appends container env vars beyond the DB_* set;
|
||||||
`runMigrations` (true for `cms`, false for `discovery`) adds a second,
|
`runMigrations` (true for both services) adds a second,
|
||||||
non-essential `<name>-migrate` container to the task — same image,
|
non-essential `<name>-migrate` container to the task — same image,
|
||||||
`command: ["migrate"]` — with the main container's `dependsOn` set to
|
`command: ["migrate"]` — with the main container's `dependsOn` set to
|
||||||
`condition: "COMPLETE"` on it. This is ECS's container-dependency
|
`condition: "COMPLETE"` on it. This is ECS's container-dependency
|
||||||
@@ -295,6 +324,11 @@ on boot if any required var is empty.
|
|||||||
a third-party action), then `aws ecs update-service --force-new-deployment`.
|
a third-party action), then `aws ecs update-service --force-new-deployment`.
|
||||||
Cluster/service ARNs come from `pulumi stack output` and are set as repo
|
Cluster/service ARNs come from `pulumi stack output` and are set as repo
|
||||||
*variables* (not secrets — ARNs aren't sensitive).
|
*variables* (not secrets — ARNs aren't sensitive).
|
||||||
|
- **`discovery-deploy.yml`**: the same pipeline for `discovery`, on pushes
|
||||||
|
touching `discovery/**`. One difference: the ECS service ARN is read from
|
||||||
|
the `DISCOVERY_ECS_SERVICE_ARN` repo variable rather than pinned in the
|
||||||
|
workflow, so it has to be set (from `pulumi stack output
|
||||||
|
discoveryServiceArn`) before the first deploy can succeed.
|
||||||
- **`infrastructure-deploy.yml`**: push to `main` touching
|
- **`infrastructure-deploy.yml`**: push to `main` touching
|
||||||
`infrastructure/**`. Runs `pulumi up` using a separate, broader AWS
|
`infrastructure/**`. Runs `pulumi up` using a separate, broader AWS
|
||||||
credential (`PULUMI_AWS_ACCESS_KEY_ID`/`SECRET`) than `gitea-ci-user`,
|
credential (`PULUMI_AWS_ACCESS_KEY_ID`/`SECRET`) than `gitea-ci-user`,
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Binaries
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool
|
||||||
|
*.out
|
||||||
|
coverage.txt
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
|
# Env files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
/bin/
|
||||||
|
/dist/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
FROM golang:1.25-alpine AS builder
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /out/discovery .
|
||||||
|
|
||||||
|
FROM alpine:3.20
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /out/discovery ./discovery
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["./discovery"]
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||||
|
package docs
|
||||||
|
|
||||||
|
import "github.com/swaggo/swag"
|
||||||
|
|
||||||
|
const docTemplate = `{
|
||||||
|
"schemes": {{ marshal .Schemes }},
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "{{escape .Description}}",
|
||||||
|
"title": "{{.Title}}",
|
||||||
|
"contact": {},
|
||||||
|
"version": "{{.Version}}"
|
||||||
|
},
|
||||||
|
"host": "{{.Host}}",
|
||||||
|
"basePath": "{{.BasePath}}",
|
||||||
|
"paths": {
|
||||||
|
"/health": {
|
||||||
|
"get": {
|
||||||
|
"description": "Reports that the service is up and serving. Used as the ALB target group health check; it does not verify the database connection.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"system"
|
||||||
|
],
|
||||||
|
"summary": "Health check",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.HealthResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"api.HealthResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||||
|
var SwaggerInfo = &swag.Spec{
|
||||||
|
Version: "1.0",
|
||||||
|
Host: "",
|
||||||
|
BasePath: "/",
|
||||||
|
Schemes: []string{},
|
||||||
|
Title: "Thamanyah Discovery API",
|
||||||
|
Description: "JSON API for browsing the Thamanyah catalogue. Read-side counterpart to the CMS, which is what ingests videos.",
|
||||||
|
InfoInstanceName: "swagger",
|
||||||
|
SwaggerTemplate: docTemplate,
|
||||||
|
LeftDelim: "{{",
|
||||||
|
RightDelim: "}}",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "JSON API for browsing the Thamanyah catalogue. Read-side counterpart to the CMS, which is what ingests videos.",
|
||||||
|
"title": "Thamanyah Discovery API",
|
||||||
|
"contact": {},
|
||||||
|
"version": "1.0"
|
||||||
|
},
|
||||||
|
"basePath": "/",
|
||||||
|
"paths": {
|
||||||
|
"/health": {
|
||||||
|
"get": {
|
||||||
|
"description": "Reports that the service is up and serving. Used as the ALB target group health check; it does not verify the database connection.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"system"
|
||||||
|
],
|
||||||
|
"summary": "Health check",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.HealthResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"api.HealthResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
basePath: /
|
||||||
|
definitions:
|
||||||
|
api.HealthResponse:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
example: ok
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
info:
|
||||||
|
contact: {}
|
||||||
|
description: JSON API for browsing the Thamanyah catalogue. Read-side counterpart
|
||||||
|
to the CMS, which is what ingests videos.
|
||||||
|
title: Thamanyah Discovery API
|
||||||
|
version: "1.0"
|
||||||
|
paths:
|
||||||
|
/health:
|
||||||
|
get:
|
||||||
|
description: Reports that the service is up and serving. Used as the ALB target
|
||||||
|
group health check; it does not verify the database connection.
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.HealthResponse'
|
||||||
|
summary: Health check
|
||||||
|
tags:
|
||||||
|
- system
|
||||||
|
swagger: "2.0"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
module thamanyah/discovery
|
||||||
|
|
||||||
|
go 1.25.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||||
|
github.com/lib/pq v1.12.3
|
||||||
|
github.com/swaggo/http-swagger/v2 v2.0.2
|
||||||
|
github.com/swaggo/swag v1.16.6
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||||
|
github.com/go-openapi/spec v0.20.6 // indirect
|
||||||
|
github.com/go-openapi/swag v0.19.15 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
|
github.com/swaggo/files/v2 v2.0.0 // indirect
|
||||||
|
golang.org/x/mod v0.29.0 // indirect
|
||||||
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
|
golang.org/x/tools v0.38.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
|
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
|
||||||
|
github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
||||||
|
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||||
|
github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
|
||||||
|
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||||
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
|
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||||
|
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||||
|
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||||
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
|
||||||
|
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
|
||||||
|
github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg=
|
||||||
|
github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ=
|
||||||
|
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||||
|
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||||
|
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||||
|
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||||
|
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||||
|
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||||
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
|
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"thamanyah/discovery/internal/db"
|
||||||
|
"thamanyah/discovery/internal/handlers"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
httpSwagger "github.com/swaggo/http-swagger/v2"
|
||||||
|
|
||||||
|
_ "thamanyah/discovery/docs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @title Thamanyah Discovery API
|
||||||
|
// @version 1.0
|
||||||
|
// @description JSON API for browsing the Thamanyah catalogue. Read-side counterpart to the CMS, which is what ingests videos.
|
||||||
|
// @BasePath /
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == "migrate" {
|
||||||
|
runMigrate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMigrate() {
|
||||||
|
concreteDBClient := db.CreateDBConnection(requireDBConnectionString())
|
||||||
|
defer db.CloseConnection(concreteDBClient)
|
||||||
|
if err := db.Migrate(concreteDBClient); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
log.Println("migrations applied successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runServer() {
|
||||||
|
concreteDBClient := db.CreateDBConnection(requireDBConnectionString())
|
||||||
|
defer db.CloseConnection(concreteDBClient)
|
||||||
|
db.AssertSuccessfulConnection(context.Background(), concreteDBClient)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("GET /health", handlers.Health)
|
||||||
|
|
||||||
|
// Swagger UI and the generated spec. The UI assets are embedded in the
|
||||||
|
// binary by swaggo/files, so this needs no static directory on disk.
|
||||||
|
mux.Handle("GET /swagger/", httpSwagger.WrapHandler)
|
||||||
|
|
||||||
|
addr := ":8080"
|
||||||
|
log.Printf("listening on %s", addr)
|
||||||
|
if err := http.ListenAndServe(addr, mux); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireDBConnectionString() string {
|
||||||
|
dbHost := os.Getenv("DB_HOST")
|
||||||
|
if dbHost == "" {
|
||||||
|
panic(fmt.Errorf("missing required env var: DB_HOST"))
|
||||||
|
}
|
||||||
|
|
||||||
|
dbPort := os.Getenv("DB_PORT")
|
||||||
|
if dbPort == "" {
|
||||||
|
panic(fmt.Errorf("missing required env var: DB_PORT"))
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUser := os.Getenv("DB_USER")
|
||||||
|
if dbUser == "" {
|
||||||
|
panic(fmt.Errorf("missing required env var: DB_USER"))
|
||||||
|
}
|
||||||
|
|
||||||
|
dbPassword := os.Getenv("DB_PASSWORD")
|
||||||
|
if dbPassword == "" {
|
||||||
|
panic(fmt.Errorf("missing required env var: DB_PASSWORD"))
|
||||||
|
}
|
||||||
|
|
||||||
|
dbName := os.Getenv("DB_NAME")
|
||||||
|
if dbName == "" {
|
||||||
|
panic(fmt.Errorf("missing required env var: DB_NAME"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defaults to require: RDS terminates TLS, and only a local Postgres that
|
||||||
|
// serves none (LocalStack's RDS emulation) has any business overriding it.
|
||||||
|
dbSSLMode := os.Getenv("DB_SSLMODE")
|
||||||
|
if dbSSLMode == "" {
|
||||||
|
dbSSLMode = "require"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
|
||||||
|
dbHost, dbPort, dbUser, dbPassword, dbName, dbSSLMode,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -123,6 +123,34 @@ services:
|
|||||||
- action: rebuild
|
- action: rebuild
|
||||||
path: ./cms
|
path: ./cms
|
||||||
|
|
||||||
|
discovery:
|
||||||
|
container_name: "${DISCOVERY_DOCKER_NAME:-discovery}"
|
||||||
|
build:
|
||||||
|
context: ./discovery
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8080:8080" # JSON API + Swagger UI at /swagger/
|
||||||
|
environment:
|
||||||
|
# Postgres only — discovery touches no AWS service, which is why it has
|
||||||
|
# no task role in infrastructure/main.go and no AWS_* wiring here. Its
|
||||||
|
# own role and database, provisioned alongside the cms ones by
|
||||||
|
# newServiceDatabase, so the two services share no credentials.
|
||||||
|
- DB_HOST=${DISCOVERY_DB_HOST:-localhost.localstack.cloud}
|
||||||
|
- DB_PORT=${DISCOVERY_DB_PORT:-4510}
|
||||||
|
- DB_NAME=${DISCOVERY_DB_NAME:-discovery}
|
||||||
|
- DB_USER=${DISCOVERY_DB_USER:-discovery}
|
||||||
|
- DB_PASSWORD=${DB_PASSWORD:?}
|
||||||
|
- DB_SSLMODE=${DISCOVERY_DB_SSLMODE:-disable}
|
||||||
|
depends_on:
|
||||||
|
infra:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
develop:
|
||||||
|
watch:
|
||||||
|
# Same as cms: a compiled binary, so every change means a new image.
|
||||||
|
# Needs `docker compose up --watch` (or `docker compose watch`).
|
||||||
|
- action: rebuild
|
||||||
|
path: ./discovery
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pulumi-state:
|
pulumi-state:
|
||||||
pulumi-home:
|
pulumi-home:
|
||||||
|
|||||||
@@ -1089,9 +1089,13 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !localstack {
|
if !localstack {
|
||||||
|
// runMigrations is true for the same reason it is for cms:
|
||||||
|
// discovery embeds its own golang-migrate migrations and applies
|
||||||
|
// them through `./discovery migrate`, so ECS runs that container to
|
||||||
|
// completion before the service accepts traffic.
|
||||||
discoveryRepo, discoveryService, discoveryAlb, err = deployFargateService(ctx, "discovery", 8080,
|
discoveryRepo, discoveryService, discoveryAlb, err = deployFargateService(ctx, "discovery", 8080,
|
||||||
cluster, execRole, nil, nil, vpcID, subnetIDs, albSecurityGroup, serviceSecurityGroup,
|
cluster, execRole, nil, nil, vpcID, subnetIDs, albSecurityGroup, serviceSecurityGroup,
|
||||||
db.Address, db.Port, discoveryPassword, false)
|
db.Address, db.Port, discoveryPassword, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user