Skip to content

Latest commit

 

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Wishpage

A simple wishlist management application built with Go, HTMX, and SQLite. Features server-side rendering, minimal JavaScript, collapsible family sections, and a clean responsive UI with a fresh mint color scheme.

Features

  • 📝 Create and manage wishlist items for family members
  • 👥 Add and organize family members with collapsible sections
  • ✅ Mark items as reserved/available with pure CSS dropdown (no extra HTTP requests)
  • 🔐 Password-protected editing mode
  • 📧 Email notifications for additions, reservation changes, and deletions
  • 🎨 Clean, responsive UI with fresh mint color scheme
  • ⚡ HTMX for dynamic updates with minimal page reloads
  • 🔒 Session-based authentication
  • 📦 Uses templ for type-safe Go templates

Quick Start

Prerequisites

  • Go 1.26 or higher
  • A C compiler toolchain (required because the go-sqlite3 driver uses CGO)
  • Node.js 20.19 or higher and npm (for CSS linting and formatting, optional - see Development Tools section)
  • golangci-lint (optional, for development - see Development Tools section)

Installation

  1. Clone the repository:
git clone https://github.com/macurovc/wishpage.git
cd wishpage
  1. Install dependencies:
go mod download
  1. Set up environment variables:
# Required for edit mode
export EDIT_PASSWORD="your-secure-password"

# Optional configuration
export DATABASE_PATH="/path/to/wishlist.db"
export PORT="3002"

# Optional: Email notifications (see Email Notifications section)
export EMAIL_USER="your-email@gmail.com"
export EMAIL_PASS="your-app-password"
export EMAIL_TO="recipient@example.com"
  1. Run the application:
go run .
  1. Open your browser to http://localhost:3002

Configuration

Environment Variables

Variable Description Default
PORT Server port 3002
DATABASE_PATH Path to SQLite database ./wishlist.db (an existing legacy temp database is detected automatically)
EDIT_PASSWORD Password for edit mode (required for editing)
EMAIL_HOST SMTP server hostname smtp.gmail.com
EMAIL_PORT SMTP server port 587
EMAIL_USER SMTP username (required for email)
EMAIL_PASS SMTP password (required for email)
EMAIL_FROM Sender email address Same as EMAIL_USER
EMAIL_TO Recipient email address (required for email)

Email Notifications

The application sends automatic email notifications for these wishlist changes:

  • Items added, reserved, or deleted
  • Family members added or deleted

Important: Email is completely optional. If not configured, the application works normally without sending notifications.

Quick Setup (Gmail)

  1. Enable 2-Factor Authentication on your Google account
  2. Generate an App Password at https://myaccount.google.com/apppasswords
  3. Set environment variables:
export EMAIL_USER=your-email@gmail.com
export EMAIL_PASS=your-16-char-app-password
export EMAIL_TO=recipient@example.com

Other Email Providers

Office 365 / Outlook:

export EMAIL_HOST=smtp.office365.com
export EMAIL_PORT=587
export EMAIL_USER=your-email@outlook.com
export EMAIL_PASS=your-password
export EMAIL_TO=recipient@example.com

Custom SMTP Server:

export EMAIL_HOST=smtp.example.com
export EMAIL_PORT=587  # or 465 for TLS
export EMAIL_USER=your-username
export EMAIL_PASS=your-password
export EMAIL_TO=recipient@example.com

Email Troubleshooting

"Email service verification failed"

  • Check SMTP hostname and port are correct
  • Verify firewall allows outbound SMTP connections
  • Corporate networks may block SMTP ports

"Authentication failed"

  • Gmail: Use App Password, not your regular password
  • Verify username and password are correct
  • Check email provider security settings

Emails not received

  • Check spam/junk folder
  • Verify EMAIL_TO address is correct
  • Check server logs for errors
  • Some providers have rate limits

Development

Development Tools

The project includes a Makefile with convenient commands for development:

# Validate everything (format, lint, test, tidy) - run this before committing!
make validate

# Show all available commands
make help

# Individual Go commands:
make fmt         # Format all Go code with go fmt
make lint        # Run golangci-lint with comprehensive checks
make test        # Run all tests with race detection and coverage
make tidy        # Clean up go.mod and verify dependencies

# CSS commands:
make fmt-css        # Format CSS files with Prettier
make fmt-css-check  # Check CSS formatting without modifying files
make lint-css       # Run Stylelint on CSS files
make lint-css-fix   # Run Stylelint and auto-fix CSS issues

# Utility commands:
make clean          # Remove build artifacts, caches, and node_modules
make check-deps     # Verify all required tools are installed
make npm-install    # Install npm dependencies for CSS tools

Prerequisites for development tools:

Go Tools:

  • golangci-lint is required for make lint and make validate
  • Install on macOS: brew install golangci-lint
  • Install on Linux: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
  • See installation guide for other platforms

CSS Tools:

  • Node.js 20.19 or higher and npm are required for CSS linting and formatting
  • Install from nodejs.org or via package manager
  • Run npm install or make npm-install to install Stylelint and Prettier
  • The CI pipeline automatically checks CSS formatting and linting

Recommended workflow:

  • Run make validate before committing changes
  • This ensures code is formatted (Go and CSS), passes linting, and all tests pass
  • The Go linter runs configured checks for code quality, security, and style
  • The CSS linter enforces best practices and prevents common mistakes

CI/CD Pipeline:

  • GitHub Actions automatically runs on all pushes and pull requests
  • Parallel jobs check Go tests, Go linting, CSS formatting, CSS linting, and build
  • All checks must pass before merging
  • Coverage reports are uploaded to Codecov

Running Tests

# Run all tests
go test ./...

# Run tests with verbose output
go test -v ./...

# Run specific test
go test -v -run TestEmailService

# Run tests with coverage (or use 'make test')
go test -v -race -coverprofile=coverage.out ./...

Generating Templates

This project uses templ for type-safe HTML templates. After modifying .templ files, regenerate the Go code:

# Install or update to the templ version pinned by go.mod
go install "github.com/a-h/templ/cmd/templ@$(go list -m -f '{{.Version}}' github.com/a-h/templ)"

# Generate template code
templ generate

Note: The generated *_templ.go files are checked into version control. Always regenerate them after editing .templ files and include both the .templ and *_templ.go files in your commits.

Project Structure

wishpage/
├── main.go                  # Application entry point and server setup
├── auth.go                  # Authentication and session management
├── database.go              # Database initialization
├── handlers.go              # Main page handlers
├── items.go                 # Item CRUD operations
├── family_members.go        # Family member CRUD operations
├── email_service.go         # Email notification service
├── helpers.go               # Utility functions
├── models/
│   └── models.go           # Data models
├── templates/
│   ├── layout.templ        # Base layout template
│   ├── view.templ          # Public view templates
│   ├── edit.templ          # Edit mode templates
│   ├── login.templ         # Login page template
│   └── error.templ         # Error template
├── static/
│   ├── css/
│   │   └── styles.css      # Application styles
│   └── js/
│       ├── htmx.min.js     # Vendored HTMX runtime
│       └── htmx.LICENSE.txt
├── Makefile                 # Development commands (validate, test, lint, fmt)
├── .golangci.yml            # Go linter configuration
├── .stylelintrc.json        # CSS linter configuration (Stylelint)
├── .prettierrc.json         # CSS formatter configuration (Prettier)
├── package.json             # Node.js dependencies for CSS tooling
├── go.mod                   # Go module dependencies
└── *_test.go               # Test files

API Endpoints

Public Endpoints (No Authentication Required)

