Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2025 gopl.dev
Copyright (c) 2026 gopl.dev Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
100 changes: 100 additions & 0 deletions SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Development Setup

Guide on how to set up your local instance of [gopl-dev/server](https://github.com/gopl-dev/server).

## Prerequisites

- [Git](https://git-scm.com/)
- [Go 1.26+](https://golang.org/dl/)
- [PostgreSQL 18](https://www.postgresql.org/download/)

If you are working on the frontend, you will also need:
- [templ](https://templ.guide/)
- [TailwindCSS](https://tailwindcss.com/)

## Setup

1. **Clone the repo:**
```bash
git clone https://github.com/gopl-dev/server.git
cd server
```

2. **Run the setup wizard:**
```bash
go run ./cmd/setup_wizard/main.go
```
This tool checks your DB connection and creates the necessary configuration files.

<details>
<summary>Manual setup (alternative):</summary>

1. Copy `config.sample.yaml` to `.config.yaml` and edit the values. At least a DB connection is required for startup.
> **Tip:** It's recommended to use a `_local_dev` suffix for the DB name (e.g., `myapp_local_dev`). The reset tool uses this convention to prevent accidental data loss.
2. Create test configurations in:
- `test/api_test/.config.yaml`
- `test/service_test/.config.yaml`
- `test/worker_test/.config.yaml`
3. For each test config, update the following:
- Set a test DB connection (e.g., `myapp_local_dev_test`).
- `email.driver: "test"`
- `tracing.enabled: false`
- `files.storage_driver: "in-memory-fs"`

</details>

3. **Run tests:**
```bash
go test ./...
```

4. **Start the server:**
```bash
go run ./cmd/server/main.go
```

## Seeding
To populate the database with test data, use the CLI tool:
```bash
go run ./cmd/cli/main.go sd
```
By default, it seeds all available entities. You can specify a specific entity and count:
* **Example:** `go run ./cmd/cli/main.go sd users 1000` (creates 1000 users).
* **Help:** `go run ./cmd/cli/main.go ? sd` for detailed options.

## Environment Reset
If you need a clean slate, run:
```bash
go run ./cmd/cli/main.go rde
```
This command recreates the database, applies migrations, and creates a default user.

---

## Toolchain

### templ — Live Watch & Reload
```bash
go tool templ generate --watch --proxy="http://localhost:8080" --cmd="go run ./cmd/server/main.go"
```

### TailwindCSS — Watch
```bash
tailwindcss -i ./frontend/assets/input.css -o ./frontend/assets/output.css --watch
```

### Linting
Requires [golangci-lint](https://golangci-lint.run/welcome/install/#local-installation)
```bash
golangci-lint run
```

### OpenAPI & Swagger
Requires [swag](https://github.com/swaggo/swag)
```bash
# Format Swagger directives
swag fmt --dir server/handler

# Generate specifications
swag init --parseDependency --parseDepth 1 --dir server/handler -g handler.go -o server/docs
```
19 changes: 9 additions & 10 deletions WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,22 @@ for simplicity, I keep todo list and progress here for now

**Before release**:
- [ ] Review "TODO!"s
- [ ] Homepage
- [x] Homepage
- [ ] Licence
- [ ] RELEASE
- [x] RELEASE
---

AFTER RELEASE CHECKLIST:
- [x] Setup & test email
- [x] Setup & test auth & registration with google
- [x] Setup & test auth & registration with github
- [x] Setup & test tracing
- [ ] Enable linting and testing on CI/CD
- [ ] Disable push to main without MR
Tweaks & fixes:
- [ ] Add description to upload book cover input
- [ ] When book cover upload ends with error, another cannot be added

NEXT:

TODO:
- [ ] Resend verification link (Sometimes email is lost in somewhere between the woods (Not mailman to blame))
- [ ] Create new topic when creating/editing entity
- [ ] Pages
- [ ] Add meta to page (Who created, last edit by who and list of activities on page (api call))
- [ ] Content chips
- [ ] Books
- [ ] Add subtitle
- [ ] Sort
Expand Down Expand Up @@ -53,3 +51,4 @@ NEXT:
- [ ] Let user continue work on reject entity and proposed changes
- [ ] Review "delete account" test. Right now, it passes even if models belonging to the user still exist.
- [ ] Order of props when reviewing changes and public diffs should be constant and predefined
- [ ] Sitemap
14 changes: 8 additions & 6 deletions app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@ type ConfigT struct {

Email struct {
// Driver can be: smtp or test
Driver string `yaml:"driver"`
From string `yaml:"from"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Username string `yaml:"username"`
Password string `yaml:"password"` //nolint:gosec
Driver string `yaml:"driver"`
From string `yaml:"from"`
SMTP struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Username string `yaml:"username"`
Password string `yaml:"password"` //nolint:gosec
} `yaml:"smtp"`
} `yaml:"email"`

Session struct {
Expand Down
16 changes: 16 additions & 0 deletions app/repo/page_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ func (r *Repo) GetPageByPublicID(ctx context.Context, publicID string) (*ds.Page
return page, err
}

// GetPagesByPublicID retrieves pages by given public ID.
func (r *Repo) GetPagesByPublicID(ctx context.Context, publicIDs ...string) (pages []ds.Page, err error) {
_, span := r.tracer.Start(ctx, "GetPageByPublicID")
defer span.End()

if len(publicIDs) == 0 {
return
}

pages = make([]ds.Page, 0, len(publicIDs))
const query = `SELECT * FROM entities e JOIN pages p USING (id) WHERE e.public_id = ANY($1) AND e.type = $2 AND e.deleted_at IS NULL`

err = pgxscan.Select(ctx, r.getDB(ctx), &pages, query, publicIDs, ds.EntityTypePage)
return
}

// GetPageByID retrieves a page by its ID.
func (r *Repo) GetPageByID(ctx context.Context, id ds.ID) (*ds.Page, error) {
_, span := r.tracer.Start(ctx, "GetPageByID")
Expand Down
10 changes: 9 additions & 1 deletion app/service/page_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,22 @@ import (
"github.com/gopl-dev/server/email"
)

// GetPageByPublicID retrieves a page by its public identifier.
// GetPageByPublicID retrieves a page by its public ID.
func (s *Service) GetPageByPublicID(ctx context.Context, id string) (*ds.Page, error) {
ctx, span := s.tracer.Start(ctx, "GetPageByPublicID")
defer span.End()

return s.db.GetPageByPublicID(ctx, id)
}

// GetPagesByPublicID retrieves pages by given public IDs.
func (s *Service) GetPagesByPublicID(ctx context.Context, id ...string) ([]ds.Page, error) {
ctx, span := s.tracer.Start(ctx, "GetPagesByPublicID")
defer span.End()

return s.db.GetPagesByPublicID(ctx, id...)
}

// GetPageByID retrieves a page by its ID from the database.
func (s *Service) GetPageByID(ctx context.Context, id ds.ID) (*ds.Page, error) {
ctx, span := s.tracer.Start(ctx, "GetBookByID")
Expand Down
Loading