On this page you will find an overview of my Portfolio.
I decided to utilise a front-end that I made a few years ago, with the express intent on developing a backend later (I made everything into re-usable "blocks"). I decided to use my SA development as an opportunity to make this backend this. I wanted to make this as efficiently as possible, noting my core build is to be done in Python; and learning is otherwise mainly around networking and cloud infrastructure. Doing this now, however, would make developing my portfolio "hassle free", so I could populate it as I went along.
An overview of a few of the key/core features/design decisions:
Space for words
---
config:
layout: elk
look: handDrawn
theme: dark
---
flowchart TB
subgraph Sec["Security + Access (ADR-006)"]
AccessCF["Cloudflare Access (GitHub OAuth + Org/Team checks + MFA)"]
Jwt["Session/JWT verification
(shared secrets / keys)"]
end
subgraph DNS["DNS Entry"]
R53["Route53
portfolio.tacsa.co.uk + subdomains"]
end
subgraph Admin["Admin UI (Cloudflare Pages)"]
AdminDev["Admin (dev)
dev.admin.portfolio.tacsa.co.uk"]
AdminProd["Admin (prod)
admin.portfolio.tacsa.co.uk"]
end
subgraph CMS["CMS API Layer"]
Proxy["Pages Function: /api/*
(adds x-cms-read-key / x-cms-write-key / branch header)"]
Worker["CMS Worker
(PR-only writes)"]
end
subgraph GH["GitHub (Source of Truth)"]
Repo["Portfolio Repo
(VoodooScience1.github.io)"]
DevBranch["dev branch
(preview source)"]
MainBranch["main/master
(prod source)"]
PR["Pull Request
(review + audit trail)"]
Actions["GitHub Actions
(build + orchestration)"]
end
subgraph CF["Cloudflare Pages (previews)"]
DevSite["Portfolio preview (dev)
dev.portfolio.tacsa.co.uk"]
end
subgraph GHP["GitHub Pages (production)"]
ProdSite["Portfolio (prod)
portfolio.tacsa.co.uk"]
end
subgraph Hosting["Hosting"]
CF
GHP
end
subgraph Hooks["Rebuild Triggers (ADR-010)"]
HookDev["Deploy Hook (dev)
Cloudflare Pages"]
HookProd["Publish (prod)
GitHub Pages"]
Webhook["Webhook / event trigger
(repo → deploy pipeline)"]
end
AdminUser["Admin"] --> AccessCF
AccessCF --> Jwt
Jwt --> AdminDev & AdminProd
AdminDev --> Proxy
Proxy --> Worker & Worker
AdminProd --> Proxy
Worker --> Repo
Repo --> PR & DevBranch & Actions
PR --> MainBranch
DevBranch --> DevSite
MainBranch --> ProdSite
Actions --> Webhook & HookProd
Webhook --> HookDev
HookDev --> DevSite
HookProd --> ProdSite
User["User"] --> R53
R53 --> DevSite & ProdSite
More space for words
space for words
sequenceDiagram
actor Admin
participant Access as Cloudflare Access
participant UI as Admin UI <br> (admin.portfolio.tacsa.co.uk)
participant Worker as CMS Worker <br> (Cloudflare Worker)
participant GH as GitHub Repo + API
participant PR as Pull Request
participant CI as GitHub Actions / <br> Orchestration
participant Preview as dev.portfolio.tacsa.co.uk <br> (CF Pages)
participant Prod as portfolio.tacsa.co.uk <br> (GitHub Pages)
Admin->>Access: Authenticate <br> (GitHub OAuth + Org/Team)
Access-->>Admin: Session granted
Admin->>UI: Open admin portal
Admin->>UI: Edit content + submit
UI->>Worker: Write request (/api/*)
Worker->>GH: Create branch + commit + PR
GH-->>Worker: PR link/status
Worker-->>UI: PR created (await merge)
Admin->>PR: Review + merge
PR->>CI: Trigger build/deploy
CI->>Preview: Rebuild dev preview
CI->>Prod: Publish prod site
space for words
space for words
---
config:
layout: elk
look: classic
theme: neo-dark
---
architecture-beta
service user(logos:users)[User]
service admin(logos:user)[Admin]
group dns(logos:aws-route53)[DNS]
service r53(logos:aws-route53)[Route53] in dns
group edge(logos:cloudflare-icon)[OAuth]
service access(logos:cloudflare-icon)[Access Gate] in edge
service controls(logos:cloudflare-icon)[Security Controls] in edge
group pages(logos:cloudflare-icon)[Pages]
service adminprod(logos:cloudflare-icon)[Admin Prod] in pages
group dev(logos:cloudflare-icon)[Dev Pages]
service admindev(logos:cloudflare-icon)[Admin Dev] in dev
service preview(logos:cloudflare-icon)[Portfolio Dev] in dev
group prod(logos:github-icon)[Production]
service live(logos:github-icon)[Portfolio Prod] in prod
group control(logos:cloudflare-icon)[Worker]
service worker(logos:cloudflare-workers)[CMS Worker] in control
group gh(logos:github-icon)[GitHub]
service repo(logos:github-icon)[Portfolio Repo] in gh
service prs(logos:github-icon)[Pull Requests] in gh
service actions(logos:github-actions)[Actions] in gh
user:B --> T:r53
r53:L --> R:preview
live:T <-- B:r53
admin:R --> L:access
access:R --> L:controls
controls:T --> L:admindev
controls:R --> L:adminprod
admindev:B --> T:worker
adminprod:R --> L:worker
worker:R --> L:repo
repo:R --> L:prs
prs:R --> L:actions
actions:T --> B:preview
actions:R --> L:live
space for words
space for words
flowchart LR
subgraph Repo["GitHub Repo"]
Dev["dev branch"]
Master["master branch"]
end
subgraph Actions["GitHub Actions"]
Job["Trigger Cloudflare Pages Rebuilds"]
end
subgraph Hooks["Deploy Hooks"]
HookProdAdmin["Prod Admin Deploy Hook\nCLOUDFLARE_PAGES_DEPLOY_HOOK"]
HookDevCore["Dev Core Deploy Hook\nCLOUDFLARE_CORE_DEPLOY_HOOK_DEV"]
HookDevAdmin["Dev Admin Deploy Hook\nCLOUDFLARE_PAGES_DEPLOY_HOOK_DEV"]
end
subgraph Targets["Targets"]
AdminProd["admin.portfolio.tacsa.co.uk"]
AdminDev["dev.admin.portfolio.tacsa.co.uk"]
PortfolioDev["dev.portfolio.tacsa.co.uk"]
end
Master -->|push| Job
Dev -->|push| Job
Job -->|if master| HookProdAdmin --> AdminProd
Job -->|if dev| HookDevCore --> PortfolioDev
Job -->|if dev| HookDevAdmin --> AdminDev
space for words
words
gitGraph
commit id: "baseline"
branch dev
checkout dev
commit id: "change(s)"
checkout main
merge dev id: "PR merged"
commit id: "release"
words