Skip to content

Instantly share code, notes, and snippets.

@bodhihawken
Last active January 12, 2025 21:52
Show Gist options
  • Save bodhihawken/acf9c700da61c93f40340df0e741ced8 to your computer and use it in GitHub Desktop.
Save bodhihawken/acf9c700da61c93f40340df0e741ced8 to your computer and use it in GitHub Desktop.
Zero Sync SST Example
/// <reference path="./.sst/platform/config.d.ts" />
const { exec } = require('child_process');
import { db } from 'drizzle';
import ZSchema from './zero-schema.json'
import { execSync } from 'child_process';
import fs from 'fs';
// This deploys a Angular/Ionic/Capacitor app
// API On Same Domain as WebApp
// Postgres Database
// Zero Sync server on same domain as WebApp
// OTP text login via Cognito
// Incoming Email Parsing
// Stripe Webhooks setup directly to API
// Drizzle Kit Studio on dev
// WAITING ON SST OBSERVE OTHER FOLDERS/FILES SUPPORT TO OBSERVE SCHEMA FILES ECT TO FORCE REDEPLOY
// https://github.com/sst/sst/pull/5074
const buildZeroSchema = () => {
execSync(`npx zero-build-schema -p "./schema.ts"`)
return fs.readFileSync('zero-schema.json', 'utf-8')
}
export default $config({
app(input) {
return {
name: 'paddck',
removal: input?.stage === 'production' ? 'retain' : 'remove',
home: 'aws',
providers: {
aws: {
region: 'ap-southeast-2',
},
},
};
},
async run() {
const domain = new sst.Secret('Domain', 'dev.paddck.com');
const openAIKey = new sst.Secret('OpenAIKey');
//ZEROSYNC STUFF
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true, nat: "ec2" });
const database = new sst.aws.Postgres("Database", {
vpc,
proxy: false,
transform: {
parameterGroup: {
parameters: [
{
name: "rds.logical_replication",
value: "1",
applyMethod: "pending-reboot",
},
{
name: "rds.force_ssl",
value: "0",
applyMethod: "pending-reboot",
},
{
name: "max_connections",
value: "1000",
applyMethod: "pending-reboot",
},
],
}
}});
let connectionString = $interpolate`postgres://${database.username}:${database.password}@${database.host}:${database.port}`;
const cluster = new sst.aws.Cluster("ZeroCluster", { vpc });
const service = cluster.addService("ZeroService", {
dev: false,
loadBalancer: {
domain: {
name: $interpolate`zero.${domain.value}`,
dns: sst.aws.dns({
zone: 'Z026850218Q7QYEXQA6GS',
}),
},
ports: [ { listen: "443/https", forward: "4848/http" }],
},
containers: [
{
name: "Zero",
image: "registry.hub.docker.com/rocicorp/zero:canary",
environment: {
ZERO_UPSTREAM_DB: $interpolate`${connectionString}/${database.database}`,
ZERO_CVR_DB: $interpolate`${connectionString}/${database.database}_cvr`,
ZERO_CHANGE_DB: $interpolate`${connectionString}/${database.database}_change`,
ZERO_REPLICA_FILE: "zero.db",
ZERO_SCHEMA_JSON: buildZeroSchema(),
ZERO_AUTO_RESET: 'true',
ZERO_LOG_LEVEL: 'debug',
}
}
],
transform: {
service: {
deploymentMaximumPercent: 100,
deploymentMinimumHealthyPercent: 0,
}
}
});
const email = new sst.aws.Email('EmailClient', {
sender: $app.stage == 'production' ? 'prod.paddck.com' : 'dev.paddck.com',
dns: sst.aws.dns({
zone: 'Z026850218Q7QYEXQA6GS',
}),
});
//boring env variables if needed
let stageVariables = {};
//use this to gen a valid pem (doable in line) if needed
//https://codesandbox.io/p/sandbox/jwk-to-pem-converter-forked-3jtr4c
const auth = new sst.aws.CognitoUserPool('PaddckAuth', {
usernames: ['phone'],
transform: {
userPool: (args, opts) => {
opts.ignoreChanges = ["schemas"]; // uncomment this to workaround this issue: https://github.com/sst/ion/issues/1319
args.schemas = [
{
name: 'customer_id',
attributeDataType: 'String',
mutable: true,
required: false,
},
{
name: 'admin',
attributeDataType: 'String',
mutable: true,
required: false,
},
],
args.usernameAttributes= ['phone_number'],
args.userPoolAddOns = {
advancedSecurityMode: 'ENFORCED',
}
args.autoVerifiedAttributes = []
args.passwordPolicy = {
minimumLength: 6,
requireLowercase: false,
requireUppercase: false,
requireNumbers: false,
requireSymbols: false,
}
}
},
triggers: {
defineAuthChallenge: {
handler: 'backend/auth/define-auth-challenge.handler',
runtime: 'nodejs18.x',
link: [
email,
domain,
database
],
environment: stageVariables,
permissions: [{ actions: ['sns:publish'], resources: ['*'] }],
},
createAuthChallenge: {
handler: 'backend/auth/create-auth-challenge.handler',
link: [
email,
domain,
database
],
environment: stageVariables,
permissions: [{ actions: ['sns:publish'], resources: ['*'] }],
},
verifyAuthChallengeResponse: {
handler: 'backend/auth/verify-auth-challenge-response.handler',
link: [
email,
domain,
database
],
environment: stageVariables,
permissions: [{ actions: ['sns:publish'], resources: ['*'] }],
},
preSignUp: {
handler: 'backend/auth/pre-sign-up.handler',
link: [
email,
domain,
database
],
environment: stageVariables,
permissions: [{ actions: ['sns:publish'], resources: ['*'] }],
},
postConfirmation: {
handler: 'backend/auth/post-confirmation.handler',
link: [
email,
domain,
database
],
environment: stageVariables,
permissions: [
{ actions: ['sns:publish'], resources: ['*'] },
{
actions: ['cognito-idp:AdminUpdateUserAttributes'],
resources: ['*'],
},
],
},
preTokenGeneration: {
handler: 'backend/auth/token-generation.handler',
link: [
email,
domain,
database
],
environment: stageVariables,
permissions: [{ actions: ['sns:publish'], resources: ['*'] }],
},
},
});
let client = auth.addClient('PaddckPoolClient', {
transform: {
client: {
explicitAuthFlows: ['CUSTOM_AUTH_FLOW_ONLY'],
generateSecret: false,
writeAttributes: ['phone_number', 'name'],
},
},
});
//Inbound Email - Recieves email for ingesting with GPT
const sns = new sst.aws.SnsTopic("EmailTopic")
let recievRuleSet = new aws.ses.ReceiptRuleSet('InboundRuleSet', {
ruleSetName: `InboundRuleSet-${$app.stage}`,
});
let inboundRule = new aws.ses.ReceiptRule('InboundRule', {
ruleSetName: `InboundRuleSet-${$app.stage}`,
enabled: true,
name: `InboundRule-${$app.stage}`,
recipients: [
$app.stage == 'production'
? $interpolate`${domain.value}`
: $interpolate`${domain.value}`,
],
'snsActions': [
{
'topicArn': sns.arn,
'encoding': "UTF-8",
position: 1
}
]
});
sns.subscribe({
handler: 'backend/email.handler',
link: [email, domain, openAIKey, database],
})
//Builds and deploys the static SPA
const site = new sst.aws.StaticSite('HostedSite', {
path: './',
dev: {
'title': 'Web App',
'command': 'ionic serve',
},
build: {
command: 'npm run build',
output: 'www',
},
environment: {
NG_APP_DOMAIN: domain.value,
NG_APP_REGION: 'ap-southeast-2',
NG_APP_USER_POOL_ID: auth.id,
NG_APP_USER_POOL_WEB_CLIENT_ID: client.id,
NG_APP_ZERO_SERVER: service.url,
},
});
//Rest API for Webhooks & Future Zero Calls
const rest_function = new sst.aws.Function('RestApi', {
handler: './backend/rest.handler',
link: [email, domain, database],
url: true,
});
const router = new sst.aws.Router('DomainRouter', {
domain: {
name: domain.value,
dns: sst.aws.dns({
zone: 'Z026850218Q7QYEXQA6GS',
}),
},
routes: {
'/*': site.url,
'/api/*': rest_function.url,
},
});
new sst.x.DevCommand("Studio", {
link: [database],
dev: {
autostart: true,
command: "npx drizzle-kit studio",
},
});
//return the table name
return {
SITE_URL: site.url,
ROUTER_URL: router.url,
UserPoolId: auth.id,
UserPoolWebClientId: client.id,
connectionString: connectionString,
host: database.host,
port: database.port,
username: database.username,
password: database.password,
database: database.database,
};
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment