add countdown apis

This commit is contained in:
Gabriel Brown 2024-10-25 16:57:49 -05:00
parent 2c2cb819e1
commit 3d09199702
7 changed files with 592 additions and 3 deletions

View File

@ -5,6 +5,8 @@
await import("./src/env.js");
/** @type {import("next").NextConfig} */
const config = {};
const config = {
};
export default config;

View File

@ -20,16 +20,20 @@
"drizzle-orm": "^0.33.0",
"geist": "^1.3.1",
"next": "^14.2.15",
"pg": "^8.13.0",
"postgres": "^3.4.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"socket.io": "^4.8.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/eslint": "^8.56.12",
"@types/node": "^20.16.13",
"@types/pg": "^8.11.10",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@types/socket.io": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^8.10.0",
"@typescript-eslint/parser": "^8.10.0",
"drizzle-kit": "^0.24.2",

357
pnpm-lock.yaml generated
View File

@ -13,13 +13,16 @@ importers:
version: 0.10.1(typescript@5.6.3)(zod@3.23.8)
drizzle-orm:
specifier: ^0.33.0
version: 0.33.0(@types/react@18.3.11)(postgres@3.4.4)(react@18.3.1)
version: 0.33.0(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(postgres@3.4.4)(react@18.3.1)
geist:
specifier: ^1.3.1
version: 1.3.1(next@14.2.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
next:
specifier: ^14.2.15
version: 14.2.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
pg:
specifier: ^8.13.0
version: 8.13.0
postgres:
specifier: ^3.4.4
version: 3.4.4
@ -29,6 +32,9 @@ importers:
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
socket.io:
specifier: ^4.8.0
version: 4.8.0
zod:
specifier: ^3.23.8
version: 3.23.8
@ -39,12 +45,18 @@ importers:
'@types/node':
specifier: ^20.16.13
version: 20.16.13
'@types/pg':
specifier: ^8.11.10
version: 8.11.10
'@types/react':
specifier: ^18.3.11
version: 18.3.11
'@types/react-dom':
specifier: ^18.3.1
version: 18.3.1
'@types/socket.io':
specifier: ^3.0.2
version: 3.0.2
'@typescript-eslint/eslint-plugin':
specifier: ^8.10.0
version: 8.10.0(@typescript-eslint/parser@8.10.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)
@ -505,6 +517,9 @@ packages:
'@rushstack/eslint-patch@1.10.4':
resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==}
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
@ -529,6 +544,12 @@ packages:
typescript:
optional: true
'@types/cookie@0.4.1':
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
'@types/cors@2.8.17':
resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
'@types/eslint@8.56.12':
resolution: {integrity: sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==}
@ -544,6 +565,9 @@ packages:
'@types/node@20.16.13':
resolution: {integrity: sha512-GjQ7im10B0labo8ZGXDGROUl9k0BNyDgzfGpb4g/cl+4yYDWVKcozANF4FGr4/p0O/rAkQClM6Wiwkije++1Tg==}
'@types/pg@8.11.10':
resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==}
'@types/prop-types@15.7.13':
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
@ -553,6 +577,10 @@ packages:
'@types/react@18.3.11':
resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==}
'@types/socket.io@3.0.2':
resolution: {integrity: sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==}
deprecated: This is a stub types definition. socket.io provides its own type definitions, so you do not need this installed.
'@typescript-eslint/eslint-plugin@8.10.0':
resolution: {integrity: sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -613,6 +641,10 @@ packages:
'@ungap/structured-clone@1.2.0':
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -709,6 +741,10 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
base64id@2.0.0:
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
engines: {node: ^4.5.0 || >= 5.9}
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@ -770,6 +806,14 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
cors@2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@ -941,6 +985,14 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
engine.io-parser@5.2.3:
resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
engines: {node: '>=10.0.0'}
engine.io@6.6.2:
resolution: {integrity: sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==}
engines: {node: '>=10.2.0'}
enhanced-resolve@5.17.1:
resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==}
engines: {node: '>=10.13.0'}
@ -1475,6 +1527,14 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@ -1503,6 +1563,10 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
next@14.2.15:
resolution: {integrity: sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==}
engines: {node: '>=18.17.0'}
@ -1561,6 +1625,9 @@ packages:
resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==}
engines: {node: '>= 0.4'}
obuf@1.1.2:
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@ -1602,6 +1669,48 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
pg-cloudflare@1.1.1:
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
pg-connection-string@2.7.0:
resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==}
pg-int8@1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
engines: {node: '>=4.0.0'}
pg-numeric@1.0.2:
resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
engines: {node: '>=4'}
pg-pool@3.7.0:
resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==}
peerDependencies:
pg: '>=8.0'
pg-protocol@1.7.0:
resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==}
pg-types@2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
engines: {node: '>=4'}
pg-types@4.0.2:
resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==}
engines: {node: '>=10'}
pg@8.13.0:
resolution: {integrity: sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
pg-native:
optional: true
pgpass@1.0.5:
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -1666,6 +1775,41 @@ packages:
resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==}
engines: {node: ^10 || ^12 || >=14}
postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'}
postgres-array@3.0.2:
resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==}
engines: {node: '>=12'}
postgres-bytea@1.0.0:
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
engines: {node: '>=0.10.0'}
postgres-bytea@3.0.0:
resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==}
engines: {node: '>= 6'}
postgres-date@1.0.7:
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
engines: {node: '>=0.10.0'}
postgres-date@2.1.0:
resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==}
engines: {node: '>=12'}
postgres-interval@1.2.0:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'}
postgres-interval@3.0.0:
resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==}
engines: {node: '>=12'}
postgres-range@1.1.4:
resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==}
postgres@3.4.4:
resolution: {integrity: sha512-IbyN+9KslkqcXa8AO9fxpk97PA4pzewvpi2B3Dwy9u4zpV32QicaEdgmF3eSQUzdRk7ttDHQejNgAEr4XoeH4A==}
engines: {node: '>=12'}
@ -1842,6 +1986,17 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
socket.io-adapter@2.5.5:
resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==}
socket.io-parser@4.2.4:
resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
engines: {node: '>=10.0.0'}
socket.io@4.8.0:
resolution: {integrity: sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==}
engines: {node: '>=10.2.0'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@ -1853,6 +2008,10 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
@ -2008,6 +2167,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
which-boxed-primitive@1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
@ -2043,6 +2206,22 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
ws@8.17.1:
resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
yaml@2.6.0:
resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==}
engines: {node: '>= 14'}
@ -2321,6 +2500,8 @@ snapshots:
'@rushstack/eslint-patch@1.10.4': {}
'@socket.io/component-emitter@3.1.2': {}
'@swc/counter@0.1.3': {}
'@swc/helpers@0.5.5':
@ -2341,6 +2522,12 @@ snapshots:
optionalDependencies:
typescript: 5.6.3
'@types/cookie@0.4.1': {}
'@types/cors@2.8.17':
dependencies:
'@types/node': 20.16.13
'@types/eslint@8.56.12':
dependencies:
'@types/estree': 1.0.6
@ -2356,6 +2543,12 @@ snapshots:
dependencies:
undici-types: 6.19.8
'@types/pg@8.11.10':
dependencies:
'@types/node': 20.16.13
pg-protocol: 1.7.0
pg-types: 4.0.2
'@types/prop-types@15.7.13': {}
'@types/react-dom@18.3.1':
@ -2367,6 +2560,14 @@ snapshots:
'@types/prop-types': 15.7.13
csstype: 3.1.3
'@types/socket.io@3.0.2':
dependencies:
socket.io: 4.8.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
'@typescript-eslint/eslint-plugin@8.10.0(@typescript-eslint/parser@8.10.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)':
dependencies:
'@eslint-community/regexpp': 4.11.1
@ -2450,6 +2651,11 @@ snapshots:
'@ungap/structured-clone@1.2.0': {}
accepts@1.3.8:
dependencies:
mime-types: 2.1.35
negotiator: 0.6.3
acorn-jsx@5.3.2(acorn@8.13.0):
dependencies:
acorn: 8.13.0
@ -2563,6 +2769,8 @@ snapshots:
balanced-match@1.0.2: {}
base64id@2.0.0: {}
binary-extensions@2.3.0: {}
brace-expansion@1.1.11:
@ -2627,6 +2835,13 @@ snapshots:
concat-map@0.0.1: {}
cookie@0.7.2: {}
cors@2.8.5:
dependencies:
object-assign: 4.1.1
vary: 1.1.2
cross-spawn@7.0.3:
dependencies:
path-key: 3.1.1
@ -2700,9 +2915,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
drizzle-orm@0.33.0(@types/react@18.3.11)(postgres@3.4.4)(react@18.3.1):
drizzle-orm@0.33.0(@types/pg@8.11.10)(@types/react@18.3.11)(pg@8.13.0)(postgres@3.4.4)(react@18.3.1):
optionalDependencies:
'@types/pg': 8.11.10
'@types/react': 18.3.11
pg: 8.13.0
postgres: 3.4.4
react: 18.3.1
@ -2712,6 +2929,25 @@ snapshots:
emoji-regex@9.2.2: {}
engine.io-parser@5.2.3: {}
engine.io@6.6.2:
dependencies:
'@types/cookie': 0.4.1
'@types/cors': 2.8.17
'@types/node': 20.16.13
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.7.2
cors: 2.8.5
debug: 4.3.7
engine.io-parser: 5.2.3
ws: 8.17.1
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
enhanced-resolve@5.17.1:
dependencies:
graceful-fs: 4.2.11
@ -3444,6 +3680,12 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
@ -3468,6 +3710,8 @@ snapshots:
natural-compare@1.4.0: {}
negotiator@0.6.3: {}
next@14.2.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@next/env': 14.2.15
@ -3535,6 +3779,8 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.0.0
obuf@1.1.2: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
@ -3575,6 +3821,53 @@ snapshots:
lru-cache: 10.4.3
minipass: 7.1.2
pg-cloudflare@1.1.1:
optional: true
pg-connection-string@2.7.0: {}
pg-int8@1.0.1: {}
pg-numeric@1.0.2: {}
pg-pool@3.7.0(pg@8.13.0):
dependencies:
pg: 8.13.0
pg-protocol@1.7.0: {}
pg-types@2.2.0:
dependencies:
pg-int8: 1.0.1
postgres-array: 2.0.0
postgres-bytea: 1.0.0
postgres-date: 1.0.7
postgres-interval: 1.2.0
pg-types@4.0.2:
dependencies:
pg-int8: 1.0.1
pg-numeric: 1.0.2
postgres-array: 3.0.2
postgres-bytea: 3.0.0
postgres-date: 2.1.0
postgres-interval: 3.0.0
postgres-range: 1.1.4
pg@8.13.0:
dependencies:
pg-connection-string: 2.7.0
pg-pool: 3.7.0(pg@8.13.0)
pg-protocol: 1.7.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.1.1
pgpass@1.0.5:
dependencies:
split2: 4.2.0
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@ -3628,6 +3921,28 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
postgres-array@2.0.0: {}
postgres-array@3.0.2: {}
postgres-bytea@1.0.0: {}
postgres-bytea@3.0.0:
dependencies:
obuf: 1.1.2
postgres-date@1.0.7: {}
postgres-date@2.1.0: {}
postgres-interval@1.2.0:
dependencies:
xtend: 4.0.2
postgres-interval@3.0.0: {}
postgres-range@1.1.4: {}
postgres@3.4.4: {}
prelude-ls@1.2.1: {}
@ -3763,6 +4078,36 @@ snapshots:
signal-exit@4.1.0: {}
socket.io-adapter@2.5.5:
dependencies:
debug: 4.3.7
ws: 8.17.1
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
socket.io-parser@4.2.4:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.7
transitivePeerDependencies:
- supports-color
socket.io@4.8.0:
dependencies:
accepts: 1.3.8
base64id: 2.0.0
cors: 2.8.5
debug: 4.3.7
engine.io: 6.6.2
socket.io-adapter: 2.5.5
socket.io-parser: 4.2.4
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@ -3772,6 +4117,8 @@ snapshots:
source-map@0.6.1: {}
split2@4.2.0: {}
streamsearch@1.1.0: {}
string-width@4.2.3:
@ -3977,6 +4324,8 @@ snapshots:
util-deprecate@1.0.2: {}
vary@1.1.2: {}
which-boxed-primitive@1.0.2:
dependencies:
is-bigint: 1.0.4
@ -4035,6 +4384,10 @@ snapshots:
wrappy@1.0.2: {}
ws@8.17.1: {}
xtend@4.0.2: {}
yaml@2.6.0: {}
yocto-queue@0.1.0: {}