Page Routes:

  • GET / - Landing page with collapsible sections for all family members and their items
  • GET /login - Login page
  • GET /static/* - Static files (CSS, JS, images)

API Routes:

  • GET /api/items - Get all items (supports ?family_member_id=X filter)
  • PUT /api/items/{id}/reserve - Mark item as reserved, returns updated item card HTML for HTMX
  • GET /api/family_members - Get all family members

Authentication:

  • POST /api/login - Login with password, returns session cookie
  • POST /api/logout - Logout and clear session

These authentication endpoints do not require an existing session.

Protected Endpoints (Require Authentication)

Page Routes:

  • GET /edit - Edit mode page (full list of items and family members)

Item Management:

  • POST /api/items - Create new item (requires name, family_member_id; optional link, price)
  • PUT /api/items/{id} - Update item details
  • DELETE /api/items/{id} - Delete item
  • PUT /api/items/{id}/unreserve - Mark item as unreserved (admin only)

Family Member Management:

  • POST /api/family_members - Create family member
  • PUT /api/family_members/{id} - Update family member name
  • DELETE /api/family_members/{id} - Delete family member (cascades to items)

Features in Detail

HTMX Integration

The application bundles HTMX 1.9.10 into the executable for dynamic updates without full page reloads:

  • Item reservation uses pure CSS <details> dropdown for confirmation (no HTTP request until confirmed)
  • Reservation confirmation updates just the single item card in-place
  • Adding/deleting items updates the list dynamically
  • Family member management happens without page navigation
  • Collapsible family sections use native HTML <details> elements
  • Smooth transitions and feedback

Session Management

  • Secure session tokens with automatic expiration (24 hours)
  • Cookie-based authentication (HttpOnly, SameSite=Strict)
  • Background cleanup of expired sessions (runs hourly)
  • Concurrent access handling with thread-safe session store
  • Constant-time password comparison to prevent timing attacks

Database

  • SQLite for simplicity and portability
  • Foreign key constraints enabled on every connection
  • Cascade deletes for family members (deleting a member removes all their items)
  • Automatic schema creation on first run
  • Items automatically ordered by price (items without price shown first)

Backups

All application data lives in one SQLite file. Back it up periodically with SQLite's online backup command or a snapshot of the persistent volume:

sqlite3 /path/to/wishlist.db ".backup '/path/to/wishlist-backup.db'"

Avoid copying the live database file directly while the service may be writing to it.

Email Notifications

When configured, the application sends HTML-formatted email notifications for additions, reservation changes, and deletions:

  • Items: added, reserved/unreserved, deleted (includes name, family member, price, link)
  • Family Members: added or deleted

The email service supports:

  • Both TLS (port 465) and STARTTLS (port 587) connections
  • Gmail, Office 365, and custom SMTP servers
  • Graceful degradation (app continues if email fails)
  • Automatic HTML escaping for security

Security Considerations

  • Password-protected edit mode with constant-time comparison
  • Session-based authentication with automatic expiration (24 hours)
  • HTTPS-only session cookies with Secure, HttpOnly, and SameSite=Strict attributes
  • Input validation and sanitization
  • SQL injection prevention through parameterized queries
  • Email credentials stored in environment variables (never committed)
  • HTML escaping in email notifications prevents injection attacks

Cookie Security Behavior

Session cookies always use the Secure, HttpOnly, and SameSite=Strict attributes. Login and edit mode therefore require an HTTPS connection between the browser and the public application URL.

This works with Cloudflare Tunnel and other HTTPS reverse proxies even when the proxy connects to the container over HTTP: the browser sees HTTPS and applies the cookie security policy there.

Direct HTTP remains available for the public wishlist, but authenticated edit mode requires HTTPS. For local development, modern browsers normally permit Secure cookies on http://localhost; use an HTTPS proxy for other hostnames.

Deployment

This application uses Go's embed directive to include all static files (CSS, JS, images) directly in the binary. This means you only need to deploy a single executable file - no separate static directory required!

Using Docker

  1. Validate the code before building (recommended):

    make validate
  2. Build the Docker image:

    docker build -t wishpage .
  3. Run the Docker container:

    docker run -d \
      -p 3002:3002 \
      -e EDIT_PASSWORD=your_super_secret_password \
      -e DATABASE_PATH=/data/wishlist.db \
      -v $(pwd)/data:/data \
      --name wishpage \
      wishpage
    • The -e flag sets environment variables
    • The -v flag mounts a local directory (./data) into the container at /data for database persistence
    • Without the volume mount, your data will be lost if the container is removed
  4. Access the application:

    The public wishlist will be accessible at http://localhost:3002. Expose production deployments through HTTPS, such as a Cloudflare Tunnel, to use login and edit mode.

Manual Deployment

  1. Validate the code before building:
make validate

This ensures your code is properly formatted, passes all linter checks, and all tests pass.

  1. Build the application:
go build -o wishpage

This creates a single, self-contained binary with all static files embedded.

  1. Copy the binary to your server:
scp wishpage your-server:/opt/wishpage/
  1. Set environment variables on your server:
export EDIT_PASSWORD="your-secure-password"
export DATABASE_PATH="/var/lib/wishpage/wishlist.db"
# Optional: Email configuration
export EMAIL_USER="your-email@gmail.com"
export EMAIL_PASS="your-app-password"
export EMAIL_TO="recipient@example.com"
  1. Run the binary:
./wishpage

That's it! No need to copy the static/ directory - it's already in the binary.

Additional recommendations:

  • Consider using a process manager like systemd or supervisor (see example below)
  • Put behind a reverse proxy (nginx/Caddy) for HTTPS

Example systemd Service

Create /etc/systemd/system/wishpage.service:

[Unit]
Description=Wishpage Application
After=network.target

[Service]
Type=simple
User=wishpage
WorkingDirectory=/opt/wishpage
ExecStart=/opt/wishpage/wishpage
Environment="EDIT_PASSWORD=your-password"
Environment="DATABASE_PATH=/var/lib/wishpage/wishlist.db"
Environment="PORT=3002"
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Then:

sudo systemctl daemon-reload
sudo systemctl enable wishpage
sudo systemctl start wishpage

Troubleshooting

Database Issues

  • Default location: If DATABASE_PATH is not set, the database is created as ./wishlist.db in the working directory
  • Legacy location: If no local database exists but the previous temp-directory database does, the application continues using it and logs a migration notice
  • Ensure the database file path is writable
  • Check file permissions (the application needs read/write access)
  • Verify a C compiler toolchain is installed so the go-sqlite3 driver can build

Email Not Working

  • Verify all required variables are set: EMAIL_USER, EMAIL_PASS, EMAIL_TO
  • Check server logs for detailed error messages
  • Look for "Email service is configured correctly and ready to send messages." on startup
  • Test with a simple action (add an item) and check spam folder
  • Ensure firewall allows outbound SMTP connections

Authentication Problems

  • Verify EDIT_PASSWORD is set
  • Verify the browser is using the HTTPS application URL
  • Clear browser cookies and try again
  • Check server logs for session-related errors

Contributing

Contributions are welcome! Please ensure:

  • All validation checks pass: make validate
  • Templates are regenerated: templ generate (if you modified .templ files)
  • CSS linting passes if you modify styles: make lint-css

The make validate command will automatically:

  • Format your Go code with go fmt
  • Format your CSS with Prettier
  • Run comprehensive linting checks (Go and CSS)
  • Run all tests with race detection
  • Verify dependencies are clean

License

See the LICENSE file in the root of the repository.

About

Self-hosted wishlist for family and friends

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages