diff --git a/package-lock.json b/package-lock.json index ce88522..ff329a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,14 +13,15 @@ "@fastify/cors": "^8.4.2", "@fastify/multipart": "^8.2.0", "@fastify/oauth2": "^7.8.0", + "@neondatabase/serverless": "^0.9.3", "@sentry/node": "^8.9.2", "drizzle-orm": "^0.31.1", "fastify": "^4.25.0", "fastify-plugin": "^4.5.1", + "fastify-raw-body": "^4.3.0", "googleapis": "^134.0.0", "mailtrap": "^3.3.0", "openai": "^4.38.5", - "pg-native": "^3.1.0", "redis": "^4.6.11", "simple-get": "^4.0.1", "stripe": "^15.7.0", @@ -994,8 +995,6 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.9.3.tgz", "integrity": "sha512-6ZBK8asl2Z3+ADEaELvbaVVGVlmY1oAzkxxZfpmXPKFuJhbDN+5fU3zYBamsahS/Ch1zE+CVWB3R+8QEI2LMSw==", - "optional": true, - "peer": true, "dependencies": { "@types/pg": "8.11.6" } @@ -2827,6 +2826,9 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "file-uri-to-path": "1.0.0" } @@ -2966,6 +2968,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -3210,7 +3220,10 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/cosmiconfig": { "version": "8.3.6", @@ -3512,6 +3525,14 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -5349,6 +5370,22 @@ "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==", "license": "MIT" }, + "node_modules/fastify-raw-body": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/fastify-raw-body/-/fastify-raw-body-4.3.0.tgz", + "integrity": "sha512-F4o8ZIMVx4YoxGfwrZys6wyjl40gF3Yv6AWWRy62ozFAyZBSS831/uyyCAqKYw3tR73g180ryG98yih6To1PUQ==", + "dependencies": { + "fastify-plugin": "^4.0.0", + "raw-body": "^2.5.1", + "secure-json-parse": "^2.4.0" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "url": "https://github.com/Eomm/fastify-raw-body?sponsor=1" + } + }, "node_modules/fastify/node_modules/process-warning": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", @@ -5380,7 +5417,10 @@ "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/fill-range": { "version": "7.0.1", @@ -6119,6 +6159,21 @@ "dev": true, "license": "ISC" }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/https-proxy-agent": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", @@ -6150,6 +6205,17 @@ "ms": "^2.0.0" } }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -7040,7 +7106,10 @@ "version": "1.8.13", "resolved": "https://registry.npmjs.org/libpq/-/libpq-1.8.13.tgz", "integrity": "sha512-t1wpnGVgwRIFSKoe4RFUllAFj953kNMcdXhGvFJwI0r6lJQqgSwTeiIciaCinjOmHk0HnFeWQSMC6Uw2591G4A==", + "dev": true, "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { "bindings": "1.5.0", "nan": "2.19.0" @@ -7417,7 +7486,10 @@ "node_modules/nan": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -7950,6 +8022,9 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-3.1.0.tgz", "integrity": "sha512-nhMf/d6ypOc6jtUYlfGkLyPkYK+boor0FziOcsnqVxQZlGIXjBlSp3bNkW27O1RN46JlupjziIr1NP3aCCyWOw==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "libpq": "1.8.13", "pg-types": "^1.12.1", @@ -7959,12 +8034,18 @@ "node_modules/pg-native/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/pg-native/node_modules/pg-types": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz", "integrity": "sha512-lfKli0Gkl/+za/+b6lzENajczwZHc7D5kiUCZfgm914jipD2kIOIvEkAhZ8GrW3/TUoP9w8FHjwpPObBye5KQQ==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~1.0.0", @@ -7977,6 +8058,9 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz", "integrity": "sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ==", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -7985,6 +8069,9 @@ "version": "1.0.31", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.31.tgz", "integrity": "sha512-tco/Dwv1f/sgIgN6CWdj/restacPKNskK6yps1981ivH2ZmLYcs5o5rVzL3qaO/cSkhN8hYOMWs7+glzOLSgRg==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -8343,6 +8430,20 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -8790,6 +8891,11 @@ "node": ">=10" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", @@ -8912,6 +9018,11 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9197,6 +9308,14 @@ "node": ">= 10.x" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stream-wormhole": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stream-wormhole/-/stream-wormhole-1.1.0.tgz", @@ -9208,7 +9327,10 @@ "node_modules/string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/string-width": { "version": "4.2.3", @@ -9547,6 +9669,14 @@ "node": ">=12" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -9779,6 +9909,14 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", diff --git a/package.json b/package.json index fb26328..636d7d2 100644 --- a/package.json +++ b/package.json @@ -34,14 +34,15 @@ "@fastify/cors": "^8.4.2", "@fastify/multipart": "^8.2.0", "@fastify/oauth2": "^7.8.0", + "@neondatabase/serverless": "^0.9.3", "@sentry/node": "^8.9.2", "drizzle-orm": "^0.31.1", "fastify": "^4.25.0", "fastify-plugin": "^4.5.1", + "fastify-raw-body": "^4.3.0", "googleapis": "^134.0.0", "mailtrap": "^3.3.0", "openai": "^4.38.5", - "pg-native": "^3.1.0", "redis": "^4.6.11", "simple-get": "^4.0.1", "stripe": "^15.7.0", diff --git a/src/db/index.js b/src/db/index.js index 16ae65d..68d8323 100644 --- a/src/db/index.js +++ b/src/db/index.js @@ -3,22 +3,28 @@ import { env, Logger } from "../utils/index.js"; import { drizzle } from "drizzle-orm/node-postgres"; import { migrate } from "drizzle-orm/node-postgres/migrator"; import pg from 'pg' -const { native } = pg +const { Pool } = pg /** @type {ReturnType>} */ export let db; export const initDb = async () => { - const client = new native.Pool({ + const client = new Pool({ connectionString: env.DATABASE_URL, keepAlive: true }); + client.on('error', e => { + console.error('Database error', e); + client = null; + }); + await client.connect(); db = drizzle(client, { schema, }); + // await migrate(db, { // migrationsFolder: "./src/db/migrations", diff --git a/src/db/migrations/0014_tan_bill_hollister.sql b/src/db/migrations/0014_tan_bill_hollister.sql new file mode 100644 index 0000000..ec6e68d --- /dev/null +++ b/src/db/migrations/0014_tan_bill_hollister.sql @@ -0,0 +1,2 @@ +ALTER TABLE "users" ADD COLUMN "subscribed_until" date DEFAULT '1970-01-01';--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "tokens_claimed" date; \ No newline at end of file diff --git a/src/db/migrations/0015_nervous_victor_mancha.sql b/src/db/migrations/0015_nervous_victor_mancha.sql new file mode 100644 index 0000000..c880047 --- /dev/null +++ b/src/db/migrations/0015_nervous_victor_mancha.sql @@ -0,0 +1 @@ +ALTER TABLE "users" ADD COLUMN "trial_used" boolean DEFAULT false; \ No newline at end of file diff --git a/src/db/migrations/meta/0014_snapshot.json b/src/db/migrations/meta/0014_snapshot.json new file mode 100644 index 0000000..b8d950e --- /dev/null +++ b/src/db/migrations/meta/0014_snapshot.json @@ -0,0 +1,525 @@ +{ + "id": "674d04c7-a853-4f20-98ac-b92e33d6d017", + "prevId": "de65dcf5-f6de-434b-80b6-ba199df3a96a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.articles": { + "name": "articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_video_id": { + "name": "source_video_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "seo_slug": { + "name": "seo_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "seo_title": { + "name": "seo_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "seo_description": { + "name": "seo_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "excerp": { + "name": "excerp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "article_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'queued'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "articles_site_id_sites_id_fk": { + "name": "articles_site_id_sites_id_fk", + "tableFrom": "articles", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "google_access_token": { + "name": "google_access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_refresh_token": { + "name": "google_refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.signups": { + "name": "signups", + "schema": "", + "columns": { + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "signups_site_id_sites_id_fk": { + "name": "signups_site_id_sites_id_fk", + "tableFrom": "signups", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sites": { + "name": "sites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "primary_color_hex": { + "name": "primary_color_hex", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true, + "default": "'c4ced4'" + }, + "secondary_color_hex": { + "name": "secondary_color_hex", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true, + "default": "'27251f'" + }, + "text_color_hex": { + "name": "text_color_hex", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true, + "default": "'ffffff'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'The best blog in the world!'" + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'Some extra info about the best blog in the world!'" + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "NULL" + }, + "send_freebie": { + "name": "send_freebie", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "freebie_name": { + "name": "freebie_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'Our newsletter'" + }, + "freebie_url": { + "name": "freebie_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "freebie_text": { + "name": "freebie_text", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "freebie_image_url": { + "name": "freebie_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "subdomain_slug": { + "name": "subdomain_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "social_medias": { + "name": "social_medias", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "auto_publish": { + "name": "auto_publish", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "pubsub_expiry": { + "name": "pubsub_expiry", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "use_contact_page": { + "name": "use_contact_page", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "contact_email": { + "name": "contact_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_about_page": { + "name": "use_about_page", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "about_text": { + "name": "about_text", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sites_user_id_users_id_fk": { + "name": "sites_user_id_users_id_fk", + "tableFrom": "sites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sites_domain_unique": { + "name": "sites_domain_unique", + "nullsNotDistinct": false, + "columns": [ + "domain" + ] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploads_playlist_id": { + "name": "uploads_playlist_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subscription_tier": { + "name": "subscription_tier", + "type": "subscription_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscribed_until": { + "name": "subscribed_until", + "type": "date", + "primaryKey": false, + "notNull": false, + "default": "'1970-01-01'" + }, + "tokens_claimed": { + "name": "tokens_claimed", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "tokens": { + "name": "tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 200 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.article_status": { + "name": "article_status", + "schema": "public", + "values": [ + "queued", + "transcribing", + "generating", + "done", + "error" + ] + }, + "public.subscription_tier": { + "name": "subscription_tier", + "schema": "public", + "values": [ + "free", + "basic", + "pro", + "enterprise" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/src/db/migrations/meta/0015_snapshot.json b/src/db/migrations/meta/0015_snapshot.json new file mode 100644 index 0000000..350f826 --- /dev/null +++ b/src/db/migrations/meta/0015_snapshot.json @@ -0,0 +1,532 @@ +{ + "id": "5df89ef6-dcab-4661-87f4-ad81b88bf4f4", + "prevId": "674d04c7-a853-4f20-98ac-b92e33d6d017", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.articles": { + "name": "articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_video_id": { + "name": "source_video_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "seo_slug": { + "name": "seo_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "seo_title": { + "name": "seo_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "seo_description": { + "name": "seo_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "excerp": { + "name": "excerp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "article_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'queued'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "articles_site_id_sites_id_fk": { + "name": "articles_site_id_sites_id_fk", + "tableFrom": "articles", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "google_access_token": { + "name": "google_access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_refresh_token": { + "name": "google_refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.signups": { + "name": "signups", + "schema": "", + "columns": { + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "signups_site_id_sites_id_fk": { + "name": "signups_site_id_sites_id_fk", + "tableFrom": "signups", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sites": { + "name": "sites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "primary_color_hex": { + "name": "primary_color_hex", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true, + "default": "'c4ced4'" + }, + "secondary_color_hex": { + "name": "secondary_color_hex", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true, + "default": "'27251f'" + }, + "text_color_hex": { + "name": "text_color_hex", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true, + "default": "'ffffff'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'The best blog in the world!'" + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'Some extra info about the best blog in the world!'" + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "NULL" + }, + "send_freebie": { + "name": "send_freebie", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "freebie_name": { + "name": "freebie_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'Our newsletter'" + }, + "freebie_url": { + "name": "freebie_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "freebie_text": { + "name": "freebie_text", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "freebie_image_url": { + "name": "freebie_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "subdomain_slug": { + "name": "subdomain_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "social_medias": { + "name": "social_medias", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "auto_publish": { + "name": "auto_publish", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "pubsub_expiry": { + "name": "pubsub_expiry", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "use_contact_page": { + "name": "use_contact_page", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "contact_email": { + "name": "contact_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_about_page": { + "name": "use_about_page", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "about_text": { + "name": "about_text", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sites_user_id_users_id_fk": { + "name": "sites_user_id_users_id_fk", + "tableFrom": "sites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sites_domain_unique": { + "name": "sites_domain_unique", + "nullsNotDistinct": false, + "columns": [ + "domain" + ] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploads_playlist_id": { + "name": "uploads_playlist_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subscription_tier": { + "name": "subscription_tier", + "type": "subscription_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscribed_until": { + "name": "subscribed_until", + "type": "date", + "primaryKey": false, + "notNull": false, + "default": "'1970-01-01'" + }, + "tokens_claimed": { + "name": "tokens_claimed", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "trial_used": { + "name": "trial_used", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "tokens": { + "name": "tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 200 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.article_status": { + "name": "article_status", + "schema": "public", + "values": [ + "queued", + "transcribing", + "generating", + "done", + "error" + ] + }, + "public.subscription_tier": { + "name": "subscription_tier", + "schema": "public", + "values": [ + "free", + "basic", + "pro", + "enterprise" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json index a91161e..1761918 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -99,6 +99,20 @@ "when": 1719134353536, "tag": "0013_lucky_vulture", "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1719389425401, + "tag": "0014_tan_bill_hollister", + "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1719403126188, + "tag": "0015_nervous_victor_mancha", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/schemas.js b/src/db/schemas.js index 03a792a..2846b00 100644 --- a/src/db/schemas.js +++ b/src/db/schemas.js @@ -13,6 +13,9 @@ export const users = pgTable("users", { channel_id: text("channel_id"), uploads_playlist_id: text("uploads_playlist_id"), subscription_tier: subscription_enum("subscription_tier").default("free").notNull(), + subscribed_until: date("subscribed_until").default(new Date(0)), + tokens_claimed: date("tokens_claimed"), + trial_used: boolean("trial_used").default(false), tokens: integer("tokens").default(200).notNull() }); diff --git a/src/index.js b/src/index.js index a44da11..948803e 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,7 @@ import xml2js from 'xml2js'; import './utils/sentry.js'; // import fastifyMultipart from "@fastify/multipart"; import * as Sentry from "@sentry/node"; - +import rawBody from 'fastify-raw-body'; const API_VERSION = "v1"; @@ -36,6 +36,15 @@ export const main = async () => { parseOptions: {}, // options for parsing cookies }); + server.register(rawBody, { + field: 'rawBody', // change the default request.rawBody property name + global: false, // add the rawBody to every request. **Default true** + encoding: 'utf8', // set it to false to set rawBody as a Buffer **Default utf8** + runFirst: true, // get the body before any preParsing hook change/uncompress it. **Default false** + routes: [], // array of routes, **`global`** will be ignored, wildcard routes not supported + jsonContentTypes: [], // array of content-types to handle as JSON. **Default ['application/json']** + }) + server.register(middleware); server.register(import("@fastify/cors"), { maxAge: 600, diff --git a/src/routes/me.js b/src/routes/me.js index 327e9b4..8712ceb 100644 --- a/src/routes/me.js +++ b/src/routes/me.js @@ -4,7 +4,7 @@ import { eq } from "drizzle-orm"; import { db } from "../db/index.js"; import { users } from "../db/schemas.js"; import { authMiddleware } from "../modules/middleware.js"; -import { getBillingDashboard } from "../utils/stripe.js"; +import { createCheckout, getBillingDashboard } from "../utils/stripe.js"; import { env } from "../utils/env.js"; /** @@ -59,5 +59,26 @@ export const meRoutes = (fastify, _, done) => { } }); + fastify.get("/subscribe", async (req, reply) => { + try { + const [user] = await db.select().from(users).where(eq(users.id, req.session.user_id)); + if (!user) throw new Error("user not found"); + const checkout_url = await createCheckout("price_1PVrA2FrdGTeTMwd5ZDjQKDZ", user.stripe_id, { + user_id: user.id + }, { + trial_end: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000) + }); + + reply.redirect(303, checkout_url); + } catch (e) { + reply.status(400).send({ + success: false, + message: "User not found", + log: e.message + }); + return; + } + }); + done(); }; diff --git a/src/routes/webhook.js b/src/routes/webhook.js index 974439a..8e75e10 100644 --- a/src/routes/webhook.js +++ b/src/routes/webhook.js @@ -6,6 +6,7 @@ import { articles, sites, users } from "../db/schemas.js"; import { getVideoWithCaptions } from "../utils/youtube.js"; import { createBlogFromCaptions } from "../utils/ai.js"; import { createArticleSlug } from "../utils/index.js"; +import { handleWebhook } from "../utils/stripe.js"; /** * @@ -14,26 +15,35 @@ import { createArticleSlug } from "../utils/index.js"; * @param {() => void} done */ export const webhookRoutes = (fastify, _, done) => { - fastify.get("/", async (request, response) => { - try { - } catch (e) { - response.status(400).send({ - success: false, - message: "User not found", - log: e.message - }); - return; + fastify.post("/stripe", { + config: { + rawBody: true + }, + handler: async (req, reply) => { + try { + await handleWebhook(req.rawBody, req.headers["stripe-signature"]); + + reply.status(200).send({}); + } catch (e) { + console.log(e); + reply.status(400).send({ + success: false, + message: "Error validating purchase", + log: e.message + }); + return; + } } }); fastify.get("/youtube", async (req, reply) => { // Check if the request contains the 'hub.challenge' query parameter if (req.query["hub.challenge"] && req.query["hub.verify_token"] === "FQNI4Suzih" && req.query["hub.topic"].startsWith("https://www.youtube.com/xml/feeds/videos.xml?channel_id=")) { - if(req.query["hub.mode" === "unsubscribe"]) return reply.code(200).send(); + if (req.query["hub.mode" === "unsubscribe"]) return reply.code(200).send(); const channel = req.query["hub.topic"].replace("https://www.youtube.com/xml/feeds/videos.xml?channel_id=", ""); const [user] = await db.select().from(users).where(eq(users.channel_id, channel)); - if(!user) return reply.code(400).send("Invalid user"); + if (!user) return reply.code(400).send("Invalid user"); await db.update(sites).set({ pubsub_expiry: new Date(new Date().getTime() + (parseInt(req.query["hub.lease_seconds"] || 172800) * 1000)) }).where(eq(sites.user_id, user.id)); @@ -58,8 +68,8 @@ export const webhookRoutes = (fastify, _, done) => { const feed = body["feed"]; // Example processing: log the video IDs of new videos const entry = feed.entry[0]; - if(!entry) return reply.code(200).send(); - const [{users: user, sites: site}] = await db.select().from(users).leftJoin(sites, eq(users.id, sites.user_id)).where(eq(users.channel_id, entry["yt:channelId"][0])); + if (!entry) return reply.code(200).send(); + const [{ users: user, sites: site }] = await db.select().from(users).leftJoin(sites, eq(users.id, sites.user_id)).where(eq(users.channel_id, entry["yt:channelId"][0])); if (!user || !site) throw new Error("User not found"); if (user.tokens < 3) throw new Error("Not enough tokens"); diff --git a/src/utils/stripe.js b/src/utils/stripe.js index 3aa043d..0bd3c4e 100644 --- a/src/utils/stripe.js +++ b/src/utils/stripe.js @@ -1,4 +1,8 @@ import loadStripe from 'stripe'; +import { env } from './env'; +import { db } from '../db'; +import { users } from '../db/schemas'; +import { eq } from 'drizzle-orm'; const stripe = await loadStripe('sk_test_51MRcs0FrdGTeTMwdCgd6Z3I1913esqFD2W171b1W4PnRsdxfOCDrwtawiKFgS7R2ZDkWTSqfxp5Gl1GTd8aospWv00vFXvO6iC'); @@ -9,10 +13,10 @@ const stripe = await loadStripe('sk_test_51MRcs0FrdGTeTMwdCgd6Z3I1913esqFD2W171b * @returns {import('stripe-v3').customer.create} */ export async function createCustomer(email, name) { - return (await stripe.customers.create({ - email: email, - name: name, - })); + return (await stripe.customers.create({ + email: email, + name: name, + })); } /** @@ -20,13 +24,84 @@ export async function createCustomer(email, name) { * @param {string} stripe_id */ export async function getBillingDashboard(stripe_id, return_url) { - const session = await stripe.billingPortal.sessions.create({ - customer: stripe_id, - return_url, - flow_data: { - type: 'payment_method_update', - }, + const session = await stripe.billingPortal.sessions.create({ + customer: stripe_id, + return_url + }); + + return session.url; +} + +export async function createCheckout(price_id, customer_id, metadata = {}, subscription_data = {}) { + const session = await stripe.checkout.sessions.create({ + mode: 'subscription', + customer: customer_id, + line_items: [ + { + price: price_id, + quantity: 1 + }, + ], + metadata, + subscription_data, + success_url: env.FRONTEND_URL + '/payments/success?session_id={CHECKOUT_SESSION_ID}', + cancel_url: env.FRONTEND_URL, + }); + + return session.url; +} + +export async function handleWebhook(body, signature) { + let data; + let eventType; + // Check if webhook signing is configured. + const webhookSecret = 'whsec_58bf2c639c4fbe412fa9338e1de48a7be52748eb36fc912af92d535b4897487a'; + if (webhookSecret) { + // Retrieve the event by verifying the signature using the raw body and secret. + let event; + try { + event = stripe.webhooks.constructEvent( + body, + signature, + webhookSecret + ); + } catch (err) { + console.log(err); + console.log(`⚠️ Webhook signature verification failed.`); + throw new Error("Verification failed") + } + // Extract the object from the event. + data = event.data; + eventType = event.type; + } else { + // Webhook signing is recommended, but if the secret is not configured in `config.js`, + // retrieve the event data directly from the request body. + data = body.data; + eventType = body.type; + } + + switch (eventType) { + case 'checkout.session.completed': + console.log("new customer!") + break; + case 'invoice.paid': + console.log("invoice paid!") + const [user] = await db.select().from(users).where(eq(users.stripe_id, data.object.customer)); + console.log(data); + if (!user) throw new Error("Failed to get user ID"); + let currentDate = new Date(); + let nextMonthDate = new Date(currentDate); + nextMonthDate.setMonth(currentDate.getMonth() + 1); + + await db.update(users).set({ + subscribed_until: nextMonthDate, + subscription_tier: "basic" }); - - return session.url; + break; + case 'invoice.payment_failed': + console.log("broke boy!") + break; + default: + break; + } } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3676881..d7bf478 100644 --- a/yarn.lock +++ b/yarn.lock @@ -265,7 +265,7 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@neondatabase/serverless@>=0.1": +"@neondatabase/serverless@^0.9.3", "@neondatabase/serverless@>=0.1": version "0.9.3" resolved "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.9.3.tgz" integrity sha512-6ZBK8asl2Z3+ADEaELvbaVVGVlmY1oAzkxxZfpmXPKFuJhbDN+5fU3zYBamsahS/Ch1zE+CVWB3R+8QEI2LMSw== @@ -1477,6 +1477,11 @@ bundle-name@^3.0.0: dependencies: run-applescript "^5.0.0" +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz" @@ -1761,6 +1766,11 @@ delayed-stream@~1.0.0: resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +depd@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" @@ -2524,6 +2534,15 @@ fastify-plugin@^4.0.0, fastify-plugin@^4.5.1: resolved "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz" integrity sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ== +fastify-raw-body@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/fastify-raw-body/-/fastify-raw-body-4.3.0.tgz" + integrity sha512-F4o8ZIMVx4YoxGfwrZys6wyjl40gF3Yv6AWWRy62ozFAyZBSS831/uyyCAqKYw3tR73g180ryG98yih6To1PUQ== + dependencies: + fastify-plugin "^4.0.0" + raw-body "^2.5.1" + secure-json-parse "^2.4.0" + fastify@^4.25.0: version "4.25.0" resolved "https://registry.npmjs.org/fastify/-/fastify-4.25.0.tgz" @@ -2973,6 +2992,17 @@ hosted-git-info@^2.1.4: resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + https-proxy-agent@^7.0.1: version "7.0.4" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz" @@ -2998,6 +3028,13 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" @@ -3079,7 +3116,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@~2.0.1, inherits@2: +inherits@~2.0.1, inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4041,7 +4078,7 @@ pg-int8@1.0.1: resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== -pg-native@^3.1.0, pg-native@>=3.0.1: +pg-native@>=3.0.1: version "3.1.0" resolved "https://registry.npmjs.org/pg-native/-/pg-native-3.1.0.tgz" integrity sha512-nhMf/d6ypOc6jtUYlfGkLyPkYK+boor0FziOcsnqVxQZlGIXjBlSp3bNkW27O1RN46JlupjziIr1NP3aCCyWOw== @@ -4331,6 +4368,16 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +raw-body@^2.5.1: + version "2.5.2" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + read-pkg-up@^7.0.1: version "7.0.1" resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz" @@ -4534,6 +4581,11 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz" integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + sax@^1.1.3, sax@^1.2.4, sax@>=0.6.0: version "1.4.1" resolved "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz" @@ -4616,6 +4668,11 @@ set-function-name@^2.0.0: functions-have-names "^1.2.3" has-property-descriptors "^1.0.0" +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" @@ -4747,6 +4804,11 @@ split2@^4.0.0, split2@^4.1.0: resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + stream-wormhole@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/stream-wormhole/-/stream-wormhole-1.1.0.tgz" @@ -4953,6 +5015,11 @@ toad-cache@^3.3.0: resolved "https://registry.npmjs.org/toad-cache/-/toad-cache-3.4.1.tgz" integrity sha512-T0m3MxP3wcqW0LaV3dF1mHBU294sgYSm4FOpa5eEJaYO7PqJZBOjZEQI1y4YaKNnih1FXCEYTWDS9osCoTUefg== +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + tr46@~0.0.3: version "0.0.3" resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" @@ -5112,6 +5179,11 @@ undici-types@~5.26.4: resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +unpipe@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + untildify@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz"