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 install stripe
|
||||
|
||||
|
||||
|
||||
|
||||
# Install code server
|
||||
RUN curl -fsSL https://code-server.dev/install.sh | sh
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /workspaces/monorepo
|
||||
|
|
@ -2,9 +2,10 @@
|
|||
"name": "Basic Python",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "one_repo_to_rule_them_all",
|
||||
"remoteUser": "vscode",
|
||||
// "remoteUser": "vscode",
|
||||
"workspaceFolder": "/workspaces/monorepo",
|
||||
"postStartCommand": "bash .devcontainer/post-install.sh",
|
||||
"postStartCommand": "bash .devcontainer/stripe-to-invoice/post-install.sh",
|
||||
"forwardPorts": [8080],
|
||||
|
||||
"features": {
|
||||
// "ghcr.io/devcontainers/features/ssh-agent:1": {}
|
||||
|
|
@ -12,7 +13,7 @@
|
|||
|
||||
"mounts": [
|
||||
// Optional convenience mount
|
||||
"source=${localEnv:HOME},target=/workspaces/home,type=bind"
|
||||
"source=${localEnv:HOME},target=/home/vscode,type=bind"
|
||||
],
|
||||
|
||||
"customizations": {
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ version: '3.8'
|
|||
|
||||
services:
|
||||
one_repo_to_rule_them_all:
|
||||
user: "${UID}:${GID}"
|
||||
build:
|
||||
context: ../..
|
||||
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:
|
||||
- ../..:/workspaces/monorepo
|
||||
extra_hosts:
|
||||
- "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
|
||||
# https://excalidraw.com
|
||||
# ================================
|
||||
# ==========================================
|
||||
# JS PAINT (STATIC DEPLOYMENT)
|
||||
# ==========================================
|
||||
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: excalidraw
|
||||
labels:
|
||||
app: excalidraw
|
||||
name: jspaint
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: excalidraw
|
||||
app: jspaint
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: excalidraw
|
||||
app: jspaint
|
||||
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:
|
||||
- name: excalidraw
|
||||
image: excalidraw/excalidraw:latest
|
||||
- name: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumeMounts:
|
||||
- name: web-content
|
||||
mountPath: /usr/share/nginx/html
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
cpu: "50m"
|
||||
memory: "64Mi"
|
||||
limits:
|
||||
cpu: "300m"
|
||||
memory: "256Mi"
|
||||
cpu: "200m"
|
||||
memory: "128Mi"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: excalidraw
|
||||
name: jspaint
|
||||
spec:
|
||||
selector:
|
||||
app: excalidraw
|
||||
app: jspaint
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
|
|
@ -49,17 +69,16 @@ spec:
|
|||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: excalidraw-ingressroute
|
||||
name: jspaint-ingress
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`draw.juntekim.com`)
|
||||
- match: Host(`jspaint.juntekim.com`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: excalidraw
|
||||
- name: jspaint
|
||||
port: 80
|
||||
passHostHeader: true
|
||||
tls:
|
||||
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