Merge pull request #87 from MealCraft/faeture/stripe_to_invoice
Faeture/stripe to invoice
This commit is contained in:
commit
9a557971c6
10 changed files with 784 additions and 280 deletions
|
|
@ -51,9 +51,8 @@ RUN echo "deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe
|
||||||
RUN sudo apt update
|
RUN sudo apt update
|
||||||
RUN sudo apt install stripe
|
RUN sudo apt install stripe
|
||||||
|
|
||||||
|
# Install code server
|
||||||
|
RUN curl -fsSL https://code-server.dev/install.sh | sh
|
||||||
|
|
||||||
|
|
||||||
# Set the working directory
|
# Set the working directory
|
||||||
WORKDIR /workspaces/monorepo
|
WORKDIR /workspaces/monorepo
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
"name": "Basic Python",
|
"name": "Basic Python",
|
||||||
"dockerComposeFile": "docker-compose.yml",
|
"dockerComposeFile": "docker-compose.yml",
|
||||||
"service": "one_repo_to_rule_them_all",
|
"service": "one_repo_to_rule_them_all",
|
||||||
"remoteUser": "vscode",
|
// "remoteUser": "vscode",
|
||||||
"workspaceFolder": "/workspaces/monorepo",
|
"workspaceFolder": "/workspaces/monorepo",
|
||||||
"postStartCommand": "bash .devcontainer/post-install.sh",
|
"postStartCommand": "bash .devcontainer/stripe-to-invoice/post-install.sh",
|
||||||
|
"forwardPorts": [8080],
|
||||||
|
|
||||||
"features": {
|
"features": {
|
||||||
// "ghcr.io/devcontainers/features/ssh-agent:1": {}
|
// "ghcr.io/devcontainers/features/ssh-agent:1": {}
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
|
|
||||||
"mounts": [
|
"mounts": [
|
||||||
// Optional convenience mount
|
// Optional convenience mount
|
||||||
"source=${localEnv:HOME},target=/workspaces/home,type=bind"
|
"source=${localEnv:HOME},target=/home/vscode,type=bind"
|
||||||
],
|
],
|
||||||
|
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
one_repo_to_rule_them_all:
|
one_repo_to_rule_them_all:
|
||||||
user: "${UID}:${GID}"
|
|
||||||
build:
|
build:
|
||||||
context: ../..
|
context: ../..
|
||||||
dockerfile: .devcontainer/stripe-to-invoice/Dockerfile
|
dockerfile: .devcontainer/stripe-to-invoice/Dockerfile
|
||||||
command: sleep infinity
|
command: su - vscode -c "code-server --bind-addr 0.0.0.0:8080"
|
||||||
|
# command: sleep infinity
|
||||||
volumes:
|
volumes:
|
||||||
- ../..:/workspaces/monorepo
|
- ../..:/workspaces/monorepo
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
cv stripe_to_invoice && npm install;
|
cd stripe_to_invoice && npm install;
|
||||||
37
code-server/codeserver.yaml
Normal file
37
code-server/codeserver.yaml
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: dev-juntekim-service
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Endpoints
|
||||||
|
metadata:
|
||||||
|
name: dev-juntekim-service
|
||||||
|
subsets:
|
||||||
|
- addresses:
|
||||||
|
- ip: 192.168.0.181 # mist node
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: dev-juntekim-ingress
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`dev.juntekim.com`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: dev-juntekim-service
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
certResolver: myresolver
|
||||||
34
code-server/sal-codeservery.yaml
Normal file
34
code-server/sal-codeservery.yaml
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: sal-juntekim-service
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8081
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Endpoints
|
||||||
|
metadata:
|
||||||
|
name: sal-juntekim-service
|
||||||
|
subsets:
|
||||||
|
- addresses:
|
||||||
|
- ip: 192.168.0.181 # mist node
|
||||||
|
ports:
|
||||||
|
- port: 8081
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: sal-juntekim-ingress
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`sal.juntekim.com`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: sal-juntekim-service
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
certResolver: myresolver
|
||||||
|
|
@ -1,46 +1,66 @@
|
||||||
# ================================
|
# ==========================================
|
||||||
# EXCALIDRAW - STATELESS
|
# JS PAINT (STATIC DEPLOYMENT)
|
||||||
# https://excalidraw.com
|
# ==========================================
|
||||||
# ================================
|
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: excalidraw
|
name: jspaint
|
||||||
labels:
|
|
||||||
app: excalidraw
|
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: excalidraw
|
app: jspaint
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: excalidraw
|
app: jspaint
|
||||||
spec:
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: mist
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: web-content
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
initContainers:
|
||||||
|
- name: fetch-jspaint
|
||||||
|
image: alpine/git
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
git clone --depth=1 https://github.com/1j01/jspaint.git /tmp/jspaint && \
|
||||||
|
cp -r /tmp/jspaint/* /web
|
||||||
|
volumeMounts:
|
||||||
|
- name: web-content
|
||||||
|
mountPath: /web
|
||||||
|
|
||||||
containers:
|
containers:
|
||||||
- name: excalidraw
|
- name: nginx
|
||||||
image: excalidraw/excalidraw:latest
|
image: nginx:alpine
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
|
volumeMounts:
|
||||||
|
- name: web-content
|
||||||
|
mountPath: /usr/share/nginx/html
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "50m"
|
||||||
memory: "128Mi"
|
memory: "64Mi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "300m"
|
cpu: "200m"
|
||||||
memory: "256Mi"
|
memory: "128Mi"
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: excalidraw
|
name: jspaint
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
app: excalidraw
|
app: jspaint
|
||||||
ports:
|
ports:
|
||||||
- port: 80
|
- port: 80
|
||||||
targetPort: 80
|
targetPort: 80
|
||||||
|
|
@ -49,17 +69,16 @@ spec:
|
||||||
apiVersion: traefik.io/v1alpha1
|
apiVersion: traefik.io/v1alpha1
|
||||||
kind: IngressRoute
|
kind: IngressRoute
|
||||||
metadata:
|
metadata:
|
||||||
name: excalidraw-ingressroute
|
name: jspaint-ingress
|
||||||
spec:
|
spec:
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- websecure
|
- websecure
|
||||||
routes:
|
routes:
|
||||||
- match: Host(`draw.juntekim.com`)
|
- match: Host(`jspaint.juntekim.com`)
|
||||||
kind: Rule
|
kind: Rule
|
||||||
services:
|
services:
|
||||||
- name: excalidraw
|
- name: jspaint
|
||||||
port: 80
|
port: 80
|
||||||
|
passHostHeader: true
|
||||||
tls:
|
tls:
|
||||||
certResolver: myresolver
|
certResolver: myresolver
|
||||||
domains:
|
|
||||||
- main: draw.juntekim.com
|
|
||||||
375
exercise/exercise.yaml
Normal file
375
exercise/exercise.yaml
Normal file
|
|
@ -0,0 +1,375 @@
|
||||||
|
# ======================================================
|
||||||
|
# WGER - PRODUCTION ARCHITECTURE
|
||||||
|
# Traefik → nginx → wger → postgres
|
||||||
|
# ======================================================
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# STORAGE CLASS
|
||||||
|
# -------------------------
|
||||||
|
---
|
||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
kind: StorageClass
|
||||||
|
metadata:
|
||||||
|
name: wger-local-storage
|
||||||
|
provisioner: kubernetes.io/no-provisioner
|
||||||
|
volumeBindingMode: WaitForFirstConsumer
|
||||||
|
|
||||||
|
# ======================================================
|
||||||
|
# PERSISTENT VOLUMES
|
||||||
|
# ======================================================
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# POSTGRES PV
|
||||||
|
# -------------------------
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: wger-postgres-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 2Gi
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
storageClassName: wger-local-storage
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
local:
|
||||||
|
path: /home/kimjunte/k8s_storage/wger/postgres
|
||||||
|
nodeAffinity:
|
||||||
|
required:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/hostname
|
||||||
|
operator: In
|
||||||
|
values: [mist]
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: wger-postgres-pvc
|
||||||
|
spec:
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
storageClassName: wger-local-storage
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 2Gi
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# STATIC PV
|
||||||
|
# -------------------------
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: wger-static-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 2Gi
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
storageClassName: wger-local-storage
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
local:
|
||||||
|
path: /home/kimjunte/k8s_storage/wger/static
|
||||||
|
nodeAffinity:
|
||||||
|
required:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/hostname
|
||||||
|
operator: In
|
||||||
|
values: [mist]
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: wger-static-pvc
|
||||||
|
spec:
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
storageClassName: wger-local-storage
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 2Gi
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# MEDIA PV
|
||||||
|
# -------------------------
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: wger-media-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 5Gi
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
storageClassName: wger-local-storage
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
local:
|
||||||
|
path: /home/kimjunte/k8s_storage/wger/media
|
||||||
|
nodeAffinity:
|
||||||
|
required:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/hostname
|
||||||
|
operator: In
|
||||||
|
values: [mist]
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: wger-media-pvc
|
||||||
|
spec:
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
storageClassName: wger-local-storage
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
|
||||||
|
# ======================================================
|
||||||
|
# POSTGRES
|
||||||
|
# ======================================================
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: wger-postgres
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: wger-postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: wger-postgres
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: mist
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: postgres:15-alpine
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: wger
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
value: wgerpassword
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: wger
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /var/lib/postgresql/data
|
||||||
|
name: postgres-storage
|
||||||
|
volumes:
|
||||||
|
- name: postgres-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: wger-postgres-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: wger-postgres
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: wger-postgres
|
||||||
|
ports:
|
||||||
|
- port: 5432
|
||||||
|
|
||||||
|
# ======================================================
|
||||||
|
# REDIS
|
||||||
|
# ======================================================
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: wger-redis
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: wger-redis
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: wger-redis
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: mist
|
||||||
|
containers:
|
||||||
|
- name: redis
|
||||||
|
image: redis:7-alpine
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: wger-redis
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: wger-redis
|
||||||
|
ports:
|
||||||
|
- port: 6379
|
||||||
|
|
||||||
|
# ======================================================
|
||||||
|
# WGER APP
|
||||||
|
# ======================================================
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: wger
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: wger
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: wger
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: mist
|
||||||
|
containers:
|
||||||
|
- name: wger
|
||||||
|
image: wger/server:latest
|
||||||
|
env:
|
||||||
|
- name: DATABASE_URL
|
||||||
|
value: postgres://wger:wgerpassword@wger-postgres:5432/wger
|
||||||
|
- name: CACHE_URL
|
||||||
|
value: redis://wger-redis:6379/1
|
||||||
|
- name: DJANGO_SECRET_KEY
|
||||||
|
value: replace-with-long-random-string
|
||||||
|
- name: ALLOWED_HOSTS
|
||||||
|
value: "*"
|
||||||
|
- name: CSRF_TRUSTED_ORIGINS
|
||||||
|
value: https://exercise.juntekim.com
|
||||||
|
ports:
|
||||||
|
- containerPort: 8000
|
||||||
|
volumeMounts:
|
||||||
|
- name: static-storage
|
||||||
|
mountPath: /home/wger/static
|
||||||
|
- name: media-storage
|
||||||
|
mountPath: /home/wger/media
|
||||||
|
volumes:
|
||||||
|
- name: static-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: wger-static-pvc
|
||||||
|
- name: media-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: wger-media-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: wger
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: wger
|
||||||
|
ports:
|
||||||
|
- port: 8000
|
||||||
|
targetPort: 8000
|
||||||
|
|
||||||
|
# ======================================================
|
||||||
|
# NGINX (STATIC + PROXY)
|
||||||
|
# ======================================================
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: wger-nginx-config
|
||||||
|
data:
|
||||||
|
default.conf: |
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location /static/ {
|
||||||
|
alias /home/wger/static/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /media/ {
|
||||||
|
alias /home/wger/media/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://wger:8000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: wger-nginx
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: wger-nginx
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: wger-nginx
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: mist
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
volumeMounts:
|
||||||
|
- name: static-storage
|
||||||
|
mountPath: /home/wger/static
|
||||||
|
- name: media-storage
|
||||||
|
mountPath: /home/wger/media
|
||||||
|
- name: nginx-config
|
||||||
|
mountPath: /etc/nginx/conf.d
|
||||||
|
volumes:
|
||||||
|
- name: static-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: wger-static-pvc
|
||||||
|
- name: media-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: wger-media-pvc
|
||||||
|
- name: nginx-config
|
||||||
|
configMap:
|
||||||
|
name: wger-nginx-config
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: wger-nginx
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: wger-nginx
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
|
||||||
|
# ======================================================
|
||||||
|
# TRAEFIK INGRESS
|
||||||
|
# ======================================================
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: wger-ingress
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`exercise.juntekim.com`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: wger-nginx
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
certResolver: myresolver
|
||||||
285
recipes/recipes.yaml
Normal file
285
recipes/recipes.yaml
Normal file
|
|
@ -0,0 +1,285 @@
|
||||||
|
# ======================================================
|
||||||
|
# TANDOOR RECIPES - PRODUCTION (PINNED TO MIST)
|
||||||
|
# ======================================================
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# POSTGRES PV
|
||||||
|
# -------------------------
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: tandoor-postgres-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 2Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: tandoor-local-storage
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
local:
|
||||||
|
path: /home/kimjunte/k8s_storage/tandoor/postgres
|
||||||
|
nodeAffinity:
|
||||||
|
required:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/hostname
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- mist
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: tandoor-postgres-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: tandoor-local-storage
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 2Gi
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# MEDIA PV
|
||||||
|
# -------------------------
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: tandoor-media-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 5Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: tandoor-local-storage
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
local:
|
||||||
|
path: /home/kimjunte/k8s_storage/tandoor/media
|
||||||
|
nodeAffinity:
|
||||||
|
required:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/hostname
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- mist
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: tandoor-media-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: tandoor-local-storage
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# POSTGRES
|
||||||
|
# -------------------------
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: tandoor-postgres
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: tandoor-postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: tandoor-postgres
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: mist
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: postgres:15-alpine
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: tandoor
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
value: tandoorpassword
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: tandoor
|
||||||
|
- name: SITE_URL
|
||||||
|
value: https://mealcraft.com
|
||||||
|
- name: ALLOWED_HOSTS
|
||||||
|
value: mealcraft.com
|
||||||
|
- name: CSRF_TRUSTED_ORIGINS
|
||||||
|
value: https://mealcraft.com
|
||||||
|
- name: NGINX_PROXY
|
||||||
|
value: "1"
|
||||||
|
- name: DEBUG
|
||||||
|
value: "1"
|
||||||
|
- name: SECURE_PROXY_SSL_HEADER
|
||||||
|
value: HTTP_X_FORWARDED_PROTO,https
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /var/lib/postgresql/data
|
||||||
|
name: postgres-storage
|
||||||
|
volumes:
|
||||||
|
- name: postgres-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: tandoor-postgres-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: tandoor-postgres
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: tandoor-postgres
|
||||||
|
ports:
|
||||||
|
- port: 5432
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# REDIS
|
||||||
|
# -------------------------
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: tandoor-redis
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: tandoor-redis
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: tandoor-redis
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: mist
|
||||||
|
containers:
|
||||||
|
- name: redis
|
||||||
|
image: redis:7-alpine
|
||||||
|
ports:
|
||||||
|
- containerPort: 6379
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: tandoor-redis
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: tandoor-redis
|
||||||
|
ports:
|
||||||
|
- port: 6379
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# TANDOOR APP
|
||||||
|
# -------------------------
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: tandoor
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: tandoor
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: tandoor
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: mist
|
||||||
|
|
||||||
|
enableServiceLinks: false # 🔥 CRITICAL FIX
|
||||||
|
|
||||||
|
containers:
|
||||||
|
- name: tandoor
|
||||||
|
image: vabene1111/recipes:1.5.24
|
||||||
|
|
||||||
|
env:
|
||||||
|
- name: SECRET_KEY
|
||||||
|
value: replace-with-long-random-string
|
||||||
|
|
||||||
|
- name: DB_ENGINE
|
||||||
|
value: django.db.backends.postgresql
|
||||||
|
- name: GUNICORN_MEDIA
|
||||||
|
value: "1"
|
||||||
|
- name: POSTGRES_HOST
|
||||||
|
value: tandoor-postgres
|
||||||
|
- name: POSTGRES_PORT
|
||||||
|
value: "5432"
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: tandoor
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: tandoor
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
value: tandoorpassword
|
||||||
|
|
||||||
|
- name: REDIS_URL
|
||||||
|
value: redis://tandoor-redis:6379/0
|
||||||
|
|
||||||
|
- name: ALLOWED_HOSTS
|
||||||
|
value: mealcraft.com
|
||||||
|
|
||||||
|
- name: CSRF_TRUSTED_ORIGINS
|
||||||
|
value: https://mealcraft.com
|
||||||
|
|
||||||
|
- name: NGINX_PROXY
|
||||||
|
value: "1"
|
||||||
|
|
||||||
|
- name: DEBUG
|
||||||
|
value: "0"
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
|
||||||
|
volumeMounts:
|
||||||
|
- name: media-storage
|
||||||
|
mountPath: /opt/recipes/mediafiles
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: media-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: tandoor-media-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: tandoor
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: tandoor
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# TRAEFIK INGRESS
|
||||||
|
# -------------------------
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: tandoor-ingress
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`mealcraft.com`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: tandoor
|
||||||
|
port: 80
|
||||||
|
passHostHeader: true
|
||||||
|
tls:
|
||||||
|
certResolver: myresolver
|
||||||
|
|
@ -1,248 +0,0 @@
|
||||||
Got you — here’s a clean, founder-brain-friendly summary of **Stripe → Invoice (Stripe → Xero)** based on everything you’ve been working through, plus **tight next steps** that fit your nights/weekends reality.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧾 What Stripe → Invoice Is (current state)
|
|
||||||
|
|
||||||
* **Problem you’re solving**
|
|
||||||
|
|
||||||
* UK VAT-registered small businesses using Stripe struggle with **audit-safe, VAT-correct invoices** in Xero
|
|
||||||
* Existing tools are overbuilt, accountant-first, or break down on VAT, clearing accounts, or reconciliation
|
|
||||||
* This is fundamentally a **VAT + audit correctness problem**, not just “sync data”
|
|
||||||
|
|
||||||
* **Who it’s for**
|
|
||||||
|
|
||||||
* UK solo founders / one-person companies / tiny teams
|
|
||||||
* Using **Stripe only** (Payment Links + Subscriptions)
|
|
||||||
* Using **Xero**
|
|
||||||
* Not accountants, not agencies, not complex multi-channel setups
|
|
||||||
|
|
||||||
* **What the MVP does today**
|
|
||||||
|
|
||||||
* Stripe OAuth + Xero OAuth both working
|
|
||||||
* Webhooks flow end-to-end (validated against real finance manager)
|
|
||||||
* Automatically:
|
|
||||||
|
|
||||||
* Creates **clean Xero invoices** from Stripe payments
|
|
||||||
* Applies VAT correctly
|
|
||||||
* Posts payments via a **Stripe Clearing account**
|
|
||||||
* Validated by a finance manager → *very happy* (huge signal)
|
|
||||||
|
|
||||||
* **Key MVP constraints (intentional)**
|
|
||||||
|
|
||||||
* UK + GBP only
|
|
||||||
* Stripe Payment Links + Subscriptions only
|
|
||||||
* Xero contacts matched/created by **email only**
|
|
||||||
* Willing to:
|
|
||||||
|
|
||||||
* Run one-off scripts
|
|
||||||
* Do manual fixes early
|
|
||||||
* Goal: **first ~5 paying customers**, not scale yet
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Recently fixed
|
|
||||||
|
|
||||||
* **Xero contact creation** — Now checks for existing contacts by email first, reuses if found, only creates if missing
|
|
||||||
* **Stripe OAuth app reuse** — Added unique constraints on `userId` and `stripeAccountId` to prevent duplicate connections
|
|
||||||
* **Smart redirect flow** — Users are automatically routed based on connection state:
|
|
||||||
* Both connected → `/dashboard`
|
|
||||||
* Only Stripe → `/connect/xero`
|
|
||||||
* No connections → `/connect/stripe`
|
|
||||||
* **Connection visibility** — Dashboard now displays connected Stripe account ID and Xero tenant ID
|
|
||||||
|
|
||||||
### Frontend Improvements Details
|
|
||||||
|
|
||||||
**Smart Onboarding Flow**
|
|
||||||
* Automatic routing based on connection state
|
|
||||||
* Users never see unnecessary steps
|
|
||||||
* Seamless progression: Login → Stripe → Xero → Dashboard
|
|
||||||
|
|
||||||
**Dashboard Enhancements**
|
|
||||||
* Connected account visibility (Stripe account ID + Xero tenant)
|
|
||||||
* Account code configuration (sales + clearing accounts)
|
|
||||||
* Real-time save confirmation
|
|
||||||
* Clean, minimal UI
|
|
||||||
|
|
||||||
**Development Experience**
|
|
||||||
* Development mode fallback for webhook testing
|
|
||||||
* Comprehensive logging at each webhook stage
|
|
||||||
* Environment-aware configuration
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚠️ Known issues & TODO
|
|
||||||
|
|
||||||
* **CRITICAL: Stripe payment integration**
|
|
||||||
* Need to implement Stripe Billing API to accept payments
|
|
||||||
* Currently not accepting any money from users (test mode only)
|
|
||||||
* Must add subscription checkout flow before going live
|
|
||||||
* Reference: [Stripe Billing API docs](https://stripe.com/docs/billing)
|
|
||||||
|
|
||||||
* **Missing UX guardrails:**
|
|
||||||
* No clear **pre-payment checklist** before enabling sync
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Current mode you’re in (important)
|
|
||||||
|
|
||||||
* You’re correctly running this in **“design partner / friend test” mode**
|
|
||||||
|
|
||||||
* Payments disabled
|
|
||||||
* Banner: *“Internal test – not a commercial product”*
|
|
||||||
* Clear paper trail of non-commercial intent
|
|
||||||
* CFO + finance manager already acting as **design partners**
|
|
||||||
* This massively de-risks VAT/audit assumptions before charging anyone
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ What you should do next (ordered, ruthless, realistic)
|
|
||||||
|
|
||||||
### 1️⃣ Finish the last **correctness blockers** (highest ROI)
|
|
||||||
|
|
||||||
These unlock charging real money.
|
|
||||||
|
|
||||||
* [x] ~~Fix Xero contact creation~~ ✅ DONE
|
|
||||||
|
|
||||||
* ~~Check by email → reuse if exists → only create if missing~~
|
|
||||||
* [x] ~~Fix Stripe OAuth app reuse (stop creating new apps)~~ ✅ DONE
|
|
||||||
* [x] ~~Re-enable "mark invoice as paid" via Stripe Clearing once accounts are valid~~ ✅ DONE
|
|
||||||
|
|
||||||
> Outcome: rock-solid, boring, accountant-approved flow
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2️⃣ Add a tiny **pre-flight checklist UI** (not a full settings page)
|
|
||||||
|
|
||||||
* [x] ~~Dashboard shows connected accounts~~ ✅ DONE
|
|
||||||
|
|
||||||
* ~~Stripe account ID displayed~~
|
|
||||||
* ~~Xero tenant ID displayed~~
|
|
||||||
* [x] ~~Smart redirect flow based on connection state~~ ✅ DONE
|
|
||||||
* [ ] VAT status detection
|
|
||||||
* [ ] Sales account code shown (editable)
|
|
||||||
* [ ] Stripe clearing account shown (editable)
|
|
||||||
|
|
||||||
> Even basic connection visibility prevents 80% of future support pain
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3️⃣ Implement subscription billing (enables first paid customer)
|
|
||||||
|
|
||||||
* Integrate Stripe Billing for subscription management
|
|
||||||
* Add usage tracking (invoice count per month)
|
|
||||||
* Create pricing page and checkout flow
|
|
||||||
* Implement subscription status checks in webhook handler
|
|
||||||
* Remove "internal test" banner once billing is live
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4️⃣ Switch from "design partner" → **first paid customer mode**
|
|
||||||
|
|
||||||
* Pick **one**:
|
|
||||||
|
|
||||||
* A founder you already know **OR**
|
|
||||||
* A cold UK Stripe + Xero business with obvious VAT needs
|
|
||||||
* Offer:
|
|
||||||
|
|
||||||
* £15/month Starter plan
|
|
||||||
* "Early access / founder pricing" (50% off for life)
|
|
||||||
* Manual support included
|
|
||||||
* Goal is **money changing hands**, not scale
|
|
||||||
|
|
||||||
> You've said it yourself: getting paid energises you — lean into that.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5️⃣ Do *targeted* cold outreach (low volume, high signal)
|
|
||||||
|
|
||||||
* 5–10 emails max, not a campaign
|
|
||||||
* Target:
|
|
||||||
|
|
||||||
* UK SaaS / indie founders
|
|
||||||
* Stripe Payment Links or Subscriptions
|
|
||||||
* Clearly VAT-registered
|
|
||||||
* Lead with:
|
|
||||||
|
|
||||||
* "I built this because my accountant hated existing tools"
|
|
||||||
* Emphasise **audit-safe, VAT-correct invoices**
|
|
||||||
* Not "automation", not "syncing"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6️⃣ Future UX polish + automation (after first paying customers)
|
|
||||||
|
|
||||||
* Auto-detect or create Stripe Clearing account in Xero
|
|
||||||
* Bulk historical invoice sync
|
|
||||||
* Invoice preview before creation
|
|
||||||
* Reduce manual fixes you find yourself repeating
|
|
||||||
* Nothing else until:
|
|
||||||
|
|
||||||
* You have **~3–5 paying users**
|
|
||||||
* And they're still using it after month 1
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💳 SaaS Subscription Model (proposed)
|
|
||||||
|
|
||||||
### Pricing Tiers
|
|
||||||
|
|
||||||
**All Plans — £50/month**
|
|
||||||
* Unlimited invoices/month
|
|
||||||
* Stripe Payment Links + Subscriptions support
|
|
||||||
* Full VAT handling and audit compliance
|
|
||||||
* Email support
|
|
||||||
* Perfect for: UK businesses using Stripe + Xero, any size
|
|
||||||
|
|
||||||
**Future tiers** (if needed):
|
|
||||||
* Starter — £30/month (up to 50 invoices)
|
|
||||||
* Professional — £50/month (up to 200 invoices)
|
|
||||||
* Business — £100/month (unlimited)
|
|
||||||
|
|
||||||
### Implementation Notes
|
|
||||||
|
|
||||||
* **Billing via Stripe Checkout** (dogfooding our own product)
|
|
||||||
* **Monthly recurring subscriptions** with automatic renewal
|
|
||||||
* **14-day free trial** — no credit card required
|
|
||||||
* **Founder pricing lock-in** — First 50 customers get lifetime 50% off
|
|
||||||
* **Usage tracking** — Invoice count displayed in dashboard, soft warnings at 80% of limit
|
|
||||||
* **Graceful degradation** — Over-limit users get notified but sync continues (no hard cutoff)
|
|
||||||
|
|
||||||
### Revenue Model
|
|
||||||
|
|
||||||
* **Target: 100 paying customers in 6 months**
|
|
||||||
* 60% Starter (£900/mo)
|
|
||||||
* 30% Professional (£1,050/mo)
|
|
||||||
* 10% Business (£750/mo)
|
|
||||||
* Total: ~£2,700/mo MRR
|
|
||||||
|
|
||||||
* **Conservative burn**
|
|
||||||
* Hosting: £50/mo (Vercel + DB)
|
|
||||||
* Email: £10/mo (AWS SES)
|
|
||||||
* Support: Founder time only
|
|
||||||
* Net: ~£2,640/mo profit margin
|
|
||||||
|
|
||||||
### Next Steps for Monetization
|
|
||||||
|
|
||||||
1. Add Stripe Billing integration to the app
|
|
||||||
2. Implement usage tracking in webhook handler
|
|
||||||
3. Create pricing page on landing site
|
|
||||||
4. Add subscription management in dashboard
|
|
||||||
5. Enable payments and remove "internal test" banner
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧠 The big picture (sanity check)
|
|
||||||
|
|
||||||
* You’re *not* early anymore — you’re **post-validation, pre-pricing**
|
|
||||||
* The hard bit (VAT correctness + finance approval) is already done
|
|
||||||
* The remaining work is boring plumbing + selling
|
|
||||||
* This is exactly where most side projects die — don’t overbuild now
|
|
||||||
|
|
||||||
If you want, next we can:
|
|
||||||
|
|
||||||
* Draft the **first cold email**
|
|
||||||
* Write the **“Why this exists” landing page copy**
|
|
||||||
* Or map a **2-week nights/weekends execution plan**
|
|
||||||
|
|
||||||
Just say the word.
|
|
||||||
Loading…
Add table
Reference in a new issue