Skip to content

Commit

Permalink
v3, no longer reliant on userbase, self-hostable
Browse files Browse the repository at this point in the history
This is now end-to-end encrypted based on open web standards, connects to a PostgreSQL (docker-compose provided for local setups, Render and DigitalOcean options for easier "self-hosting"), and had the frontend code completely migrated to TypeScript.

Existing version will be available for another month at v2.budgetzen.net to allow data export, to be imported into v3 at app.budgetzen.net.
  • Loading branch information
BrunoBernardino committed Mar 10, 2023
1 parent 7e88a60 commit 9076585
Show file tree
Hide file tree
Showing 58 changed files with 5,389 additions and 4,432 deletions.
43 changes: 43 additions & 0 deletions .do/deploy.template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
spec:

This comment has been minimized.

Copy link
@BrunoBernardino

BrunoBernardino Mar 10, 2023

Author Owner

I was pushed to do this earlier than I wanted mostly because of smallbets/userbase#427

name: budgetzen
envs:
- key: BASE_URL
scope: RUN_AND_BUILD_TIME
value: ${app.PUBLIC_URL}
services:
- name: app
dockerfile_path: Dockerfile
git:
branch: main
http_port: 8000
instance_count: 1
instance_size_slug: basic-xs
routes:
- path: /
health_check:
http_path: /
source_dir: /
envs:
- key: POSTGRESQL_HOST
scope: RUN_AND_BUILD_TIME
value: ${db.HOSTNAME}
- key: POSTGRESQL_USER
scope: RUN_AND_BUILD_TIME
value: ${db.USERNAME}
- key: POSTGRESQL_PASSWORD
scope: RUN_AND_BUILD_TIME
value: ${db.PASSWORD}
- key: POSTGRESQL_DBNAME
scope: RUN_AND_BUILD_TIME
value: ${db.DATABASE}
- key: POSTGRESQL_PORT
scope: RUN_AND_BUILD_TIME
value: ${db.PORT}
- key: POSTGRESQL_CAFILE
scope: RUN_AND_BUILD_TIME
value: ""
databases:
- name: db
engine: PG
production: false
version: "15"
2 changes: 1 addition & 1 deletion .dvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.29.1
1.30.3
17 changes: 16 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
USERBASE_APP_ID=get-from-userbase.com
PORT=8000
BASE_URL="http://localhost:8000"

POSTGRESQL_HOST="localhost"
POSTGRESQL_USER="postgres"
POSTGRESQL_PASSWORD="fake"
POSTGRESQL_DBNAME="budgetzen"
POSTGRESQL_PORT=5432
POSTGRESQL_CAFILE=""

POSTMARK_SERVER_API_TOKEN="fake"

STRIPE_API_KEY="fake"

PAYPAL_CLIENT_ID="fake"
PAYPAL_CLIENT_SECRET="fake"
28 changes: 28 additions & 0 deletions .github/workflows/cron-check-subscriptions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: "Cron: Check subscriptions"

on:
workflow_dispatch:
schedule:
# At 04:06 every day.
- cron: '6 4 * * *'

jobs:
cron-cleanup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v1
with:
deno-version: v1.30.3
- env:
POSTGRESQL_HOST: ${{ secrets.POSTGRESQL_HOST }}
POSTGRESQL_USER: ${{ secrets.POSTGRESQL_USER }}
POSTGRESQL_PASSWORD: ${{ secrets.POSTGRESQL_PASSWORD }}
POSTGRESQL_DBNAME: ${{ secrets.POSTGRESQL_DBNAME }}
POSTGRESQL_PORT: ${{ secrets.POSTGRESQL_PORT }}
POSTGRESQL_CAFILE: ${{ secrets.POSTGRESQL_CAFILE }}
STRIPE_API_KEY: ${{ secrets.STRIPE_API_KEY }}
PAYPAL_CLIENT_ID: ${{ secrets.PAYPAL_CLIENT_ID }}
PAYPAL_CLIENT_SECRET: ${{ secrets.PAYPAL_CLIENT_SECRET }}
run: |
make crons/check-subscriptions
25 changes: 25 additions & 0 deletions .github/workflows/cron-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: "Cron: Cleanup"

on:
workflow_dispatch:
schedule:
# At 03:05 every day.
- cron: '5 3 * * *'

jobs:
cron-cleanup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v1
with:
deno-version: v1.30.3
- env:
POSTGRESQL_HOST: ${{ secrets.POSTGRESQL_HOST }}
POSTGRESQL_USER: ${{ secrets.POSTGRESQL_USER }}
POSTGRESQL_PASSWORD: ${{ secrets.POSTGRESQL_PASSWORD }}
POSTGRESQL_DBNAME: ${{ secrets.POSTGRESQL_DBNAME }}
POSTGRESQL_PORT: ${{ secrets.POSTGRESQL_PORT }}
POSTGRESQL_CAFILE: ${{ secrets.POSTGRESQL_CAFILE }}
run: |
make crons/cleanup
8 changes: 7 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ jobs:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v1
with:
deno-version: v1.29.1
deno-version: v1.30.3
- run: docker-compose pull
- uses: jpribyl/action-docker-layer-caching@v0.1.1
continue-on-error: true
- run: |
cp .env.sample .env
docker-compose up -d
make migrate-db
make test
3 changes: 3 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
localhost

reverse_proxy * localhost:8000
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM denoland/deno:1.30.3

EXPOSE 8000

WORKDIR /app

# Prefer not to run as root.
USER deno

# These steps will be re-run upon each file change in your working directory:
ADD . /app

# Compile the main app so that it doesn't need to be compiled each startup/entry.
RUN deno cache --reload main.ts

CMD ["make", "start"]
20 changes: 18 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: start
start:
deno run --watch --allow-net --allow-read=public,pages,.env,.env.defaults,.env.example --allow-env main.ts
deno run --watch --allow-net --allow-read --allow-env main.ts

.PHONY: format
format:
Expand All @@ -10,4 +10,20 @@ format:
test:
deno fmt --check
deno lint
deno test --allow-net --allow-read=public,pages,.env,.env.defaults,.env.example --allow-env --check=all
deno test --allow-net --allow-read --allow-env --check

.PHONY: migrate-db
migrate-db:
deno run --allow-net --allow-read --allow-env migrate-db.ts

.PHONY: crons/check-subscriptions
crons/check-subscriptions:
deno run --allow-net --allow-read --allow-env crons/check-subscriptions.ts

.PHONY: crons/cleanup
crons/cleanup:
deno run --allow-net --allow-read --allow-env crons/cleanup.ts

.PHONY: exec-db
exec-db:
docker exec -it -u postgres $(shell basename $(CURDIR))_postgresql_1 psql
51 changes: 40 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,61 @@

