refactor: restructure project from bookmann to shelf

- Rename project from 'bookmann' to 'shelf'
- Move all backend/ contents to root level (flatten structure)
- Update Go module name from 'bookmann' to 'shelf'
- Update all import paths to use new 'shelf' module
- Update Dockerfile to work without backend/ subdirectory
- Update docker-compose.yml to use new structure and rename containers
- Update .gitignore for new file paths
- Update README.md with new project name and structure
- Regenerate database code with new module imports
This commit is contained in:
john-okeefe committed 2026-01-23 09:08:04 -05:00
1 parent 152ed27200
commit 4318f8624b
31 files changed
+38 -175

No files matched your search

File renamed without changes.
+2 -3
View File
@@ -197,9 +197,8 @@ coverage/
bruno/collection.bru bruno/collection.bru
# Generated static assets (SvelteKit build output) # Generated static assets (SvelteKit build output)
backend/cmd/server/static/ cmd/server/static/
# Built binaries # Built binaries
main main
backend/main server
backend/server
+7 -10
View File
@@ -6,20 +6,17 @@ WORKDIR /app
# Install sqlc # Install sqlc
RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
# Copy backend source code # Copy source code
COPY backend/ ./backend/ COPY . .
# Download dependencies # Download dependencies
RUN cd backend && go mod tidy RUN go mod tidy
# Generate sqlc code # Generate sqlc code
RUN cd backend && sqlc generate RUN sqlc generate
# Copy templates (updated for new homepage)
COPY backend/templates ./backend/templates
# Build the application # Build the application
RUN cd backend && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o ../main ./cmd/server RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server
# Final stage # Final stage
FROM alpine:latest FROM alpine:latest
@@ -31,10 +28,10 @@ WORKDIR /root/
COPY --from=builder /app/main . COPY --from=builder /app/main .
# Copy migrations (if needed for initialization) # Copy migrations (if needed for initialization)
COPY --from=builder /app/backend/migrations ./migrations COPY --from=builder /app/migrations ./migrations
# Copy templates # Copy templates
COPY --from=builder /app/backend/templates ./templates COPY --from=builder /app/templates ./templates
# Expose port # Expose port
EXPOSE 8765 EXPOSE 8765
+18 -17
View File
@@ -1,6 +1,6 @@
# 📚 Bookmann # 📚 Shelf
A self-hosted ebook management system built with Go, PostgreSQL, HTMX, and Tailwind CSS (fully integrated into a single service) featuring multiple beautiful dark themes with Tokyo Night as default. A self-hosted ebook management system built with Go, PostgreSQL, HTMX, and Tailwind CSS featuring multiple beautiful dark themes with Tokyo Night as default. Smart folder scanning with real-time monitoring and rich EPUB metadata extraction.
## ✨ Features ## ✨ Features
@@ -35,7 +35,7 @@ A self-hosted ebook management system built with Go, PostgreSQL, HTMX, and Tailw
docker-compose up --build docker-compose up --build
``` ```
3. Access the application at http://localhost:8765 3. Access Shelf at http://localhost:8765
### Database ### Database
@@ -46,16 +46,15 @@ PostgreSQL runs on port 5432 with default credentials:
## Development ## Development
### Backend ### Development
```bash ```bash
cd backend
go mod tidy go mod tidy
go run github.com/sqlc-dev/sqlc/cmd/sqlc@latest generate go run github.com/sqlc-dev/sqlc/cmd/sqlc@latest generate
go run cmd/server/main.go go run cmd/server/main.go
``` ```
The application uses Go HTML templates for server-side rendering with HTMX for dynamic interactions. Templates are located in `backend/templates/`. The application uses Go HTML templates for server-side rendering with HTMX for dynamic interactions. Templates are located in `templates/`.
**✨ Enhanced Features:** **✨ Enhanced Features:**
- **Authentication**: Login/Register forms with multiple theme support at `/login` and `/register` - **Authentication**: Login/Register forms with multiple theme support at `/login` and `/register`
@@ -140,18 +139,20 @@ Use the included Bruno collection in the `bruno/` directory for testing the API:
``` ```
. .
├── backend/ ├── cmd/server/ # Application entry point
│ ├── cmd/server/ # Application entry point ├── internal/
│ ├── internal/ │ ├── config/ # Configuration management
│ │ ├── config/ # Configuration management │ ├── database/ # Database connection and queries
│ │ ├── database/ # Database connection and queries │ ├── handlers/ # HTTP handlers (auth + ebooks)
│ │ ├── handlers/ # HTTP handlers (auth + ebooks) │ └── services/ # Business logic services (ebook scanner)
│ │ └── services/ # Business logic services (ebook scanner) ├── migrations/ # Database migrations
│ ├── migrations/ # Database migrations ├── templates/ # Go HTML templates with HTMX
│ ├── templates/ # Go HTML templates with HTMX
│ └── sqlc.yaml # sqlc configuration
├── bruno/ # Bruno API testing collection ├── bruno/ # Bruno API testing collection
├── docker-compose.yml ├── Dockerfile # Docker build configuration
├── docker-compose.yml # Docker Compose setup
├── go.mod # Go module definition
├── go.sum # Go module checksums
├── sqlc.yaml # SQL code generation config
└── README.md └── README.md
``` ```
-134
View File
@@ -1,134 +0,0 @@
package main
import (
"bookmann/internal/config"
"bookmann/internal/database"
"bookmann/internal/handlers"
"html/template"
"io"
"log"
"net/http"
"strings"
"github.com/go-playground/validator/v10"
jwtgo "github.com/golang-jwt/jwt"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type TemplateRenderer struct {
templates *template.Template
}
func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
// CustomValidator wraps the go-playground validator
type CustomValidator struct {
validator *validator.Validate
}
func (cv *CustomValidator) Validate(i interface{}) error {
return cv.validator.Struct(i)
}
func main() {
cfg := config.LoadConfig()
pool, err := database.NewConnection(cfg.DatabaseURL())
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer pool.Close()
queries := database.New(pool)
e := echo.New()
// Set up validator
e.Validator = &CustomValidator{validator: validator.New()}
// Set up templates
renderer := &TemplateRenderer{
templates: template.Must(template.ParseGlob("templates/*.html")),
}
e.Renderer = renderer
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
// Auth routes (no auth required)
auth := handlers.NewAuthHandler(queries, cfg.JWTSecret)
e.POST("/api/auth/register", auth.Register)
e.POST("/api/auth/login", auth.Login)
// JWT middleware for protected routes
jwtMiddleware := middleware.JWTWithConfig(middleware.JWTConfig{
SigningKey: []byte(cfg.JWTSecret),
ContextKey: "user",
SuccessHandler: func(c echo.Context) {
token := c.Get("user").(*jwtgo.Token)
claims := token.Claims.(jwtgo.MapClaims)
c.Set("user_id", claims["user_id"])
},
})
// Protected routes
protected := e.Group("/api", jwtMiddleware)
protected.GET("/auth/profile", auth.GetProfile)
protected.POST("/auth/ebook-folders", auth.AddEbookFolder)
protected.GET("/auth/ebook-folders", auth.GetEbookFolders)
protected.DELETE("/auth/ebook-folders/:folderPath", auth.DeleteEbookFolder)
protected.GET("/users", auth.ListUsers)
// Routes
handlers.SetupRoutes(protected, queries)
// Static files
e.Static("/static", "static")
// Page routes
e.GET("/", func(c echo.Context) error {
// Check if user has valid JWT token
tokenString := c.Request().Header.Get("Authorization")
if tokenString != "" && strings.HasPrefix(tokenString, "Bearer ") {
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
token, err := jwtgo.Parse(tokenString, func(token *jwtgo.Token) (interface{}, error) {
return []byte(cfg.JWTSecret), nil
})
if err == nil && token.Valid {
// User is authenticated, get user info and serve dashboard
claims := token.Claims.(jwtgo.MapClaims)
userID := claims["user_id"].(string)
user, err := queries.GetUser(c.Request().Context(), pgtype.UUID{Bytes: uuid.MustParse(userID), Valid: true})
if err == nil {
return c.Render(http.StatusOK, "dashboard.html", map[string]interface{}{
"User": map[string]string{
"ID": uuid.UUID(user.ID.Bytes).String(),
"Username": user.Username,
"Email": user.Email,
},
})
}
}
}
// User not authenticated or token invalid, serve landing page
return c.Render(http.StatusOK, "index.html", nil)
})
e.GET("/login", func(c echo.Context) error {
return c.Render(http.StatusOK, "login.html", nil)
})
e.GET("/register", func(c echo.Context) error {
return c.Render(http.StatusOK, "register.html", nil)
})
// Start server
log.Printf("Starting server on port %s", cfg.ServerPort)
e.Logger.Fatal(e.Start(":" + cfg.ServerPort))
}
+6 -6
View File
@@ -3,14 +3,14 @@ version: "3.8"
services: services:
db: db:
image: postgres:15-alpine image: postgres:15-alpine
container_name: bookmann_db container_name: shelf_db
environment: environment:
POSTGRES_DB: ebookdb POSTGRES_DB: ebookdb
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${DBPASS} POSTGRES_PASSWORD: ${DBPASS}
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
- ./backend/migrations:/docker-entrypoint-initdb.d - ./migrations:/docker-entrypoint-initdb.d
ports: ports:
- "5432:5432" - "5432:5432"
healthcheck: healthcheck:
@@ -21,11 +21,11 @@ services:
env_file: env_file:
- .env - .env
backend: app:
build: build:
context: . context: .
dockerfile: ./backend/Dockerfile dockerfile: ./Dockerfile
container_name: bookmann container_name: shelf
environment: environment:
DATABASE_HOST: db DATABASE_HOST: db
DATABASE_PORT: 5432 DATABASE_PORT: 5432
@@ -40,7 +40,7 @@ services:
db: db:
condition: service_healthy condition: service_healthy
volumes: volumes:
- ./backend/uploads:/app/uploads - ./uploads:/app/uploads
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8765/"] test: ["CMD", "curl", "-f", "http://localhost:8765/"]
interval: 30s interval: 30s
+1 -1
View File
@@ -1,4 +1,4 @@
module bookmann module shelf
go 1.25 go 1.25
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
package handlers package handlers
import ( import (
"bookmann/internal/database" "shelf/internal/database"
"fmt" "fmt"
"net/http" "net/http"
"time" "time"
@@ -1,8 +1,8 @@
package handlers package handlers
import ( import (
"bookmann/internal/database" "shelf/internal/database"
"bookmann/internal/services" "shelf/internal/services"
"context" "context"
"net/http" "net/http"
"strconv" "strconv"
@@ -1,7 +1,7 @@
package services package services
import ( import (
"bookmann/internal/database" "shelf/internal/database"
"context" "context"
"fmt" "fmt"
"io/fs" "io/fs"
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.