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
|
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
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
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,4 +1,4 @@
|
|||||||
module bookmann
|
module shelf
|
||||||
|
|
||||||
go 1.25
|
go 1.25
|
||||||
|
|
||||||
@@ -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"
|
||||||
|
Before Width: | Height: | Size: 661 B After Width: | Height: | Size: 661 B |
Reference in New Issue
Block a user