This is the web app for the [Budget Zen app](https://budgetzen.net), built with [Deno](https://deno.land) and deployed to [Deno Deploy](https://deno.com/deploy).

This is v2, which is [end-to-end encrypted via userbase](https://userbase.com), and works via web on any device (it's a PWA - Progressive Web App).
This is v3, which is [end-to-end encrypted with open Web Standards](https://en.wikipedia.org/wiki/End-to-end_encryption), and works via web on any device (it's a PWA - Progressive Web App).

It's not compatible with Budget Zen v1 (not end-to-end encrypted), which you can still get locally from [this commit](https://github.com/BrunoBernardino/budgetzen-web/tree/397d625469b7dfd8d1968c847b32e607ee7c8ee9). You can still export and import the data as the JSON format is the same (unencrypted).
It's not compatible with Budget Zen v2 ([end-to-end encrypted via Userbase](https://userbase.com)) which you can still get locally from [this commit](https://github.com/BrunoBernardino/budgetzen-web/tree/7e88a602be437cd4d54268f87113b21e9cff5c60), nor v1 (not end-to-end encrypted), which you can still get locally from [this commit](https://github.com/BrunoBernardino/budgetzen-web/tree/397d625469b7dfd8d1968c847b32e607ee7c8ee9). You can still export and import the data as the JSON format is the same across all 3 versions (unencrypted).

## Self-host it!

[![Deploy to DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/BrunoBernardino/budgetzen-web)

[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/BrunoBernardino/budgetzen-web)

Or check the [Development section below](#development).

> **NOTE:** You don't need to have emails (Postmark) and subscriptions (Stripe/PayPal) setup to have the app work. Those are only used for allowing others to automatically manage their account. You can simply make any `user.status = 'active'` and `user.subscription.expires_at = new Date('2100-01-01')` to "never" expire, in the database, directly.
## Framework-less

This right here is vanilla TypeScript and JavaScript using Web Standards. It's very easy to update and maintain.

It's meant to have no unnecessary dependencies, packagers, or bundlers. Just vanilla, simple stuff.

## Requirements

This was tested with `deno`'s version in the `.dvmrc` file, though it's possible other versions might work.
This was tested with [`Deno`](https://deno.land)'s version stated in the `.dvmrc` file, though other versions may work.

There are no other dependencies. **Deno**!
For the PostgreSQL dependency (used when running locally, self-hosted, or in CI), you should have `Docker` and `docker-compose` installed.

If you want to run the app locally with SSL (Web Crypto standards require `https` except for Chrome), you can use [`Caddy`](https://caddyserver.com) (there's a `Caddyfile` that proxies `https://localhost` to the Deno app).

Don't forget to set up your `.env` file based on `.env.sample`.

## Development

```sh
$ make start
$ make format
$ make test
$ docker-compose up # (optional) runs docker with postgres, locally
$ sudo caddy run # (optional) runs an https proxy for the deno app
$ make migrate-db # runs any missing database migrations
$ make start # runs the app
$ make format # formats the code
$ make test # runs tests
```

## Structure
## Other less-used commands

This is vanilla JS, web standards, no frameworks. If you'd like to see/use [the Next.js version deployed to AWS via Serverless, check this commit](https://github.com/BrunoBernardino/budgetzen-web/tree/b1097c710ba89abf9aed044a7d7444e91d04a6a7).
```sh
$ make exec-db # runs psql inside the postgres container, useful for running direct development queries like `DROP DATABASE "budgetzen"; CREATE DATABASE "budgetzen";`
```

## Structure

- Backend routes are defined at `routes.ts`.
- Static files are defined at `public/`.
- Publicly-available files are defined at `public/`.
- Pages are defined at `pages/`.
- Cron jobs are defined at `crons/`.
- Reusable bits of code are defined at `lib/`.
- Database migrations are defined at `db-migrations/`.

## Deployment

Expand All @@ -37,4 +67,3 @@ This is vanilla JS, web standards, no frameworks. If you'd like to see/use [the
## TODOs:

- [ ] Enable true offline mode (securely cache data, allow read-only)
- https://github.com/smallbets/userbase/issues/255 has interesting ideas, while it's not natively supported
7 changes: 4 additions & 3 deletions components/footer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { html } from '../lib/utils.ts';
import { helpEmail, html } from '/lib/utils.ts';

export default function footer() {
return html`
Expand All @@ -9,7 +9,7 @@ export default function footer() {
<article class="faq-item" data-has-invalid-session>
<h4>What is Budget Zen?</h4>
<p>
Simple and encrypted budget management.
Simple and encrypted expense management.
<a href="https://budgetzen.net">Read more here</a>.
</p>
</article>
Expand Down Expand Up @@ -47,8 +47,9 @@ export default function footer() {
</section>
<h3 class="links">
<a href="https://budgetzen.net/blog">Blog</a> |
<a href="https://budgetzen.net/terms">Terms of Service</a> |
<a href="https://budgetzen.net/privacy">Privacy Policy</a> |
<a href="mailto:me@brunobernardino.com">Get Help</a> | <span class="by">by</span>
<a href="mailto:${helpEmail}">Get Help</a> | <span class="by">by</span>
<a href="https://brunobernardino.com">Bruno Bernardino</a>
</h3>
</footer>
Expand Down
2 changes: 1 addition & 1 deletion components/header.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { html } from '../lib/utils.ts';
import { html } from '/lib/utils.ts';

export default function header(currentPath: string) {
return html`
Expand Down
2 changes: 1 addition & 1 deletion components/loading.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { html } from '../lib/utils.ts';
import { html } from '/lib/utils.ts';

export default function loading() {
return html`
Expand Down
31 changes: 31 additions & 0 deletions components/modals/verification-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { html } from '/lib/utils.ts';

export default function verificationCodeModal() {
return html`
<template id="verification-code-modal">
<swal-title>
Verification Code
</swal-title>
<swal-html>
<span class="text">
You have received an email with a verification code. Type it here.
</span>
<form id="verification-code-form">
<fieldset class="input-wrapper">
<label for="verification-code-input">Code</label>
<input
id="verification-code-input"
type="text"
/>
</fieldset>
</form>
</swal-html>
<swal-button type="confirm">
Verify
</swal-button>
<swal-button type="cancel">
Cancel
</swal-button>
</template>
`;
}
Loading

0 comments on commit 9076585

Please sign in to comment.