View File

@ -0,0 +1,37 @@
'use server';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getCountdown } from '~/server/functions';
import { middleware } from '~/middleware';
import type { Countdown } from '~/server/types';
export const GET = async (request: NextRequest) => {
const middlewareResponse = await middleware(request);
if (middlewareResponse) return middlewareResponse;
try {
const url = new URL(request.url);
const userId = Number.parseInt(url.searchParams.get('userId') ?? '');
if (!userId || isNaN(userId))
return NextResponse.json(
{ message: 'Missing userId' }, { status: 400 }
);
const countdown: Countdown | null = await getCountdown(userId);
if (!countdown) {
return NextResponse.json(
{ message: 'No countdown found' }, { status: 404 }
);
}
return NextResponse.json(countdown);
} catch (error) {
console.error('Error getting countdown:', error);
if (error instanceof Error) {
return NextResponse.json(
{ message: error.message }, { status: 500 }
);
} else {
return NextResponse.json(
{ message: 'Unknown error occurred' }, { status: 500 }
);
}
}
};

View File

@ -0,0 +1,40 @@
'use server';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { setCountdown } from '~/server/functions';
import { middleware } from '~/middleware';
import type { Countdown } from '~/server/types';
export const POST = async (request: NextRequest) => {
const middlewareResponse = await middleware(request);
if (middlewareResponse) return middlewareResponse;
try {
const body = await request.json() as {
userId: number;
countdown: Countdown;
};
const { userId, countdown } = body;
if (!userId || !countdown || isNaN(userId))
return NextResponse.json(
{ message: 'Missing userId or countdown' }, { status: 400 }
);
const newCountdown: Countdown = await setCountdown(userId, countdown);
if (!newCountdown) {
return NextResponse.json(
{ message: 'Error setting countdown' }, { status: 500 }
);
}
return NextResponse.json(newCountdown);
} catch (error) {
console.error('Error setting countdown:', error);
if (error instanceof Error) {
return NextResponse.json(
{ message: error.message }, { status: 500 }
);
} else {
return NextResponse.json(
{ message: 'Unknown error occurred' }, { status: 500 }
);
}
}
};

View File

@ -0,0 +1,80 @@
// WebSocket for client to receive messages
//import { Server } from 'socket.io';
//import { Client, Notification } from 'pg';
//import { NextResponse } from 'next/server';
//import type { NextRequest } from 'next/server';
//import type { Message } from '~/server/types';
//let isInitialized = false;
//const io = new Server();
//const pgClient = new Client({
//connectionString: process.env.DATABASE_URL,
//});
//const initializePostgres = async () => {
//if (isInitialized) return;
//try {
//await pgClient.connect();
//console.log('Connected to PostgreSQL');
//await pgClient.query('LISTEN new_message');
//pgClient.on('notification', (msg: Notification) => {
//try {
//const newMessage: Message = JSON.parse(msg.payload ?? '{}') as Message;
//const { receiverId, text, id } = newMessage;
//if (receiverId && typeof receiverId === 'number') {
//io.to(receiverId.toString()).emit('message', newMessage);
//console.log(`Message sent to room ${receiverId} with text: ${text}`);
//} else {
//console.error('Invalid receiverId:', receiverId);
//}
//} catch (error) {
//console.error('Error parsing notification payload:', error);
//}
//});
//isInitialized = true;
//} catch (error) {
//console.error('Error connecting to PostgreSQL:', error);
//throw error;
//}
//};
//io.on('connection', (socket) => {
//console.log('WebSocket client connected', socket.id);
//socket.on('join', async (userId: number) => {
//const roomId = userId.toString();
//await socket.join(roomId);
//console.log(`WebSocket client joined room ${userId}`);
//});
//socket.on('error', (error) => {
//console.error('WebSocket error:', error);
//});
//socket.on('disconnect', () => {
//console.log('WebSocket client disconnected');
//});
//});
//export const runtime = 'edge';
//export const GET = async (request: NextRequest) => {
//try {
//await initializePostgres();
//// @ts-expect-error: Socket.IO types conflict with Next.js Request
//const upgrade = await io.handleUpgrade(request);
//if (!upgrade || !upgrade.headers)
//throw new Error('Failed to upgrade connection');
//return new NextResponse(null, {
//status: 101,
//headers: upgrade.headers,
//});
//} catch (error) {
//console.error('Error handling upgrade:', error);
//return NextResponse.json({ message: 'Internal server error' }, { status: 500 });
//}
//};

View File

@ -26,6 +26,29 @@ export const getUser = async (userId: number) => {
}
};
export const getRelationship = async (userId: number) => {
try {
const user = await getUser(userId);
if (!user) throw new Error("User not found");
const userRelationship = await db.select()
.from(schema.userRelationships)
.where(eq(schema.userRelationships.userId, user.id))
.limit(1)
.then(results => results[0]);
if (!userRelationship) throw new Error('No relationships found for user');
const relationship = await db.select()
.from(schema.relationships)
.where(eq(schema.relationships.id, userRelationship.relationshipId))
.limit(1)
.then(results => results[0] as Relationship);
if (!relationship) throw new Error('Relationship not found');
return relationship;
} catch (error) {
console.error('Error getting relationship:', error);
throw error;
}
};
export const getInitialDataByAppleId = async (appleId: string) => {
try {
const users = await db.select().from(schema.users)
@ -358,3 +381,53 @@ export const sendMessage = async (message: Message) => {
throw error;
}
};
export const setCountdown = async (userId: number, countdown: Countdown) => {
try {
const user = await getUser(userId);
if (!user) throw new Error("User not found");
const relationship = await getRelationship(userId);
if (!relationship) throw new Error("Relationship not found");
const existingCountdown: Countdown | null = await getCountdown(userId);
let result;
if (existingCountdown !== null) {
result = await db.update(schema.countdowns)
.set({ title: countdown.title, date: countdown.date })
.where(eq(schema.countdowns.id, existingCountdown.id))
.returning();
} else {
result = await db.insert(schema.countdowns)
.values({
relationshipId: relationship.id,
title: countdown.title,
date: countdown.date,
}).returning();
}
return result[0] as Countdown;
} catch (error) {
console.error('Error setting countdown:', error);
throw error;
}
};
export const getCountdown = async (userId: number) => {
try {
const user = await getUser(userId);
if (!user) throw new Error("User not found");
const relationship = await getRelationship(userId);
if (!relationship) throw new Error("Relationship not found");
const countdown = await db.select()
.from(schema.countdowns)
.where(eq(schema.countdowns.relationshipId, relationship.id))
.limit(1)
.then(results => results[0] as Countdown);
if (!countdown) {
console.log('No countdown found');
return null;
}
return countdown;
} catch (error) {
console.error('Error getting countdown:', error);
throw error;
}
};