# Goyco

[![Go Version](https://img.shields.io/badge/Go-1.25.0-blue.svg)](https://golang.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-18-blue.svg)](https://www.postgresql.org/)
[![License](https://img.shields.io/badge/License-GPLv3-green.svg)](LICENSE)

Goyco is a Y Combinator-style news aggregation platform built in Go. It will allow you to host your own news aggregation platform, with a modern-ish UI and a fully functional REST API.

You have the flexibility to personalize the UI with your community’s name, and you can deploy Goyco on your own server, in the cloud or anywhere else you want. The rest of the features is described below.

It's free (as in free beer), open-source and sadly not (yet) fully customizable.

By the way, the web interface is living proof that I'm not a front-end developer — but hey, it loads! Please, don't judge me too harshly.

You can get a preview of the application through the [screenshots](screenshots).

## Architecture

### Technology Stack

It's basically pure Go (using Chi router), raw CSS and PostgreSQL 18.

## Quick Start

### Prerequisites

- Go 1.26.0 or later
- PostgreSQL 18 or later
- SMTP server for email functionality

### Setup PostgreSQL database and user

If you're not using a managed database service or a docker container, we wrote a script to help you setup a local PostgreSQL database along with the `goyco` user.

```bash
scripts/setup-postgres.sh
```

It'll prompt you for the password for the `goyco` user and then setup the database and user.

### Installation

In order to install Goyco on your system, you can use the following commands run as root:

```bash
make
make install
cp .env.example /opt/goyco/.env # edit it to add your own parameters
make migrations
```

This will:

- Create system user and group
- Install the binary to `/opt/goyco/bin`
- Install the static assets to `/opt/goyco/internal/static/`
- Install the templates to `/opt/goyco/internal/templates/`
- Install the license to `/usr/share/licenses/goyco/`
- Install the documentation to `/usr/share/doc/goyco/`
- Run database migrations

Finally, polish permissions and enable and start the service:

```bash
chown -R goyco:goyco /opt/goyco
systemctl enable --now goyco
```

### Deploy using Docker (compose)

```bash
# Build the image
make docker-image

# Run with Docker Compose (from project root)
docker compose --env-file .env -f docker/compose.prod.yml up -d

# migrate the database
docker compose --env-file .env -f docker/compose.prod.yml exec app goyco migrate
```

Once you built the image, you can also run the docker container itself with right environment variables:

```bash
docker run -d --name goyco -p 8080:8080 --env-file .env --restart unless-stopped goyco:latest
```

## Configuration

Goyco uses environment variables for configuration.

Key settings include:

### Database Configuration

```bash
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=your_password
DB_NAME=goyco
DB_SSLMODE=disable
```

### Server Configuration

```bash
SERVER_HOST=0.0.0.0
SERVER_PORT=8080
```

### JWT Configuration

```bash
JWT_SECRET=your-secure-secret-key
JWT_EXPIRATION=1
JWT_REFRESH_EXPIRATION=168
```

Refresh tokens rotate on each successful refresh, the previous refresh token is invalidated.

### SMTP Configuration

```bash
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=your-email@example.com
SMTP_PASSWORD=your-password
SMTP_FROM=noreply@example.com
```

### Application Settings

```bash
APP_BASE_URL=https://goyco.example.com
ADMIN_EMAIL=admin@example.com
TITLE=Goyco
DEBUG=false
BCRYPT_COST=10
```

And also CLI configuration (for json output), rate limiting configuration, etc. Be sure to check `.env.example` for more details.

**Warning**: Don't underestimate RATE_LIMIT parameters. You might want to set them to higher values if you are running a production environment.

### Reverse Proxy Configuration

To use a reverse proxy in order to offload the SSL termination (for example), here's a sample nginx configuration:

```nginx
upstream goyco {
   server 10.200.1.11:8080;
}

server {
    listen 443 ssl;
    server_name goyco.example.com;

    ssl_certificate /etc/letsencrypt/live/goyco.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/goyco.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers 'ECDHE+AESGCM:CHACHA20';

    location / {
        proxy_pass http://goyco;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
```

### Security headers and middleware ordering

When `RATE_LIMIT_TRUST_PROXY` is `true`, the application trusts `X-Forwarded-Proto` (among other forwarded headers) for HSTS and client IP derivation. Configure your reverse proxy to set trustworthy values and strip or overwrite any client-supplied forwarding headers before they reach the app.

Response caching uses `DecompressionMiddleware` before `DefaultRequestSizeLimitMiddleware`. Decompression is additionally capped internally so gzipped bodies cannot expand without limit before other limits apply.

In production (`GOYCO_ENV=production`), Swagger UI is not registered unless `SWAGGER_ENABLED=true`.

## API Documentation

The API is fully documented with Swagger (enabled for non-production environments, or when `SWAGGER_ENABLED=true`).

Once running, visit:

- **Swagger UI**: `https://goyco.example.com/swagger/index.html` (not served in production unless explicitly enabled via `SWAGGER_ENABLED`)

You can also use `curl` to get the API info, health check and even metrics:

```bash
curl -X GET https://goyco.example.com/api
curl -X GET https://goyco.example.com/health
curl -X GET https://goyco.example.com/metrics
```

You can also use `jq` to parse the JSON responses:

```bash
curl -X GET https://goyco.example.com/api | jq
curl -X GET https://goyco.example.com/health | jq
curl -X GET https://goyco.example.com/metrics | jq
```

It'll be more readable and easier to parse.

### Key Endpoints

#### Authentication

- `POST /api/auth/register` - Register new user
- `POST /api/auth/login` - Login user
- `GET /api/auth/confirm` - Confirm email address
- `POST /api/auth/resend-verification` - Resend verification email
- `POST /api/auth/forgot-password` - Request password reset
- `POST /api/auth/reset-password` - Reset password
- `GET /api/auth/me` - Get current user profile (protected)
- `POST /api/auth/logout` - Logout user (protected)
- `POST /api/auth/refresh` - Refresh access token (rotates refresh token)
- `POST /api/auth/revoke` - Revoke a refresh token (protected)
- `POST /api/auth/revoke-all` - Revoke all refresh tokens for the current user (protected)
- `PUT /api/auth/email` - Update email address (protected)
- `PUT /api/auth/username` - Update username (protected)
- `PUT /api/auth/password` - Update password (protected)
- `DELETE /api/auth/account` - Request account deletion (protected)
- `POST /api/auth/account/confirm` - Confirm account deletion

#### Posts

- `GET /api/posts` - List posts
- `GET /api/posts/search` - Search posts
- `GET /api/posts/title` - Fetch title from URL
- `GET /api/posts/{id}` - Get specific post
- `POST /api/posts` - Create post (protected)
- `PUT /api/posts/{id}` - Update post (protected)
- `DELETE /api/posts/{id}` - Delete post (protected)

#### Voting

- `POST /api/posts/{id}/vote` - Cast vote (protected)
- `DELETE /api/posts/{id}/vote` - Remove vote (protected)
- `GET /api/posts/{id}/vote` - Get current user's vote (protected)
- `GET /api/posts/{id}/votes` - Get all votes for post

#### Users

- `GET /api/users` - List all users (protected)
- `POST /api/users` - Create new user (protected)
- `GET /api/users/{id}` - Get specific user
- `GET /api/users/{id}/posts` - Get user's posts (protected)

## CLI Commands

Goyco includes a comprehensive CLI for administration:

```bash
# Server management
./bin/goyco run                    # Run server in foreground
./bin/goyco start                  # Start server as daemon
./bin/goyco stop                   # Stop daemon
./bin/goyco status                 # Check server status
./bin/goyco health                 # Check application and dependencies health

# Database management
./bin/goyco migrate                # Run database migrations
./bin/goyco seed database          # Seed database with sample data
./bin/goyco seed database --posts 100 --users 10 --upvote-ratio 0.5  # Customize seeding

# User management
./bin/goyco user create            # Create new user
./bin/goyco user list              # List users
./bin/goyco user update            # Update user
./bin/goyco user delete            # Delete user
./bin/goyco user lock              # Lock user
./bin/goyco user unlock            # Unlock user

# Post management
./bin/goyco post list              # List posts
./bin/goyco post search            # Search posts
./bin/goyco post delete            # Delete post

# Maintenance
./bin/goyco prune posts             # Hard delete posts of deleted users
./bin/goyco prune users             # Hard delete users
./bin/goyco prune all               # Hard delete all users and posts
```

### JSON Output

All CLI commands support JSON output for easier parsing and integration with scripts. Use the `--json` flag to enable structured JSON output:

```bash
# Get JSON output
./bin/goyco --json user list
./bin/goyco --json post list
./bin/goyco --json status
./bin/goyco --json health          # Check health with JSON output

# Example: Parse JSON output with jq
./bin/goyco --json user list | jq '.users[0].username'
./bin/goyco --json status | jq '.status'
```

You can also set JSON output as the default by setting the `CLI_JSON_OUTPUT` environment variable to `true` in your `.env` file:

```bash
CLI_JSON_OUTPUT=true
```

When set, all CLI commands will output JSON by default.

**Note for destructive operations**: When using `--json` with `prune` commands, you must also use the `--yes` flag to skip interactive confirmation prompts:

```bash
./bin/goyco --json prune posts --yes
./bin/goyco --json prune users --yes --with-posts
./bin/goyco --json prune all --yes
```

## Development

### Get the sources

```bash
git clone https://git.kharec.info/Kharec/goyco.git
cd goyco
```

Note: if you mean to contribute to the project, please fork the repository first.

### Create a `.env` file

```bash
cp .env.example .env
```

Customize the `.env` file to add your own parameters.

Here's the SMTP configuration for `mailpit` (for development purposes):

```bash
# SMTP Configuration
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_FROM=noreply@goyco.xiz
```

While you're hacking around, be sure to set `SERVER_HOST` and `SERVER_PORT` in order to be able to access the application from your browser. Also, beware of `APP_BASE_URL` parameter.

### Install and manage development dependencies

```bash
make build-deps
```

It will start a PostgreSQL database and a [mailpit](https://mailpit.axllent.org/) server in order to test the application.

The web front of mailpit server will be available at `http://localhost:8025` and will allow you to view the emails sent by the application. No matter the recipient, all emails will be captured by `mailpit`.

Once you're done, you can use `make clean-deps` to stop the dependencies and remove the containers and volumes.

### Build the application

```bash
make
```

The build process will create the binary in the `bin/` directory.

Then, make the migrations:

```bash
./bin/goyco migrate
```

It will create the necessary tables in the database.

### Run the application

```bash
./bin/goyco run
```

It will start the application in development mode. You can also run it as a daemon:

```bash
./bin/goyco start
```

Then, use `./bin/goyco` to manage the application and notably to seed the database with sample data.

### Testing

```bash
# Run all tests
make test

# Run specific test suites
make unit-tests
make integration-tests
make e2e-tests

# Run fuzz testing (can take a bit of CPU and time)
make fuzz-tests
```

### Code Quality

```bash
# Format code
make format

# Run linter
make lint
```

Note: `golangci-lint` is set up with `.golangci.yml` file.

### Regenerate Swagger documentation

If you make changes to the API, you can regenerate the swagger documentation by running the following command after modifying the swagger annotations:

```bash
# Regenerate Swagger documentation
make swagger
```

This will regenerate the swagger documentation and update the `docs/swagger.json` and `docs/swagger.yaml` files.

## Roadmap

- [x] migrate cli to urfave/cli
- [ ] use pgx instead of gorm
- [ ] add a ML powered nsfw link detection
- [ ] add a way to run read-only communities
- [ ] migrate raw CSS to UnoCSS

## License

This project is licensed under the GNU General Public License v3.0 or later (GPLv3+). See the [LICENSE](LICENSE) file for details.
