feat(story-1.1): implement walking skeleton with Go Chi and Docker Compose

This commit is contained in:
Michael committed 2026-02-04 14:33:38 +01:00
1 parent d7ad2536d6
commit 99f1300e13
11 files changed
+411

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
# Build stage
FROM golang:1.22-alpine AS builder
WORKDIR /app
# Install dependencies
RUN apk add --no-cache git
# Copy go mod files
COPY go.mod go.sum* ./
RUN go mod download
# Copy source code
COPY . .
# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /server ./cmd/server
# Final stage
FROM alpine:3.19
WORKDIR /app
# Install wget for healthcheck
RUN apk add --no-cache wget ca-certificates
# Copy binary from builder
COPY --from=builder /server /app/server
# Expose port
EXPOSE 8080
# Run the application
CMD ["/app/server"]
+45
View File
@@ -0,0 +1,45 @@
.PHONY: dev build run test lint clean docker-up docker-down migrate-up migrate-down
# Development
dev:
go run ./cmd/server
build:
go build -o bin/server ./cmd/server
run: build
./bin/server
# Testing
test:
go test -v ./...
test-coverage:
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
# Linting
lint:
golangci-lint run
# Docker
docker-up:
docker-compose up --build -d
docker-down:
docker-compose down
docker-logs:
docker-compose logs -f
# Database migrations (placeholder for Story 1.2)
migrate-up:
@echo "Migration system not yet configured (Story 1.2)"
migrate-down:
@echo "Migration system not yet configured (Story 1.2)"
# Cleanup
clean:
rm -rf bin/
rm -f coverage.out coverage.html
+56
View File
@@ -0,0 +1,56 @@
# FlowReader
A minimalist, self-hosted RSS/Atom reader focused on simplicity and resource efficiency.
## Quick Start
```bash
# Start with Docker Compose
docker-compose up --build
# Or use Make
make docker-up
```
Access the API: `http://localhost:8080`
## Development
```bash
# Run locally (requires Go 1.22+)
make dev
# Run tests
make test
# Lint code
make lint
```
## Project Structure
```
flowreader/
├── cmd/
│ └── server/ # Application entry point
├── internal/
│ ├── domain/ # Business logic (entities, services)
│ ├── handler/ # HTTP handlers
│ └── repository/ # Database access
├── pkg/ # Shared utilities
└── migrations/ # SQL migrations
```
## Tech Stack
- **Backend**: Go 1.22+ (Chi Router, pgx driver)
- **Database**: PostgreSQL 16+
- **Frontend**: React + Vite (coming in Epic 3)
## Memory Target
Total container footprint: **< 150MB RAM**
## License
MIT
@@ -0,0 +1,71 @@
# Sprint Status - FlowReader
# generated: 2026-02-04
# project: FlowReader
# tracking_system: file-system
# story_location: _bmad-output/implementation-artifacts
# STATUS DEFINITIONS:
# ==================
# Epic Status:
# - backlog: Epic not yet started
# - in-progress: Epic actively being worked on
# - done: All stories in epic completed
#
# Story Status:
# - backlog: Story only exists in epic file
# - ready-for-dev: Story file created, ready for development
# - in-progress: Developer actively working on implementation
# - review: Implementation complete, ready for review
# - done: Story completed
#
# Retrospective Status:
# - optional: Can be completed but not required
# - done: Retrospective has been completed
#
# WORKFLOW NOTES:
# ===============
# - Mark epic as 'in-progress' when starting work on its first story
# - SM typically creates next story ONLY after previous one is 'done'
# - Dev moves story to 'review', then runs code-review
generated: 2026-02-04
project: FlowReader
tracking_system: file-system
story_location: "_bmad-output/implementation-artifacts"
development_status:
# Epic 1: Walking Skeleton & User Access
epic-1: in-progress
1-1-project-initialization-walking-skeleton: review
1-2-database-migration-system: backlog
1-3-user-registration-api: backlog
1-4-session-authentication: backlog
1-5-frontend-auth-foundation: backlog
epic-1-retrospective: optional
# Epic 2: Feed Core Engine
epic-2: backlog
2-1-feed-model-add-feed-by-url: backlog
2-2-feed-metadata-auto-discovery: backlog
2-3-opml-import: backlog
2-4-feed-management-crud: backlog
2-5-background-feed-fetcher: backlog
epic-2-retrospective: optional
# Epic 3: Lecture Plaisir & Real-Time Experience
epic-3: backlog
3-1-dashboard-nouveautes: backlog
3-2-article-detail-reader-mode: backlog
3-3-mark-as-read-manual-auto: backlog
3-4-mark-all-as-read: backlog
3-5-favorites-system: backlog
3-6-websocket-push-notifications: backlog
epic-3-retrospective: optional
# Epic 4: Advanced Systems & Robustness
epic-4: backlog
4-1-multi-device-sync: backlog
4-2-admin-user-list: backlog
4-3-delete-user-account: backlog
4-4-performance-ram-optimization: backlog
epic-4-retrospective: optional
+105
View File
@@ -0,0 +1,105 @@
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/jackc/pgx/v5/pgxpool"
)
func main() {
// Load configuration from environment
port := getEnv("PORT", "8080")
dbURL := getEnv("DATABASE_URL", "postgres://flowreader:flowreader@db:5432/flowreader?sslmode=disable")
// Initialize database connection pool
ctx := context.Background()
pool, err := pgxpool.New(ctx, dbURL)
if err != nil {
log.Fatalf("Unable to create connection pool: %v", err)
}
defer pool.Close()
// Verify database connection
if err := pool.Ping(ctx); err != nil {
log.Fatalf("Unable to ping database: %v", err)
}
log.Println("Connected to database")
// Initialize router
r := chi.NewRouter()
// Middleware
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Timeout(30 * time.Second))
// Health check endpoint
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
// Check database connection
if err := pool.Ping(r.Context()); err != nil {
http.Error(w, "Database connection failed", http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok","service":"flowreader"}`))
})
// API routes placeholder
r.Route("/api/v1", func(r chi.Router) {
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"message":"FlowReader API v1"}`))
})
})
// Create server
srv := &http.Server{
Addr: ":" + port,
Handler: r,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Graceful shutdown
go func() {
log.Printf("Server starting on port %s", port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server failed: %v", err)
}
}()
// Wait for interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited properly")
}
// getEnv returns the value of an environment variable or a default value
func getEnv(key, defaultValue string) string {
if value, exists := os.LookupEnv(key); exists {
return value
}
return defaultValue
}
+46
View File
@@ -0,0 +1,46 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- PORT=8080
- DATABASE_URL=postgres://flowreader:flowreader@db:5432/flowreader?sslmode=disable
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=flowreader
- POSTGRES_PASSWORD=flowreader
- POSTGRES_DB=flowreader
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U flowreader -d flowreader"]
interval: 5s
timeout: 5s
retries: 5
# RAM Optimization (from Architecture NFR1)
command: >
postgres
-c shared_buffers=24MB
-c max_connections=20
-c work_mem=2MB
volumes:
postgres_data:
+17
View File
@@ -0,0 +1,17 @@
module github.com/michael/flowreader
go 1.22
require (
github.com/go-chi/chi/v5 v5.0.11
github.com/jackc/pgx/v5 v5.5.2
)
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
+3
View File
@@ -0,0 +1,3 @@
// Package domain contains business logic entities and interfaces.
// This package defines the core types used throughout the application.
package domain
+4
View File
@@ -0,0 +1,4 @@
// Package handler contains HTTP handlers for the REST API.
// Handlers are responsible for request/response translation only.
// No business logic should be placed here.
package handler
+3
View File
@@ -0,0 +1,3 @@
// Package repository contains database access implementations.
// Repositories use raw SQL with pgx - No ORM.
package repository
+27
View File
@@ -0,0 +1,27 @@
# SQL Migrations
This folder contains versioned database migrations.
Migrations are managed using `golang-migrate`.
## Naming Convention
```
{version}_{description}.up.sql
{version}_{description}.down.sql
```
Example:
- `001_create_users.up.sql`
- `001_create_users.down.sql`
## Commands
```bash
# Apply all pending migrations
make migrate-up
# Rollback last migration
make migrate-down
```