mirror of
https://github.com/R0m1k3/FlowReader.git
synced 2026-10-12 01:38:16 +02:00
feat(story-1.1): implement walking skeleton with Go Chi and Docker Compose
This commit is contained in:
1 parent
d7ad2536d6
commit
99f1300e13
11 files changed
+411
No files matched your search
+34
@@ -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"]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
// Package domain contains business logic entities and interfaces.
|
||||
// This package defines the core types used throughout the application.
|
||||
package domain
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
// Package repository contains database access implementations.
|
||||
// Repositories use raw SQL with pgx - No ORM.
|
||||
package repository
|
||||
@@ -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
|
||||
```
|
||||
Reference in new issue
Block a user