Skip to content

Commit d83f4ec

Browse files
author
bbk
committed
start implementing powersync
1 parent 52be762 commit d83f4ec

5 files changed

Lines changed: 533 additions & 41 deletions

File tree

charts/wger/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ Celery requires persistent volumes.
120120
| Name | Description | Type | Default Value |
121121
|--------------------------------|------------------------------------------|--------|-------------------|
122122
| `app.jwt.secret.name` | Name of the secret | String | `jwt` |
123-
| `app.jwt.secret.key` | Key for the `SIGNING_KEY` | String | `randAlphaNum 50` |
123+
| `app.jwt.signing.key` | Key for the `SIGNING_KEY` | String | `randAlphaNum 50` |
124124
| `app.jwt.accessTokenLifetime` | Duration of the access token, in minutes | String | `10` |
125125
| `app.jwt.refreshTokenLifetime` | Duration of the refresh token, in hours | String | `24` |
126126

charts/wger/templates/configmap.yaml

Lines changed: 296 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,73 +5,118 @@ metadata:
55
name: {{ .Release.Name }}-nginx-configmap
66
data:
77
wger-app.conf: |
8-
# custom access log configuration
9-
log_format custom '$remote_addr - $remote_user [$time_local] "$request" '
10-
'$status $body_bytes_sent "$http_referer" '
11-
'"$http_user_agent" "$http_x_forwarded_for"';
12-
13-
# proxy
148
upstream app_server {
159
# fail_timeout=0 means we always retry an upstream even if it failed
1610
# to return a good HTTP response <- according to gunicorn doc
1711
server 127.0.0.1:8000 fail_timeout=0;
1812
zone upstreams 64K;
1913
keepalive 2;
2014
}
15+
# Used by the /ps/ proxy below.
16+
upstream powersync {
17+
server powersync:8080;
18+
}
2119
22-
# if no Host match, close the connection to prevent host spoofing
23-
server {
24-
listen 80 default_server;
25-
return 444;
20+
# custom access log configuration
21+
log_format custom '$remote_addr - $remote_user [$time_local] "$request" '
22+
'$status $body_bytes_sent "$http_referer" '
23+
'"$http_user_agent" "$http_x_forwarded_for"';
24+
25+
# JSON access log format compatible with the Loki/Alloy parser
26+
# (same field names as the Caddy parser, so the same dashboard works).
27+
log_format json_access escape=json
28+
'{'
29+
'"ts":"$time_iso8601",'
30+
'"client_ip":"$remote_addr",'
31+
'"request_method":"$request_method",'
32+
'"request_uri":"$request_uri",'
33+
'"request_proto":"$server_protocol",'
34+
'"request_host":"$host",'
35+
'"status":$status,'
36+
'"size":$body_bytes_sent,'
37+
'"duration":$request_time,'
38+
'"user_agent":"$http_user_agent",'
39+
'"referer":"$http_referer"'
40+
'}';
41+
42+
# Universal X-Forwarded-Proto handling for all deployment scenarios
43+
# - Uses upstream value when behind reverse proxy (Traefik, Caddy, etc.)
44+
# - Falls back to nginx's scheme for direct connections and port forwarding
45+
map $http_x_forwarded_proto $final_forwarded_proto {
46+
default $http_x_forwarded_proto;
47+
'' $scheme;
2648
}
2749
2850
# webserver
2951
server {
30-
listen 8080 deferred;
31-
client_max_body_size 4G;
52+
listen 80;
3253
3354
# set the correct host(s) for your site
3455
server_name {{ join " " .Values.ingress.url }};
3556
36-
access_log /var/log/nginx/access.log custom;
37-
error_log /var/log/nginx/error.log warn;
38-
39-
# path for static files (only needed for serving "local" static and media files)
40-
root /var/www/html/;
57+
access_log /var/log/nginx/access.log custom json_access;
4158
4259
location / {
43-
# checks for static file, if not found proxy to app
44-
try_files $uri @proxy_to_app;
45-
}
60+
proxy_pass http://app_server;
4661
47-
location @proxy_to_app {
62+
# Standard proxy headers
63+
proxy_set_header Host $http_host;
64+
proxy_set_header X-Real-IP $remote_addr;
65+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
66+
proxy_set_header X-Forwarded-Proto $final_forwarded_proto;
67+
proxy_set_header X-Forwarded-Host $http_host;
68+
69+
# WebSocket support
70+
proxy_set_header Upgrade $http_upgrade;
71+
proxy_set_header Connection "upgrade";
72+
73+
proxy_redirect off;
4874
proxy_http_version 1.1;
4975
50-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
51-
{{- if .Values.ingress.tls }}
52-
proxy_set_header X-Forwarded-Proto https;
53-
{{- else }}
54-
proxy_set_header X-Forwarded-Proto $scheme;
55-
{{- end }}
76+
# Timeouts for WebSocket connections
77+
proxy_read_timeout 86400s;
78+
proxy_send_timeout 86400s;
79+
}
80+
81+
# Reverse proxy for the PowerSync service, used by the mobile app for offline mode
82+
location /ps/ {
83+
proxy_pass http://powersync/;
84+
5685
proxy_set_header Host $http_host;
86+
proxy_set_header X-Real-IP $remote_addr;
87+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
88+
proxy_set_header X-Forwarded-Proto $final_forwarded_proto;
5789
58-
# keep alive
59-
proxy_set_header "Connection" "";
90+
# WebSocket upgrade (used by the diagnostics app and the Web SDK)
91+
proxy_http_version 1.1;
92+
proxy_set_header Upgrade $http_upgrade;
93+
proxy_set_header Connection "upgrade";
6094
61-
# https://www.getpagespeed.com/server-setup/nginx/tuning-proxy_buffer_size-in-nginx
62-
proxy_buffer_size 32k;
63-
proxy_busy_buffers_size 40k; # proxy_buffer_size + 2 small buffers of 4k
64-
proxy_buffers 64 4k;
65-
proxy_max_temp_file_size 0;
95+
# Sync streams are long-lived (HTTP/2 streaming POST) and must not
96+
# be buffered or cut off by short timeouts.
97+
proxy_buffering off;
98+
proxy_read_timeout 1d;
99+
proxy_send_timeout 1d;
100+
}
66101
67-
# give gunicorn time to process
68-
proxy_read_timeout 1800;
102+
location /static/ {
103+
alias /wger/static/;
69104
70-
proxy_redirect off;
71-
proxy_pass http://app_server;
105+
# Files in the static folder are preprocessed by collectstatic and have a hash
106+
# in their names, so we can cache longer
107+
add_header Cache-Control "public, max-age=31536000, immutable" always;
108+
add_header Vary "Accept-Encoding" always;
72109
}
110+
111+
location /media/ {
112+
alias /wger/media/;
113+
}
114+
115+
# Increase max body size to allow for video uploads
116+
client_max_body_size 100M;
73117
}
74118
{{- end }}
119+
75120
---
76121
kind: ConfigMap
77122
apiVersion: v1
@@ -80,3 +125,216 @@ metadata:
80125
data:
81126
40-grantSuperuser.sql: |
82127
ALTER USER {{ .Values.postgres.userDatabase.user.value }} WITH SUPERUSER;
128+
129+
---
130+
kind: ConfigMap
131+
apiVersion: v1
132+
metadata:
133+
name: {{ .Release.Name }}-powersync-configmap
134+
data:
135+
powersync.yaml: |
136+
# yaml-language-server: $schema=../schema/schema.json
137+
138+
# Note that this example uses YAML custom tags for environment variable substitution.
139+
# Using `!env [variable name]` will substitute the value of the environment variable named
140+
# [variable name].
141+
#
142+
# Only environment variables with names starting with `PS_` can be substituted.
143+
#
144+
# e.g. With the environment variable `export PS_STORAGE_MONGO_URI=mongodb://localhost:27017`
145+
# and YAML code:
146+
# uri: !env PS_STORAGE_MONGO_URI
147+
# The YAML will resolve to:
148+
# uri: mongodb://localhost:27017
149+
#
150+
# If using VS Code see the `.vscode/settings.json` definitions which define custom tags.
151+
152+
# migrations:
153+
# # Migrations run automatically by default.
154+
# # Setting this to true will skip automatic migrations.
155+
# # Migrations can be triggered externally by altering the container `command`.
156+
# disable_auto_migration: true
157+
158+
# Settings for telemetry reporting
159+
# See https://docs.powersync.com/self-hosting/telemetry
160+
telemetry:
161+
disable_telemetry_sharing: true
162+
163+
# Expose prometheus metrics on this port
164+
prometheus_port: 9090
165+
166+
# Settings for source database replication
167+
replication:
168+
# Specify database connection details
169+
# Note only 1 connection is currently supported
170+
# Multiple connection support is on the roadmap
171+
connections:
172+
- type: postgresql
173+
uri: !env PS_DATABASE_URI
174+
175+
# Or use individual params
176+
# hostname: db # From the Docker Compose service name
177+
# port: 5432
178+
# database: postgres
179+
# username: postgres
180+
# password: mypassword
181+
182+
# SSL settings
183+
sslmode: disable # 'verify-full' (default) or 'verify-ca' or 'disable'
184+
# 'disable' is OK for local/private networks, not for public networks
185+
186+
187+
# Connection settings for sync bucket storage
188+
storage:
189+
type: postgresql
190+
uri: !env PS_STORAGE_PG_URI
191+
sslmode: disable
192+
193+
# The port which the PowerSync API server will listen on
194+
port: !env PS_PORT
195+
196+
# Specify sync rules
197+
sync_rules:
198+
path: sync_rules.yaml
199+
200+
# Client (application end user) authentication settings
201+
client_auth:
202+
allow_local_jwks: true
203+
jwks_uri: !env PS_JWKS_URL
204+
205+
# JWKS audience
206+
audience: ["powersync"]
207+
208+
sync_rules.yaml: |
209+
# Note that changes to this file are not watched.
210+
# The service needs to be restarted for changes to take effect.
211+
212+
# Warning: a user may have at most 1000 buckets, i.e. parameter-query results
213+
# summed across all streams. This counts the *parameter* rows, not the data
214+
# rows inside a bucket (a single bucket can hold any number of rows). For a
215+
# stream with a `with:` CTE the count is the number of rows the CTE returns,
216+
# so for `user_ingredients` below that is the number of distinct ingredients
217+
# a user has ever referenced. Exceeding the limit is a hard error
218+
# (PSYNC_S2305 "Too many parameter query results").
219+
# See https://docs.powersync.com/sync/rules/parameter-queries
220+
#
221+
# Streams are split by update frequency (cold / medium / hot) so that
222+
# bucket compaction can collapse the head of hot buckets without being
223+
# blocked by long-lived rows from cold tables.
224+
#
225+
# For details, see the documentation:
226+
# https://docs.powersync.com/sync/streams/overview
227+
# https://docs.powersync.com/maintenance-ops/compacting-buckets
228+
229+
config:
230+
edition: 3
231+
232+
streams:
233+
# Global reference data, shared by all users, changes rarely enough
234+
core:
235+
auto_subscribe: true
236+
queries:
237+
- SELECT * FROM core_language
238+
- SELECT * FROM core_license
239+
- SELECT * FROM core_repetitionunit
240+
- SELECT * FROM core_weightunit
241+
- SELECT * FROM exercises_exercise
242+
- SELECT * FROM exercises_translation
243+
- SELECT * FROM exercises_alias
244+
- SELECT * FROM exercises_exercisecomment
245+
- SELECT * FROM exercises_muscle
246+
- SELECT * FROM exercises_exercise_muscles
247+
- SELECT * FROM exercises_exercise_muscles_secondary
248+
- SELECT * FROM exercises_equipment
249+
- SELECT * FROM exercises_exercise_equipment
250+
- SELECT * FROM exercises_exercisecategory
251+
- SELECT * FROM exercises_exerciseimage
252+
- SELECT * FROM exercises_exercisevideo
253+
254+
# COLD, per-user data that almost never changes after creation.
255+
user_profile:
256+
auto_subscribe: true
257+
queries:
258+
- SELECT * FROM core_userprofile WHERE CAST(user_id AS TEXT) = auth.user_id()
259+
- SELECT * FROM gallery_image WHERE CAST(user_id AS TEXT) = auth.user_id()
260+
261+
# COLD but potentially large, only the per-user *filter set* changes when the user logs new foods
262+
user_ingredients:
263+
auto_subscribe: true
264+
with:
265+
user_ingredients: |
266+
SELECT DISTINCT nutrition_synced_ingredient.id
267+
FROM nutrition_synced_ingredient
268+
WHERE nutrition_synced_ingredient.id IN (
269+
SELECT nutrition_logitem.ingredient_id FROM nutrition_logitem
270+
JOIN nutrition_nutritionplan ON nutrition_logitem.plan_id = nutrition_nutritionplan.id
271+
WHERE CAST(nutrition_nutritionplan.user_id AS TEXT) = auth.user_id())
272+
OR nutrition_synced_ingredient.id IN (
273+
SELECT nutrition_mealitem.ingredient_id FROM nutrition_mealitem
274+
JOIN nutrition_meal ON nutrition_mealitem.meal_id = nutrition_meal.id
275+
JOIN nutrition_nutritionplan ON nutrition_meal.plan_id = nutrition_nutritionplan.id
276+
WHERE CAST(nutrition_nutritionplan.user_id AS TEXT) = auth.user_id())
277+
queries:
278+
- SELECT * FROM nutrition_synced_ingredient AS nutrition_ingredient WHERE id IN user_ingredients
279+
- |
280+
SELECT nutrition_image.* FROM nutrition_image
281+
WHERE nutrition_image.ingredient_id IN user_ingredients
282+
- |
283+
SELECT nutrition_ingredientweightunit.* FROM nutrition_ingredientweightunit
284+
WHERE nutrition_ingredientweightunit.ingredient_id IN user_ingredients
285+
286+
# MEDIUM. Edited e.g. when the user builds or edits their routine or nutrition plan,
287+
# but not on every workout.
288+
user_planning:
289+
auto_subscribe: true
290+
queries:
291+
# Routines (templates excluded)
292+
- SELECT * FROM manager_routine WHERE CAST(user_id AS TEXT) = auth.user_id() AND is_template = FALSE
293+
294+
# Measurements
295+
- SELECT * FROM measurements_category WHERE CAST(user_id AS TEXT) = auth.user_id()
296+
- |
297+
SELECT measurements_measurement.*
298+
FROM measurements_measurement
299+
INNER JOIN measurements_category
300+
ON measurements_measurement.category_id = measurements_category.id
301+
WHERE CAST(measurements_category.user_id AS TEXT) = auth.user_id()
302+
303+
# Nutrition plan structure (not the log items)
304+
- SELECT * FROM nutrition_nutritionplan WHERE CAST(user_id AS TEXT) = auth.user_id()
305+
- |
306+
SELECT nutrition_meal.*
307+
FROM nutrition_meal
308+
JOIN nutrition_nutritionplan
309+
ON nutrition_meal.plan_id = nutrition_nutritionplan.id
310+
WHERE CAST(nutrition_nutritionplan.user_id AS TEXT) = auth.user_id()
311+
- |
312+
SELECT nutrition_mealitem.*
313+
FROM nutrition_mealitem
314+
JOIN nutrition_meal
315+
ON nutrition_mealitem.meal_id = nutrition_meal.id
316+
JOIN nutrition_nutritionplan
317+
ON nutrition_meal.plan_id = nutrition_nutritionplan.id
318+
WHERE CAST(nutrition_nutritionplan.user_id AS TEXT) = auth.user_id()
319+
320+
321+
# HOT. Generates one or more new rows per workout / meal. Compaction has the
322+
# biggest impact here, so it must stay isolated from the other streams above
323+
user_activity:
324+
auto_subscribe: true
325+
queries:
326+
# Weight tracking
327+
- SELECT uuid AS id, weight, date, user_id FROM weight_weightentry WHERE CAST(user_id AS TEXT) = auth.user_id()
328+
329+
# Workout sessions and per-set logs
330+
- SELECT * FROM manager_workoutsession WHERE CAST(user_id AS TEXT) = auth.user_id()
331+
- SELECT * FROM manager_workoutlog WHERE CAST(user_id AS TEXT) = auth.user_id()
332+
333+
# Nutrition log entries
334+
- |
335+
SELECT nutrition_logitem.*
336+
FROM nutrition_logitem
337+
JOIN nutrition_nutritionplan
338+
ON nutrition_logitem.plan_id = nutrition_nutritionplan.id
339+
WHERE CAST(nutrition_nutritionplan.user_id AS TEXT) = auth.user_id()
340+

0 commit comments

Comments
 (0)