forked from mirrors/pronouns.cc
feat: serve media on /media/, not separate domain
This commit is contained in:
parent
7b7b0ca15b
commit
d3eaaaaa9d
5 changed files with 109 additions and 11 deletions
15
README.md
15
README.md
|
@ -14,16 +14,23 @@ A work-in-progress site to share your names, pronouns, and other preferred terms
|
|||
|
||||
When working on the frontend, run the API and then use `yarn dev` in `frontend/` for hot reloading.
|
||||
|
||||
Note that the Next.js server assumes that the backend listens on `:8080` and MinIO listens on `:9000`.
|
||||
If these ports differ on your development environment, you must edit `next.config.js`.
|
||||
|
||||
## Building
|
||||
|
||||
Run `make backend` to build the API server, then run `yarn build` in `frontend/`.
|
||||
|
||||
## Running
|
||||
|
||||
Both the backend and frontend are expected to run behind a reverse proxy such as [Caddy](https://caddyserver.com/).
|
||||
Both the backend and frontend are expected to run behind a reverse proxy such as [Caddy](https://caddyserver.com/) or nginx.
|
||||
|
||||
The frontend should serve every possible path _except_ anything starting with `/api/`, which should be routed to the backend instead.
|
||||
**Make sure to rewrite requests going to the API server to remove the starting `/api/`.**
|
||||
Every path should be proxied to the frontend, except:
|
||||
|
||||
- `/api/`: this should be proxied to the backend, with the URL being rewritten to remove `/api`
|
||||
(for example, a request to `$DOMAIN/api/v1/users/@me` should be proxied to `localhost:8080/v1/users/@me`)
|
||||
- `/media/`: this should be proxied to your object storage.
|
||||
Make sure to rewrite `/media` into your storage bucket's name.
|
||||
|
||||
## Development
|
||||
|
||||
|
@ -52,7 +59,7 @@ HMAC_KEY="`go run -v ./scripts/genkey`"
|
|||
DATABASE_URL=postgresql://<username>:<pass>@localhost/<database> # PostgreSQL database URL
|
||||
REDIS=localhost:6379
|
||||
PORT=8080 # Port the API will listen on. Default is 8080, this is also default for the backend.
|
||||
MINIO_ENDPOINT=localhost:9000 # This always needs to be set, it *does not* need to point to a MinIO server.
|
||||
MINIO_ENDPOINT=localhost:9000 # This always needs to be set, it *does not* need to point to a running MinIO server.
|
||||
```
|
||||
|
||||
## License
|
||||
|
|
|
@ -148,21 +148,23 @@ func (db *DB) WriteUserAvatar(ctx context.Context,
|
|||
jpegLocation string,
|
||||
err error,
|
||||
) {
|
||||
webpInfo, err := db.minio.PutObject(ctx, db.minioBucket, "/users/"+userID.String()+".webp", webp, -1, minio.PutObjectOptions{
|
||||
_, err = db.minio.PutObject(ctx, db.minioBucket, "/users/"+userID.String()+".webp", webp, -1, minio.PutObjectOptions{
|
||||
ContentType: "image/webp",
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", errors.Wrap(err, "uploading webp avatar")
|
||||
}
|
||||
|
||||
jpegInfo, err := db.minio.PutObject(ctx, db.minioBucket, "/users/"+userID.String()+".jpg", jpeg, -1, minio.PutObjectOptions{
|
||||
_, err = db.minio.PutObject(ctx, db.minioBucket, "/users/"+userID.String()+".jpg", jpeg, -1, minio.PutObjectOptions{
|
||||
ContentType: "image/jpeg",
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", errors.Wrap(err, "uploading jpeg avatar")
|
||||
}
|
||||
|
||||
return webpInfo.Location, jpegInfo.Location, nil
|
||||
return db.baseURL.JoinPath("/media/users/" + userID.String() + ".webp").String(),
|
||||
db.baseURL.JoinPath("/media/users/" + userID.String() + ".jpg").String(),
|
||||
nil
|
||||
}
|
||||
|
||||
func (db *DB) WriteMemberAvatar(ctx context.Context,
|
||||
|
@ -172,19 +174,21 @@ func (db *DB) WriteMemberAvatar(ctx context.Context,
|
|||
jpegLocation string,
|
||||
err error,
|
||||
) {
|
||||
webpInfo, err := db.minio.PutObject(ctx, db.minioBucket, "/members/"+memberID.String()+".webp", webp, -1, minio.PutObjectOptions{
|
||||
_, err = db.minio.PutObject(ctx, db.minioBucket, "/members/"+memberID.String()+".webp", webp, -1, minio.PutObjectOptions{
|
||||
ContentType: "image/webp",
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", errors.Wrap(err, "uploading webp avatar")
|
||||
}
|
||||
|
||||
jpegInfo, err := db.minio.PutObject(ctx, db.minioBucket, "/members/"+memberID.String()+".jpg", jpeg, -1, minio.PutObjectOptions{
|
||||
_, err = db.minio.PutObject(ctx, db.minioBucket, "/members/"+memberID.String()+".jpg", jpeg, -1, minio.PutObjectOptions{
|
||||
ContentType: "image/jpeg",
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", errors.Wrap(err, "uploading jpeg avatar")
|
||||
}
|
||||
|
||||
return webpInfo.Location, jpegInfo.Location, nil
|
||||
return db.baseURL.JoinPath("/media/members/" + memberID.String() + ".webp").String(),
|
||||
db.baseURL.JoinPath("/media/members/" + memberID.String() + ".jpg").String(),
|
||||
nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"codeberg.org/u1f320/pronouns.cc/backend/log"
|
||||
|
@ -26,6 +27,7 @@ type DB struct {
|
|||
|
||||
minio *minio.Client
|
||||
minioBucket string
|
||||
baseURL *url.URL
|
||||
}
|
||||
|
||||
func New() (*DB, error) {
|
||||
|
@ -53,12 +55,18 @@ func New() (*DB, error) {
|
|||
return nil, errors.Wrap(err, "creating minio client")
|
||||
}
|
||||
|
||||
baseURL, err := url.Parse(os.Getenv("BASE_URL"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parsing base URL")
|
||||
}
|
||||
|
||||
db := &DB{
|
||||
Pool: pool,
|
||||
Redis: redis,
|
||||
|
||||
minio: minioClient,
|
||||
minioBucket: os.Getenv("MINIO_BUCKET"),
|
||||
baseURL: baseURL,
|
||||
}
|
||||
|
||||
return db, nil
|
||||
|
|
|
@ -8,8 +8,12 @@ const nextConfig = {
|
|||
return [
|
||||
{
|
||||
source: "/api/:path*",
|
||||
destination: "http://localhost:8080/:path*", // Proxy to Backend
|
||||
destination: "http://localhost:8080/:path*", // proxy to backend
|
||||
},
|
||||
{
|
||||
source: "/media/:path*",
|
||||
destination: "http://localhost:9000/pronouns.cc/:path*", // proxy to media server
|
||||
}
|
||||
];
|
||||
},
|
||||
sentry: {
|
||||
|
|
75
pronounscc.nginx
Normal file
75
pronounscc.nginx
Normal file
|
@ -0,0 +1,75 @@
|
|||
server {
|
||||
server_name example.tld;
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
# For SSL domain validation
|
||||
root /var/www/html;
|
||||
location /.well-known/acme-challenge/ { allow all; }
|
||||
location /.well-known/pki-validation/ { allow all; }
|
||||
location / { return 301 https://$server_name$request_uri; }
|
||||
}
|
||||
|
||||
# For media proxy
|
||||
proxy_cache_path /tmp/pronouns-media-cache levels=1:2 keys_zone=pronouns_media_cache:10m max_size=1g
|
||||
inactive=720m use_temp_path=off;
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name example.tld;
|
||||
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:ssl_session_cache:10m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
|
||||
# To use a Let's Encrypt certificate
|
||||
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
|
||||
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
|
||||
|
||||
# To use Debian/Ubuntu's self-signed certificate (For testing or before issuing a certificate)
|
||||
#ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
|
||||
#ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
|
||||
|
||||
client_max_body_size 8m;
|
||||
|
||||
location ~ ^/api {
|
||||
rewrite ^/api(.*) $1 break;
|
||||
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
location ~ ^/media {
|
||||
proxy_cache pronouns_media_cache;
|
||||
slice 1m;
|
||||
proxy_cache_key $host$uri$is_args$args$slice_range;
|
||||
proxy_set_header Range $slice_range;
|
||||
proxy_cache_valid 200 206 301 304 1h;
|
||||
proxy_cache_lock on;
|
||||
proxy_ignore_client_abort on;
|
||||
proxy_buffering on;
|
||||
chunked_transfer_encoding on;
|
||||
|
||||
# Rewrite URL to remove /media/ and add bucket
|
||||
rewrite ^/media/(.*) /pronouns/$1 break;
|
||||
|
||||
proxy_pass http://127.0.0.1:9000;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue