Last active
January 12, 2025 21:52
-
-
Save bodhihawken/acf9c700da61c93f40340df0e741ced8 to your computer and use it in GitHub Desktop.
Zero Sync SST Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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