rust
This commit is contained in:
parent
9f69d80906
commit
0ad2f8a2b0
38
.gitignore
vendored
38
.gitignore
vendored
@ -1,38 +0,0 @@
|
|||||||
HELP.md
|
|
||||||
# Track jOOQ generated sources
|
|
||||||
!target/generated-sources/jooq/
|
|
||||||
target/
|
|
||||||
.mvn/wrapper/maven-wrapper.jar
|
|
||||||
!**/src/main/**/target/
|
|
||||||
!**/src/test/**/target/
|
|
||||||
|
|
||||||
### STS ###
|
|
||||||
.apt_generated
|
|
||||||
.classpath
|
|
||||||
.factorypath
|
|
||||||
.project
|
|
||||||
.settings
|
|
||||||
.springBeans
|
|
||||||
.sts4-cache
|
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
|
||||||
.idea
|
|
||||||
*.iws
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
|
|
||||||
### NetBeans ###
|
|
||||||
/nbproject/private/
|
|
||||||
/nbbuild/
|
|
||||||
/dist/
|
|
||||||
/nbdist/
|
|
||||||
/.nb-gradle/
|
|
||||||
build/
|
|
||||||
!**/src/main/**/build/
|
|
||||||
!**/src/test/**/build/
|
|
||||||
|
|
||||||
### VS Code ###
|
|
||||||
.vscode/
|
|
||||||
.env
|
|
||||||
|
|
||||||
|
|
||||||
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "rhythm_backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
45
Dockerfile
45
Dockerfile
@ -1,45 +0,0 @@
|
|||||||
# ---- STAGE 1: Build ----
|
|
||||||
# Use an OpenJDK image that matches the version you develop with.
|
|
||||||
# It contains the JDK, but not Maven.
|
|
||||||
FROM eclipse-temurin:25-jdk-jammy AS builder
|
|
||||||
|
|
||||||
# Set the working directory
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# ---- Caching Dependencies ----
|
|
||||||
# First, copy the files that define the build, including the Maven Wrapper
|
|
||||||
COPY mvnw .
|
|
||||||
COPY .mvn .mvn
|
|
||||||
COPY pom.xml .
|
|
||||||
|
|
||||||
# Make the wrapper executable
|
|
||||||
RUN chmod +x ./mvnw
|
|
||||||
|
|
||||||
# Run a Maven command to download dependencies.
|
|
||||||
# Since pom.xml and wrapper files rarely change, this layer will be cached by Docker,
|
|
||||||
# speeding up subsequent builds significantly.
|
|
||||||
RUN ./mvnw dependency:go-offline
|
|
||||||
|
|
||||||
# ---- Building the Application ----
|
|
||||||
# Now, copy the source code. If only source code changes, the layers above are cached.
|
|
||||||
COPY src ./src
|
|
||||||
|
|
||||||
# Build the application JAR using the Maven Wrapper
|
|
||||||
RUN ./mvnw clean package -DskipTests -Djooq.codegen.skip=true -Dflyway.skip=true
|
|
||||||
|
|
||||||
|
|
||||||
# ---- STAGE 2: Runtime ----
|
|
||||||
# Use a lean Eclipse Temurin JRE image for a small and secure final container.
|
|
||||||
FROM eclipse-temurin:25-jre-jammy
|
|
||||||
|
|
||||||
# Set the working directory
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy only the built JAR file from the 'builder' stage
|
|
||||||
COPY --from=builder /app/target/*.jar app.jar
|
|
||||||
|
|
||||||
# Expose the application port
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
# Command to run the application
|
|
||||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
|
||||||
107
README.md
107
README.md
@ -1,107 +0,0 @@
|
|||||||
# Rhythm Backend (Go)
|
|
||||||
|
|
||||||
Lean Go API backend for ISeeU Tracker.
|
|
||||||
|
|
||||||
## Current Project State
|
|
||||||
|
|
||||||
- [x] Go module initialized (`go.mod`)
|
|
||||||
- [x] Entry point created (`cmd/api/main.go`)
|
|
||||||
- [x] Env config package created (`internal/config/config.go`)
|
|
||||||
- [x] `.env` loading added with required DB variables
|
|
||||||
- [x] DB URL builder added in config (`DatabaseURL`) with schema `search_path`
|
|
||||||
- [x] DB package scaffold created (`internal/db/`)
|
|
||||||
- [x] Local Postgres service available in `compose.yaml` (dev profile)
|
|
||||||
- [ ] Database connection package (`internal/db`) not implemented yet
|
|
||||||
- [ ] Goose migrations folder/files not created yet
|
|
||||||
- [ ] HTTP router and handlers not implemented yet
|
|
||||||
- [ ] User DTO/model/repository/service not implemented yet
|
|
||||||
|
|
||||||
## General Folder Structure
|
|
||||||
|
|
||||||
```text
|
|
||||||
cmd/
|
|
||||||
api/
|
|
||||||
main.go # application entrypoint
|
|
||||||
|
|
||||||
internal/
|
|
||||||
config/ # env and app config
|
|
||||||
db/ # postgres/sqlx connection + migrations
|
|
||||||
http/ # router, middleware, handlers
|
|
||||||
auth/ # jwt, password hashing, auth middleware
|
|
||||||
service/ # business logic
|
|
||||||
repository/ # SQLx queries (no ORM)
|
|
||||||
model/ # domain models + request/response DTOs
|
|
||||||
|
|
||||||
migrations/ # Goose SQL migrations
|
|
||||||
scripts/ # optional local/dev scripts
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roadmap Checklist (Do Not Delete)
|
|
||||||
|
|
||||||
### Chapter 1 - Bootstrap and Config
|
|
||||||
|
|
||||||
- [x] Create `cmd/api/main.go`
|
|
||||||
- [x] Create `internal/config` package
|
|
||||||
- [x] Load `.env` and validate required DB env vars
|
|
||||||
- [x] Add DB URL builder method in config
|
|
||||||
- [ ] Add `APP_PORT` env var with default fallback
|
|
||||||
- [ ] Improve startup logs (without printing secrets)
|
|
||||||
|
|
||||||
### Chapter 2 - Database and Goose
|
|
||||||
|
|
||||||
- [ ] Implement `internal/db/postgres.go` with `sqlx` connection (`pgx` driver)
|
|
||||||
- [ ] Add `internal/db/migrate.go` to run Goose at startup
|
|
||||||
- [ ] Create `migrations/` directory
|
|
||||||
- [ ] Create first migration for `users` table
|
|
||||||
- [ ] Wire migration call into app startup (before HTTP server)
|
|
||||||
- [ ] Add `goose status` and rollback notes in README
|
|
||||||
|
|
||||||
### Chapter 3 - User Vertical Slice (First Feature)
|
|
||||||
|
|
||||||
- [ ] Add user DTO (`username`, `password`) in `internal/model`
|
|
||||||
- [ ] Add user DB model (`id`, `username`, `password_hash`, timestamps)
|
|
||||||
- [ ] Add user repository with SQLx (`CreateUser`, `GetByUsername`)
|
|
||||||
- [ ] Add user service with bcrypt hashing
|
|
||||||
- [ ] Add `POST /users/register` handler
|
|
||||||
- [ ] Add input validation and proper error responses
|
|
||||||
|
|
||||||
### Chapter 4 - HTTP Layer and Health
|
|
||||||
|
|
||||||
- [ ] Add router setup in `internal/http/router.go`
|
|
||||||
- [ ] Add `GET /health` endpoint
|
|
||||||
- [ ] Add JSON response helpers
|
|
||||||
- [ ] Add request logging middleware
|
|
||||||
- [ ] Add panic recovery middleware
|
|
||||||
|
|
||||||
### Chapter 5 - Security Foundation
|
|
||||||
|
|
||||||
- [ ] Add password hashing and compare helpers
|
|
||||||
- [ ] Add JWT generation/verification package
|
|
||||||
- [ ] Add auth middleware for protected routes
|
|
||||||
- [ ] Add `POST /auth/login`
|
|
||||||
- [ ] Add `GET /auth/me`
|
|
||||||
|
|
||||||
### Chapter 6 - Developer Experience
|
|
||||||
|
|
||||||
- [ ] Add `Makefile` targets (`run`, `db-up`, `migrate-up`, `migrate-down`)
|
|
||||||
- [ ] Add graceful shutdown in `main.go`
|
|
||||||
- [ ] Add structured logging (`log/slog`)
|
|
||||||
- [ ] Add `.env.example` updates for all required vars
|
|
||||||
- [ ] Add basic project usage section in README
|
|
||||||
|
|
||||||
### Chapter 7 - Testing
|
|
||||||
|
|
||||||
- [ ] Add unit tests for config package
|
|
||||||
- [ ] Add unit tests for user service
|
|
||||||
- [ ] Add repository integration test setup (test DB)
|
|
||||||
- [ ] Add handler tests for register endpoint
|
|
||||||
- [ ] Add CI step to run tests
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Keep handlers thin, business logic in `service`, SQL in `repository`.
|
|
||||||
- Use `sqlx` for explicit SQL and scan helpers.
|
|
||||||
- Use `goose` for schema versioning and run migrations automatically at startup.
|
|
||||||
- Never store plain passwords; always use `password_hash`.
|
|
||||||
- Keep one shared `*sqlx.DB` pool for the app lifetime; do not open DB per request.
|
|
||||||
- Pass `context.Context` from handler (`r.Context()`) to service/repository methods.
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.kanopo.dev/rhythm/rhythm-backend/internal/config"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cfg := config.Load()
|
|
||||||
log.Println(cfg)
|
|
||||||
}
|
|
||||||
57
compose.yaml
57
compose.yaml
@ -1,57 +0,0 @@
|
|||||||
services:
|
|
||||||
# api-prod:
|
|
||||||
# build: .
|
|
||||||
# restart: unless-stopped
|
|
||||||
# container_name: qrcode-api
|
|
||||||
# ports:
|
|
||||||
# - "8080:8080"
|
|
||||||
# environment:
|
|
||||||
# DB_HOST: db-prod
|
|
||||||
# env_file:
|
|
||||||
# - ".env"
|
|
||||||
# depends_on:
|
|
||||||
# db-prod:
|
|
||||||
# condition: service_healthy
|
|
||||||
# profiles:
|
|
||||||
# - prod
|
|
||||||
#
|
|
||||||
# db-prod:
|
|
||||||
# image: postgres:18.0-alpine
|
|
||||||
# restart: unless-stopped
|
|
||||||
# container_name: qrcode-database-prod
|
|
||||||
# ports:
|
|
||||||
# - "${DB_PORT:-5432}:5432"
|
|
||||||
# environment:
|
|
||||||
# POSTGRES_USER: ${DB_USERNAME}
|
|
||||||
# POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
||||||
# POSTGRES_DB: ${DB_NAME}
|
|
||||||
# volumes:
|
|
||||||
# - postgres_data:/var/lib/postgresql/data
|
|
||||||
# profiles:
|
|
||||||
# - prod
|
|
||||||
# healthcheck:
|
|
||||||
# test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_NAME}"]
|
|
||||||
# interval: 5s
|
|
||||||
# timeout: 3s
|
|
||||||
# retries: 10
|
|
||||||
|
|
||||||
db-dev:
|
|
||||||
image: postgres:18.0-alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
container_name: rhythm-db-dev
|
|
||||||
ports:
|
|
||||||
- "${DB_PORT:-5432}:5432"
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: ${DB_USERNAME}
|
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
||||||
POSTGRES_DB: ${DB_NAME}
|
|
||||||
profiles:
|
|
||||||
- dev
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_NAME}"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 10
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
DB_USERNAME=user
|
|
||||||
DB_PASSWORD=password
|
|
||||||
DB_NAME=rhythm-dev
|
|
||||||
DB_PORT=5432
|
|
||||||
DB_HOST=localhost
|
|
||||||
5
go.mod
5
go.mod
@ -1,5 +0,0 @@
|
|||||||
module git.kanopo.dev/rhythm/rhythm-backend
|
|
||||||
|
|
||||||
go 1.26.2
|
|
||||||
|
|
||||||
require github.com/joho/godotenv v1.5.1
|
|
||||||
2
go.sum
2
go.sum
@ -1,2 +0,0 @@
|
|||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
DBHost string
|
|
||||||
DBPort string
|
|
||||||
DBName string
|
|
||||||
DBUser string
|
|
||||||
DBPassword string
|
|
||||||
DBSchema string
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEnv(key string) string {
|
|
||||||
v := os.Getenv(key)
|
|
||||||
if v == "" {
|
|
||||||
log.Fatalf("missing required env var: %s", key)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
func getEnvOrDefault(key, default_string string) string {
|
|
||||||
v := os.Getenv(key)
|
|
||||||
if v == "" {
|
|
||||||
return default_string
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func Load() Config {
|
|
||||||
_ = godotenv.Load()
|
|
||||||
|
|
||||||
return Config{
|
|
||||||
DBHost: getEnv("DB_HOST"),
|
|
||||||
DBPort: getEnv("DB_PORT"),
|
|
||||||
DBName: getEnv("DB_NAME"),
|
|
||||||
DBUser: getEnv("DB_USERNAME"),
|
|
||||||
DBPassword: getEnv("DB_PASSWORD"),
|
|
||||||
DBSchema: getEnvOrDefault("DB_SCHEMA", "public"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg Config) DatabaseURL() string {
|
|
||||||
u := &url.URL{
|
|
||||||
Scheme: "postgres",
|
|
||||||
User: url.UserPassword(cfg.DBUser, cfg.DBPassword),
|
|
||||||
Host: net.JoinHostPort(cfg.DBHost, cfg.DBPort),
|
|
||||||
Path: cfg.DBName,
|
|
||||||
}
|
|
||||||
q := u.Query()
|
|
||||||
q.Set("sslmode", "false")
|
|
||||||
q.Set("search_path", cfg.DBSchema)
|
|
||||||
u.RawQuery = q.Encode()
|
|
||||||
return u.String()
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
package db
|
|
||||||
@ -1 +0,0 @@
|
|||||||
package db
|
|
||||||
3
src/main.rs
Normal file
3
src/main.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user