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