refactor: implement database-specific schema architecture with schema-as-query pattern

🏗️ **Major Database Schema Refactoring**

**Problem Solved**: Eliminated model duplication and multiple sources of truth by:
- Removed duplicate models (`internal/models/content.go`)
- Replaced inlined schema strings with sqlc-generated setup functions
- Implemented database-specific schemas with proper NOT NULL constraints

**Key Improvements**:
 **Single Source of Truth**: Database schemas define all types, no manual sync needed
 **Clean Generated Types**: sqlc generates `string` and `int64` instead of `sql.NullString/sql.NullTime`
 **Schema-as-Query Pattern**: Setup functions generated by sqlc for type safety
 **Database-Specific Optimization**: SQLite INTEGER timestamps, PostgreSQL BIGINT timestamps
 **Cross-Database Compatibility**: Single codebase supports both SQLite and PostgreSQL

**Architecture Changes**:
- `db/sqlite/` - SQLite-specific schema and setup queries
- `db/postgresql/` - PostgreSQL-specific schema and setup queries
- `db/queries/` - Cross-database CRUD queries using `sqlc.arg()` syntax
- `internal/db/database.go` - Database abstraction with runtime selection
- `internal/api/models.go` - Clean API models for requests/responses

**Version Control System**: Complete element-level history with user attribution and rollback

**Verification**:  Full API workflow tested (create → update → rollback → versions)
**Production Ready**: Supports SQLite (development) → PostgreSQL (production) migration
This commit is contained in:
2025-09-09 00:25:07 +02:00
parent 161c320304
commit bab329b429
41 changed files with 3703 additions and 561 deletions

View File

@@ -23,7 +23,7 @@ func main() {
flag.Parse()
// Initialize database
database, err := db.NewSQLiteDB(*dbPath)
database, err := db.NewDatabase(*dbPath)
if err != nil {
log.Fatalf("Failed to initialize database: %v", err)
}
@@ -48,12 +48,17 @@ func main() {
// Content endpoints matching the expected API contract
apiRouter.HandleFunc("/bulk", contentHandler.GetBulkContent).Methods("GET")
apiRouter.HandleFunc("/{id}/versions", contentHandler.GetContentVersions).Methods("GET")
apiRouter.HandleFunc("/{id}/rollback", contentHandler.RollbackContent).Methods("POST")
apiRouter.HandleFunc("/{id}", contentHandler.GetContent).Methods("GET")
apiRouter.HandleFunc("/{id}", contentHandler.UpdateContent).Methods("PUT")
apiRouter.HandleFunc("/{id}", contentHandler.DeleteContent).Methods("DELETE")
apiRouter.HandleFunc("", contentHandler.GetAllContent).Methods("GET")
apiRouter.HandleFunc("", contentHandler.CreateContent).Methods("POST")
// Handle CORS preflight requests explicitly
apiRouter.HandleFunc("/{id}/versions", api.CORSPreflightHandler).Methods("OPTIONS")
apiRouter.HandleFunc("/{id}/rollback", api.CORSPreflightHandler).Methods("OPTIONS")
apiRouter.HandleFunc("/{id}", api.CORSPreflightHandler).Methods("OPTIONS")
apiRouter.HandleFunc("", api.CORSPreflightHandler).Methods("OPTIONS")
apiRouter.HandleFunc("/bulk", api.CORSPreflightHandler).Methods("OPTIONS")
@@ -68,8 +73,11 @@ func main() {
fmt.Printf(" GET /api/content?site_id={site}\n")
fmt.Printf(" GET /api/content/{id}?site_id={site}\n")
fmt.Printf(" GET /api/content/bulk?site_id={site}&ids[]={id1}&ids[]={id2}\n")
fmt.Printf(" GET /api/content/{id}/versions?site_id={site}\n")
fmt.Printf(" POST /api/content\n")
fmt.Printf(" PUT /api/content/{id}\n")
fmt.Printf(" POST /api/content/{id}/rollback\n")
fmt.Printf(" DELETE /api/content/{id}?site_id={site}\n")
fmt.Printf("\n🔄 Press Ctrl+C to shutdown gracefully\n\n")
// Setup graceful shutdown