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:
+2
-3
@@ -197,9 +197,8 @@ coverage/
|
||||
bruno/collection.bru
|
||||
|
||||
# Generated static assets (SvelteKit build output)
|
||||
backend/cmd/server/static/
|
||||
cmd/server/static/
|
||||
|
||||
# Built binaries
|
||||
main
|
||||
backend/main
|
||||
backend/server
|
||||
server
|
||||
|
||||
@@ -6,20 +6,17 @@ WORKDIR /app
|
||||
# Install sqlc
|
||||
RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
||||
|
||||
# Copy backend source code
|
||||
COPY backend/ ./backend/
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Download dependencies
|
||||
RUN cd backend && go mod tidy
|
||||
RUN go mod tidy
|
||||
|
||||
# Generate sqlc code
|
||||
RUN cd backend && sqlc generate
|
||||
|
||||
# Copy templates (updated for new homepage)
|
||||
COPY backend/templates ./backend/templates
|
||||
RUN sqlc generate
|
||||
|
||||
# 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
|
||||
FROM alpine:latest
|
||||
@@ -31,10 +28,10 @@ WORKDIR /root/
|
||||
COPY --from=builder /app/main .
|
||||
|
||||
# Copy migrations (if needed for initialization)
|
||||
COPY --from=builder /app/backend/migrations ./migrations
|
||||
COPY --from=builder /app/migrations ./migrations
|
||||
|
||||
# Copy templates
|
||||
COPY --from=builder /app/backend/templates ./templates
|
||||
COPY --from=builder /app/templates ./templates
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8765
|
||||
@@ -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
|
||||
|
||||
@@ -35,7 +35,7 @@ A self-hosted ebook management system built with Go, PostgreSQL, HTMX, and Tailw
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
3. Access the application at http://localhost:8765
|
||||
3. Access Shelf at http://localhost:8765
|
||||
|
||||
### Database
|
||||
|
||||
@@ -46,16 +46,15 @@ PostgreSQL runs on port 5432 with default credentials:
|
||||
|
||||
## Development
|
||||
|
||||
### Backend
|
||||
### Development
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
go mod tidy
|
||||
go run github.com/sqlc-dev/sqlc/cmd/sqlc@latest generate
|
||||
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:**
|
||||
- **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
|
||||
│ ├── internal/
|
||||
│ │ ├── config/ # Configuration management
|
||||
│ │ ├── database/ # Database connection and queries
|
||||
│ │ ├── handlers/ # HTTP handlers (auth + ebooks)
|
||||
│ │ └── services/ # Business logic services (ebook scanner)
|
||||
│ ├── migrations/ # Database migrations
|
||||
│ ├── templates/ # Go HTML templates with HTMX
|
||||
│ └── sqlc.yaml # sqlc configuration
|
||||
├── cmd/server/ # Application entry point
|
||||
├── internal/
|
||||
│ ├── config/ # Configuration management
|
||||
│ ├── database/ # Database connection and queries
|
||||
│ ├── handlers/ # HTTP handlers (auth + ebooks)
|
||||
│ └── services/ # Business logic services (ebook scanner)
|
||||
├── migrations/ # Database migrations
|
||||
├── templates/ # Go HTML templates with HTMX
|
||||
├── 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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
@@ -3,14 +3,14 @@ version: "3.8"
|
||||
services:
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
container_name: bookmann_db
|
||||
container_name: shelf_db
|
||||
environment:
|
||||
POSTGRES_DB: ebookdb
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: ${DBPASS}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./backend/migrations:/docker-entrypoint-initdb.d
|
||||
- ./migrations:/docker-entrypoint-initdb.d
|
||||
ports:
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
@@ -21,11 +21,11 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
backend:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./backend/Dockerfile
|
||||
container_name: bookmann
|
||||
dockerfile: ./Dockerfile
|
||||
container_name: shelf
|
||||
environment:
|
||||
DATABASE_HOST: db
|
||||
DATABASE_PORT: 5432
|
||||
@@ -40,7 +40,7 @@ services:
|
||||
db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./backend/uploads:/app/uploads
|
||||
- ./uploads:/app/uploads
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8765/"]
|
||||
interval: 30s
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module bookmann
|
||||
module shelf
|
||||
|
||||
go 1.25
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bookmann/internal/database"
|
||||
"shelf/internal/database"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -1,8 +1,8 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bookmann/internal/database"
|
||||
"bookmann/internal/services"
|
||||
"shelf/internal/database"
|
||||
"shelf/internal/services"
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -1,7 +1,7 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bookmann/internal/database"
|
||||
"shelf/internal/database"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
|
Before Width: | Height: | Size: 661 B After Width: | Height: | Size: 661 B |
Reference in New Issue
Block a user