├── scripts ├── .gitignore ├── requirements.txt └── zstory.py ├── public ├── favicon.ico ├── friedolin_woff │ ├── Friedolin.woff │ ├── example.css │ └── example.html ├── auth-success.html ├── auth-error.html ├── support.html ├── phone.html ├── auth-details.html ├── privacy.html ├── index.html ├── tos.html └── auth-start.html ├── docs ├── img │ ├── slack-call.png │ ├── zoom-users.png │ └── slack-call-update.png ├── add-zoom-licenses.md ├── certify.md ├── 2021-04-02_account_transition_email.md └── runbook.md ├── migration.md ├── env.js ├── .gitignore ├── prisma ├── migrations │ ├── 20230614152527_call_id │ │ └── migration.sql │ ├── 20220112185013_host_key_is_optional │ │ └── migration.sql │ ├── migration_lock.toml │ ├── 20220112182425_fix_required_constraint_on_scheduling_link_creator_slack_id │ │ └── migration.sql │ ├── 20230614152406_add_custom_logs │ │ └── migration.sql │ ├── 20220112185406_relax_required_fields_in_meeting_model_while_migrating_from_airtable │ │ └── migration.sql │ ├── 20220112184852_move_json_fields_to_strings_while_importing_from_airtable │ │ └── migration.sql │ ├── 20220112184558_attempt_to_fix_invalid_reference_from_meeting_to_host │ │ └── migration.sql │ ├── 20220112135901_scheduling_link_id_opt │ │ └── migration.sql │ ├── 20220112182619_fix_required_constraint_on_scheduling_link_authed_account_id │ │ └── migration.sql │ ├── 20220112180006_fix_ids_for_a_couple_models │ │ └── migration.sql │ └── 20220112132642_first │ │ └── migration.sql └── schema.prisma ├── api ├── endpoints │ ├── signup.js │ ├── stats.js │ ├── slack │ │ ├── slash-z-rooms.js │ │ ├── index.js │ │ ├── events.js │ │ ├── host-code.js │ │ └── slash-z.js │ ├── new-schedule-link.js │ ├── schedule-link.js │ ├── slack-auth.js │ └── zoom.js ├── time-hash.js ├── string-to-color.js ├── record-error.js ├── channel-is-forbidden.js ├── user-is-restricted.js ├── send-recording-notification.js ├── send-host-key.js ├── ensure-slack-authenticated.js ├── ensure-zoom-authenticated.js ├── get-scheduled-meetings.js ├── zoom-meeting-to-recording.js ├── is-public-slack-channel.js ├── close-stale-calls.js ├── state.js ├── get-public-meetings.js ├── update-slack-call-participant-list.js ├── NOTES.md ├── hack-night.js ├── find-or-create-meeting.js ├── close-zoom-call.js ├── zoom-client.js ├── transcript.js ├── open-zoom-meeting.js ├── slack-app-home-opened.js └── prisma.js ├── prettier.config.json ├── isprod.js ├── template.env ├── bugsnag.js ├── docker-compose.yaml ├── metrics.js ├── Dockerfile ├── jobs ├── index.js ├── batch-upload.js └── remove-table.js ├── package.json ├── manifest.yml ├── race.test.js ├── server.js ├── routes.js ├── README.md ├── lib └── transcript.yml └── dashboards └── grafana.json /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .env 3 | venv 4 | -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | psycopg 2 | slack_sdk -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackclub/slash-z/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /docs/img/slack-call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackclub/slash-z/HEAD/docs/img/slack-call.png -------------------------------------------------------------------------------- /docs/img/zoom-users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackclub/slash-z/HEAD/docs/img/zoom-users.png -------------------------------------------------------------------------------- /migration.md: -------------------------------------------------------------------------------- 1 | # Migration todo list: 2 | - [ ] Google calendar integration 3 | - [ ] Recording meetings -------------------------------------------------------------------------------- /docs/img/slack-call-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackclub/slash-z/HEAD/docs/img/slack-call-update.png -------------------------------------------------------------------------------- /env.js: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv' 2 | import isProd from './isprod.js' 3 | if (!isProd) { 4 | dotenv.config() 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env_prod 2 | .vscode 3 | .vercel 4 | node_modules 5 | *.env 6 | .replit 7 | package-lock.json 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /public/friedolin_woff/Friedolin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackclub/slash-z/HEAD/public/friedolin_woff/Friedolin.woff -------------------------------------------------------------------------------- /prisma/migrations/20230614152527_call_id/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "CustomLogs" ADD COLUMN "zoomCallId" TEXT; 3 | -------------------------------------------------------------------------------- /api/endpoints/signup.js: -------------------------------------------------------------------------------- 1 | export default (req, res) => { 2 | res.redirect('http://workspace.google.com/marketplace/app/appname/299631665103') 3 | } -------------------------------------------------------------------------------- /prisma/migrations/20220112185013_host_key_is_optional/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Meeting" ALTER COLUMN "hostKey" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /prettier.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid", 5 | "printWidth": 80, 6 | "semi": false 7 | } -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /prisma/migrations/20220112182425_fix_required_constraint_on_scheduling_link_creator_slack_id/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "SchedulingLink" ALTER COLUMN "creatorSlackID" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /isprod.js: -------------------------------------------------------------------------------- 1 | // detects whether or not we're on staging 2 | // returns true if NODE_ENV === "production" otherwise false 3 | const isProd = process.env.NODE_ENV === "production"; 4 | export default isProd; 5 | -------------------------------------------------------------------------------- /api/time-hash.js: -------------------------------------------------------------------------------- 1 | import { createHash } from 'crypto'; 2 | 3 | export function currentTimeHash () { 4 | return createHash('sha256').update(new Date().getHours() + '' + (process.env.JOIN_URL_SALT ?? '')).digest('hex'); 5 | } -------------------------------------------------------------------------------- /prisma/migrations/20230614152406_add_custom_logs/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "CustomLogs" ( 3 | "id" TEXT NOT NULL, 4 | "text" TEXT, 5 | 6 | CONSTRAINT "CustomLogs_pkey" PRIMARY KEY ("id") 7 | ); 8 | -------------------------------------------------------------------------------- /public/auth-success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 7 | 8 | 9 |Authentication successful! You can now close this window
10 | 11 | -------------------------------------------------------------------------------- /template.env: -------------------------------------------------------------------------------- 1 | AIRTABLE_API_KEY=REPLACEME 2 | AIRTABLE_BASE=REPLACEME 3 | AIRBRIDGE_API_KEY=REPLACEME 4 | SLACK_BOT_USER_OAUTH_ACCESS_TOKEN=REPLACEME 5 | SLACK_SIGNING_SECRET=REPLACEME # optional 6 | ZOOM_VERIFICATION_TOKEN=REPLACEME 7 | BUGSNAG_API_KEY=REPLACEME # optional 8 | -------------------------------------------------------------------------------- /public/auth-error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |Something went wrong in the sign up process. Please try again or send us an email at slash-z@hackclub.com. Included screenshots help!
7 | 8 | -------------------------------------------------------------------------------- /prisma/migrations/20220112185406_relax_required_fields_in_meeting_model_while_migrating_from_airtable/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Meeting" ALTER COLUMN "creatorSlackID" DROP NOT NULL, 3 | ALTER COLUMN "joinURL" DROP NOT NULL, 4 | ALTER COLUMN "hostJoinURL" DROP NOT NULL; 5 | -------------------------------------------------------------------------------- /public/friedolin_woff/example.css: -------------------------------------------------------------------------------- 1 | /* #### Generated By: http://www.fontget.com #### */ 2 | 3 | @font-face { 4 | font-family: 'Friedolin'; 5 | font-style: normal; 6 | font-weight: normal; 7 | src: local('Friedolin'), url('friedolin_woff/Friedolin.woff') format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /prisma/migrations/20220112184852_move_json_fields_to_strings_while_importing_from_airtable/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Meeting" ALTER COLUMN "rawWebhookEvents" SET DATA TYPE TEXT, 3 | ALTER COLUMN "rawData" SET DATA TYPE TEXT; 4 | 5 | -- AlterTable 6 | ALTER TABLE "WebhookEvent" ALTER COLUMN "rawData" SET DATA TYPE TEXT; 7 | -------------------------------------------------------------------------------- /prisma/migrations/20220112184558_attempt_to_fix_invalid_reference_from_meeting_to_host/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "Meeting" DROP CONSTRAINT "Meeting_hostID_fkey"; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "Meeting" ADD CONSTRAINT "Meeting_hostID_fkey" FOREIGN KEY ("hostID") REFERENCES "Host"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /api/endpoints/stats.js: -------------------------------------------------------------------------------- 1 | import { getTotalHosts, getOpenHosts, getCurrentlyActiveUsers } from "../state.js"; 2 | 3 | export default async (req, res) => { 4 | const data = { 5 | hosts: { 6 | total: await getTotalHosts(), 7 | open: await getOpenHosts(), 8 | active_users: await getCurrentlyActiveUsers() 9 | } 10 | } 11 | return res.send(data) 12 | } 13 | -------------------------------------------------------------------------------- /bugsnag.js: -------------------------------------------------------------------------------- 1 | import Bugsnag from '@bugsnag/js' 2 | import BugsnagPluginExpress from '@bugsnag/plugin-express' 3 | 4 | let started = false 5 | 6 | export default () => { 7 | if (!started) { 8 | Bugsnag.start({ 9 | apiKey: process.env.BUGSNAG_API_KEY, 10 | plugins: [BugsnagPluginExpress], 11 | }) 12 | started = true 13 | } 14 | return Bugsnag.getPlugin('express') 15 | } -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | slash-z: 5 | build: . 6 | container_name: slash-z 7 | environment: 8 | - NODE_OPTIONS=--max-old-space-size=492 # ~0.5GB 9 | env_file: 10 | - .env 11 | ports: 12 | - "3000" 13 | restart: unless-stopped 14 | deploy: 15 | resources: 16 | limits: 17 | memory: 512M 18 | 19 | -------------------------------------------------------------------------------- /metrics.js: -------------------------------------------------------------------------------- 1 | import StatsD from 'node-statsd' 2 | 3 | const environment = process.env.NODE_ENV 4 | const graphite = process.env.GRAPHITE_HOST 5 | 6 | if (graphite == null) { 7 | throw new Error('Graphite host not configured!') 8 | } 9 | 10 | const options = { 11 | host: graphite, 12 | port: 8125, 13 | prefix: `${environment}.slashz.`, 14 | } 15 | 16 | const metrics = new StatsD(options) 17 | 18 | export default metrics; 19 | -------------------------------------------------------------------------------- /prisma/migrations/20220112135901_scheduling_link_id_opt/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "Meeting" DROP CONSTRAINT "Meeting_schedulingLinkId_fkey"; 3 | 4 | -- AlterTable 5 | ALTER TABLE "Meeting" ALTER COLUMN "schedulingLinkId" DROP NOT NULL; 6 | 7 | -- AddForeignKey 8 | ALTER TABLE "Meeting" ADD CONSTRAINT "Meeting_schedulingLinkId_fkey" FOREIGN KEY ("schedulingLinkId") REFERENCES "SchedulingLink"("id") ON DELETE SET NULL ON UPDATE CASCADE; 9 | -------------------------------------------------------------------------------- /api/string-to-color.js: -------------------------------------------------------------------------------- 1 | // Taken from https://jsfiddle.net/sUK45/ 2 | // https://stackoverflow.com/a/16348977 3 | 4 | export default (str) => { 5 | let hash = 0; 6 | for (let i = 0; i < str.length; i++) { 7 | hash = str.charCodeAt(i) + ((hash << 5) - hash) 8 | } 9 | let colour = '' 10 | for (var i = 0; i < 3; i++) { 11 | var value = (hash >> (i * 8)) & 0xFF 12 | colour += ('00' + value.toString(16)).substr(-2) 13 | } 14 | return colour 15 | } 16 | -------------------------------------------------------------------------------- /public/friedolin_woff/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 |