diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 63d3c53..eacda62 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,8 +1,10 @@ { "permissions": { "allow": [ - "Bash(pnpm run build:*)", - "Bash(npx tsc:*)" + "Bash(pnpm build:*)", + "Bash(pnpm deploy:firebase:*)", + "Bash(firebase deploy:*)", + "Bash(firebase functions:log:*)" ] } } diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f18a704 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,71 @@ +# ========================================= +# Docker Build Context Ignore File +# ========================================= +# This file is at the project root because Docker builds +# use the root as the build context + +# Git +.git +.github +.gitignore + +# All node_modules (will be installed fresh in Docker) +node_modules +**/node_modules + +# Build outputs (rebuilt in Docker) +.next +**/.next +**/dist +**/.turbo +.turbo + +# Environment files (injected at runtime) +.env +.env.* +!.env.example +sites/**/.env* + +# Logs and temp files +*.log +.cache +**/.cache + +# Firebase (not needed for Cloud Run) +firebase-functions +.firebase +.firebaserc +firebase.json + +# IDE +.vscode +.idea +**/.vscode +**/.idea + +# Testing and coverage +coverage +.nyc_output +**/coverage + +# OS files +.DS_Store +Thumbs.db +**/.DS_Store +**/Thumbs.db + +# Documentation +*.md +!README.md + +# Lock files are needed for reproducible builds +# !pnpm-lock.yaml + +# Other sites we don't need (still copied for workspace resolution, but not used) +# sites/mainweb + +# Build artifacts +build_*.log +build_*.txt +firebase_logs.txt +nul diff --git a/.env.cloudrun.example b/.env.cloudrun.example new file mode 100644 index 0000000..77daf64 --- /dev/null +++ b/.env.cloudrun.example @@ -0,0 +1,27 @@ +# ========================================= +# Cloud Run Environment Variables Template +# ========================================= +# Copy sensitive values from .env.production or configure +# via Google Secret Manager for production deployments + +# Node.js Configuration +NODE_ENV=production +PORT=8080 + +# Application URLs (update with your actual Cloud Run URL after first deployment) +AUTH_URL=https://dsgt-portal.web.app +NEXT_PUBLIC_APP_URL=https://dsgt-portal.web.app + +# ========================================= +# SENSITIVE VARIABLES - Use Secret Manager! +# ========================================= +# The following should be configured via Cloud Run console +# or Google Secret Manager, NOT stored in this file: +# +# DATABASE_URL=your-database-connection-string +# AUTH_SECRET=your-nextauth-secret +# AUTH_GOOGLE_ID=your-google-oauth-client-id +# AUTH_GOOGLE_SECRET=your-google-oauth-client-secret +# AUTH_GITHUB_ID=your-github-oauth-client-id +# AUTH_GITHUB_SECRET=your-github-oauth-client-secret +# RESEND_API_KEY=your-resend-api-key diff --git a/.firebase/hosting.c2l0ZXNcanVkZ2luZ1xwdWJsaWM.cache b/.firebase/hosting.c2l0ZXNcanVkZ2luZ1xwdWJsaWM.cache new file mode 100644 index 0000000..f57067f --- /dev/null +++ b/.firebase/hosting.c2l0ZXNcanVkZ2luZ1xwdWJsaWM.cache @@ -0,0 +1,25 @@ +images/logos/shepcenter.jpeg,1767556163025,a57a0bf31aabaab7285fcfdb7637bf513c40bc29ec5e82fd79468d741d37f127 +images/logos/storm.png,1767556163185,49090aaf8b26eac91881de46e7c90d9eda8228af55f40b1a46f1bc0f969f2966 +images/logos/birdclef.png,1767556162911,463d632fcd787259c8aceb0f8ad53c89414eacd776b8b566e4fe1bcee70072da +images/logos/blueconduit.png,1767556162930,726e598bbc52b99c907087d2ddced839c61df4ab32bf95d5aa00cccd3bfd140a +images/dsgt/sports_icon.png,1767556162796,aee29d6323a3222c9393642dfd1a6b61b23890ffba2bbe484a3b3876c56615ef +images/dsgt/financial_icon.png,1767556162685,560a6e3ba0ab30fea54596721cf16acb68c6410aeaa42e285a7cd3c8bcae72de +images/dsgt/favicon-16x16.png,1767556162630,96e40605151630d7b4ef81deb967a1175580f4bb9c5fb332960c92af95efcc9a +images/dsgt/favicon-32x32.png,1767556162650,1c59159f4c69cb8812c8e0b6963252b011a634f2613024785f340890175b68e0 +images/dsgt/Icon-128.png,1767556162546,b7909633f01379164dde11e53e42af2d12f7ad104762e36ebb5c8e05af0707f6 +images/dsgt/favicon.ico,1767556162666,9960c28b7d6ac352cf1ba11369584c8247271cb77e217561c647620d67872eea +images/dsgt/Icon-196.png,1767556162563,d785daa440354422616e9889f35616604710e9dd70313c6915b06fdd630b5b5a +images/dsgt/heathcare_icon.png,1767556162703,b6af8ce1284fff38bca3f01dc825bd6ec20923910b4f5ac1298e1096bb0bb236 +images/dsgt/apple-touch-icon.png,1767556162607,d60615679e857bfba6b5e33b60dd6b92f06af5a1b51c1ecb18aac24efec1e51d +images/dsgt/logo20.ico,1767556162724,11235ade4b9531c843a4c099f92e7d282ba0b3e2d70c5761b84d9ef057620871 +images/logos/gtaa.png,1767556163001,615cfea20fb380d6528b2a63fab185030efc89273855b1f3202f4a301b45ceba +images/logos/Mentra.png,1767556162851,69597ac2d91b8290d657c207b49b1a322c6066b020e1ae41f6127264c81eeaee +images/dsgt/Icon-512.png,1767556162586,54e168ed5dcdc6fdc61bbac8c99342f016c185708d8d04efe3f0b063fb691315 +images/dsgt/logos-20.ico,1767556162747,f283f6e06664b8a6710adb6f120c5b6a7bfd7967b95ca2205c3376a51641f852 +images/logos/furnichanter.png,1767556162972,156f59d5d1dfd47a3d57212c16846f3ce0848e7268dabba3971873f685d2caf0 +images/dsgt/square-logo.png,1767556162820,5af85cd153427f89d01c1109e64d5110163bf32b6d199ea1817bd41048d106ed +images/logos/arc-logo-v3.png,1767556162894,7d0f48acde15ea2c9dd41653ddc8a52e2b0937df99fd0f501edec6686acb9e68 +images/dsgt/logos-20.png,1767556162775,233de3c083e7fff89c44d462e56e0a813dc878b29b0329d141ebd5dc27398896 +images/logos/dlp4.png,1767556162950,14e1e01f0d3436e9c13f64f400c16d6fb52471bdb2dfccaf7bcd7acc1f579cc3 +images/logos/stock.png,1767556163164,305debac9897de35133db980e481ed646355f3af8c84aa111e5e78e12d9edee4 +images/logos/trading.png,1767556163597,187df50fb7e40b3e7c7d1a0c9b678b588a11a90c6415265cb326f284a0a75764 diff --git a/.firebase/hosting.c2l0ZXNcbWFpbndlYlxvdXQ.cache b/.firebase/hosting.c2l0ZXNcbWFpbndlYlxvdXQ.cache new file mode 100644 index 0000000..dfd8127 --- /dev/null +++ b/.firebase/hosting.c2l0ZXNcbWFpbndlYlxvdXQ.cache @@ -0,0 +1,91 @@ +next.svg,1767556162023,0665b9ab4493aa9d7e988b57024e28772ae543e804b8cf6b0a3633854b7c3c7f +manifest.json,1767556162010,a683f0cd827b000a28a4dbbbe3d336394e1650b811b03d8d1af094a2b3add866 +turborepo.svg,1767556162054,cf4a25dc416d4668bb4afd0ca002d5263f8b50d30c67f2533be7d150bddb5456 +vercel.svg,1767556162070,41e95a86eeec887b8b898097594cf4c4bc5da8f58faf05830e1229d7dcbeea59 +robots.txt,1767556162038,2de27ad6b0ca2f3a304bc56d0096eb3a807fa97e4980bea76619f795eab38c42 +logo128.png,1767556161967,b7909633f01379164dde11e53e42af2d12f7ad104762e36ebb5c8e05af0707f6 +favicon-32x32.png,1767556161918,1c59159f4c69cb8812c8e0b6963252b011a634f2613024785f340890175b68e0 +favicon-16x16.png,1767556161905,96e40605151630d7b4ef81deb967a1175580f4bb9c5fb332960c92af95efcc9a +circles.svg,1767556161891,91bd260651a2afe3cdc632ea37b7ace2a2a68eae6a7dca855733571e2da3ef30 +background-design-1.png,1767556161874,b65b749e62adb599e8ccd10dd31d9077ae8cd9614ee215a0467d8e3dc969a153 +index.html,1768708090230,6ee92051dcea6aeaffe134cf315d46be501c064991e45fb3dcadcddbeb3da58c +favicon.ico,1768708090221,9960c28b7d6ac352cf1ba11369584c8247271cb77e217561c647620d67872eea +404.html,1768708132177,8cff1af02d13106df56ea14024ecabd5a9a54867c3ee21fcd58ac402ac40fc52 +index.txt,1768708090230,927c5a4299eea16cf5312db6e838d1e2d73c4f9997b53c1ae7b9f808a5fc6323 +_next/static/media/hero3--export.7a918f56.svg,1768708021926,7d96637bc73baa76c82b880f72ca40d8d0b09db2f4496c96d8bbabe76fac7481 +_next/static/media/hero2-mobile--export.66989f90.svg,1768708021939,97132306377652a495fe096d59d235ea8f8daf4735109f72e5f1a124c6f76a2a +logo196.png,1767556161981,d785daa440354422616e9889f35616604710e9dd70313c6915b06fdd630b5b5a +_next/static/media/eaa89f43bcff2436-s.p.woff2,1768708056708,b515dd90e543b7d58f6deba32c34d6895dcf3d5e3e3d5551279db1b2fc01c8b8 +_next/static/media/e127622016e145c0.p.woff2,1768708056709,56a7ed1c7604f2cfad35bea5c33a72898dab050c4b4cf4f6b0d9ecbcdcda3b58 +_next/static/media/dc1cce3fa2af61db-s.p.woff2,1768708056708,ea5007c3e87efaef4c594433d50344107847559b704a986ed19aa40cfe7d44fd +_next/static/media/da60e700622ebc65.p.woff2,1768708056710,df86e307ace4e9dc4d0bbd4455b3ab510ad7ee5b0305d8fad0fb545af47197d5 +_next/static/media/ca070aef19a160ac-s.p.woff2,1768708056708,8b34795dc5ab55699dbac35ade3c3bdc2abbee61828ae7b0048215286c57783f +_next/static/media/b0f83fa59267e7b6.p.woff2,1768708056743,a27724d1679dc02fbafe2895542a9cd144f579fb997c6dd6788373b82c264d3f +_next/static/media/acd8756c4a5b05ec.p.woff2,1768708056709,cffdb9897db53cb2b2eb88cae82a6f60826ae39bbbe9e9e25fb28798c0694863 +_next/static/media/7f6ca03465f53582.p.woff2,1768708056749,c6659da5b890353b05830ccb798be06955ff7b54ce373fda3e47c4f61a00fba8 +_next/static/media/8a5ed7a420e77c77-s.p.woff2,1768708056709,b8717e8eb60d08dcfba18cae79eeb20be95d9cbfd40667041074b210b19d3036 +_next/static/media/apple-touch-icon.18c4af19.png,1768708021926,d60615679e857bfba6b5e33b60dd6b92f06af5a1b51c1ecb18aac24efec1e51d +_next/static/media/gtaa.f15d59c5.png,1768708021918,615cfea20fb380d6528b2a63fab185030efc89273855b1f3202f4a301b45ceba +_next/static/gLTOM3eqSFXM4mqD3_5m4/_ssgManifest.js,1768708094318,dc28a4dc92fe352ed5d2201bd3972ce47691bc8e89e0400a68d1541d0567c6d5 +_next/static/gLTOM3eqSFXM4mqD3_5m4/_buildManifest.js,1768708056831,abbd52ad9fb4d9039bd92c66348455081f45f37fb8753f37027e3ca1cd20d391 +_next/static/chunks/webpack-ebdc40c7b3df2980.js,1768708056885,4bff4e745acf5d229cbe7fefad9ee483ea0b2c91ca399b8c2af3dd729955d07f +_next/static/chunks/main-app-fb0ee16db92b0db2.js,1768708056838,a5620191ca8912b0173dfb441d409609c7c57ad5f49dea0cfa0489c81c26ecf7 +_next/static/css/d91cb33af42543fa.css,1768708056890,3300026f05ef1229e748ea5bb9d3ec0735955a37a0b32c167f704ca433fabf64 +_next/static/media/785370768b5a7618-s.p.woff2,1768708056709,4d57ef2b25f85cfc72aa89a350aa17081f5b4204134268004284ac3840a4ae73 +_next/static/media/6c2eb4a4397e6726.p.woff2,1768708056742,1022b6f49b0a6cb7773bbeac2e0ae4783a7a82901d63dd9fab9d2c8ba929ef29 +_next/static/media/7d612f06858af31a.p.woff2,1768708056709,ba4574a042d8dd5fb294d93fd1074fb47aaf9ec5002370d72f71ca41c9d3daca +_next/static/media/48c373c2bbb15a2a-s.p.woff2,1768708056709,5f07aee36dbc4e16a91aee45a14789d35a5dc6bf70574e90b5aec569041b173b +_next/static/media/26e35e3aa0f2ff52-s.p.woff2,1768708056709,6fb115d592dd310235d18c4a3a705f2479fbdb337125b6e9657e7b7e32b7d7d5 +_next/static/media/24faed2484bb8b0c-s.p.woff2,1768708056708,2beb4c5a53ddd5120a6179a6973bf8f68a362dbcf04ded082243904a381f19ed +_next/static/media/26bfa5f558072926.p.woff2,1768708056749,c2b783736b2280a4caf39dffbff10f1f7e8fc128b57825f76ea620c5ec297664 +_next/static/media/1e2bfed25cc02ae1.p.woff2,1768708056756,9d09d0dd38814ecb1af5e8f50657fc42b7ee79105a23b6bb2ebe3b0ffe050e80 +_next/static/chunks/826.20e7545f83ce1c50.js,1768708056886,0fc60d2cd606d4f6d4aa18202a02cc9a348f855d5a2a364ab200ef9abe1518d9 +logo512.png,1767556161997,54e168ed5dcdc6fdc61bbac8c99342f016c185708d8d04efe3f0b063fb691315 +_next/static/chunks/511.c4afe65fc4aa4b0f.js,1768708056888,322ba9eebff4554218a30e045e9ecae983a07c6cdc060d604541943ff73d92cf +_next/static/chunks/655.3fc65d6d0da75415.js,1768708056888,70d459f941bded717619bb4275d1d8e9c8d05ccdef6c69d10b84d4f350e0c388 +_next/static/chunks/269-919ec01a9f151dc1.js,1768708056982,b0a21a14f646e03693da70414f2feaca73cd39f6113528295c2cdbdb29324dab +_next/static/chunks/pages/_error-7d5be752d8cdda07.js,1768708056844,bd7dd331671838f8d34ecdbb64775d82e78fcf238d0a8ec8b1ceea50571187c5 +_next/static/chunks/pages/_app-552893fa4c6cf2f7.js,1768708056836,4720aed646bb80aff9c09b485fd892b021dc8030d777ae7b45b096ecedf3bc0f +_next/static/media/alysha.814d9539.png,1768708021847,76d2d5ccb459d41e819df9528f9f55d8c32158cb2a5f054077ef651ff35aab55 +_next/static/media/69a390d3fcb2378e-s.p.woff2,1768708056708,a49cfd8fbbac9317a0ac77278a7297578cc2ef9882b276378033380ddec9b661 +_next/static/chunks/134-e0329fa0531c866d.js,1768708056983,51948439b1d455bf116ad9f4625434cff68bf4f0af71d80424a8ce7f7bb114ff +_next/static/media/square-logo.1162872e.png,1768708021927,5af85cd153427f89d01c1109e64d5110163bf32b6d199ea1817bd41048d106ed +_next/static/media/slide7.882e33a7.jpg,1768708021900,a41caad5bdf1dbd5b6e87fd7864b14df9e8564ac1d96a89bc23bbaeec654f0b2 +_next/static/chunks/app/page-1619d8c9c53625b8.js,1768708056876,6ebfa0fed16c569d8eca8d81a7c12bc951d5b0d37f6dc4b1dc539fd11d20b88f +_next/static/media/arc-logo-v3.65ac35df.png,1768708021915,7d0f48acde15ea2c9dd41653ddc8a52e2b0937df99fd0f501edec6686acb9e68 +_next/static/chunks/polyfills-42372ed130431b0a.js,1768708056817,18e28d3214eda45048d80d3925ea7627b809e69ad2e95f7f98459e9146a61c3d +_next/static/chunks/app/layout-3653b3f5ef654d9b.js,1768708056850,09490e394ad4630bb56c39759384707980a88c18289c7d7df57252034886d87e +_next/static/chunks/app/_not-found/page-b0df47e0e9abfe0b.js,1768708056877,ce582249fe109f72039542c65dde5b8d8fe5f5dc3dc00b23c0d8dee2c21fb140 +_next/static/chunks/app/not-found-399d6e2b47282562.js,1768708056847,b21d94723ff49f11fce4046c79d001b6b1a27aa363567ef5d15d0f8a70c2e8e9 +_next/static/chunks/app/tbd/page-c92a566dd2b74e85.js,1768708056888,cb4fd9424577e55085233e98c565bf758292bd45e21ff1b8fa31d636a4122459 +_next/static/chunks/app/team/page-b91a6d408568e234.js,1768708056846,a91cc0be81726f781db63621be79263285a4c6c1726b46e2f67be71cc1ad6802 +_next/static/chunks/main-5c47ec2c271ec1ec.js,1768708056833,1dfc848920f74066d5030ea77dae0a01cb1dda7b834eaf68b6be02076ea9a8a4 +_next/static/chunks/app/projects/page-fd46ebc20602844f.js,1768708056888,df6e9c138682fc25a2d7238a6a0360d09cb954a09956c8b1ae1283fb2bf09780 +team/index.txt,1768708090231,fef28476786ca59b85837659de74ee9ffee9d9eead629d0c102da5ec665d81ee +tbd/index.txt,1768708090230,1c8bec8c2aab9bec460aeeb7b43b8fd31deaecb2412aa1a0515468cb8b5e3606 +team/index.html,1768708090231,30522bc36aff00b585363d04b0a11c953884c6ca140c5af26d0f5644664c8e28 +projects/index.txt,1768708090231,c88e184c620f8aa87136c85f3a74e721a035032d19e9de8381893e947ca775d4 +tbd/index.html,1768708090230,3123732518aa172aa83a39f0f90e2600d2500ffafbc37f44e4458e0cc1ebf1b6 +404/index.html,1768708132177,8cff1af02d13106df56ea14024ecabd5a9a54867c3ee21fcd58ac402ac40fc52 +projects/index.html,1768708090244,5c7de5760d780c2f24238c7dd44e9cd28a68c54d0620c0b942b7c3911ecd30fc +_next/static/chunks/framework-d8f01f7e25201916.js,1768708056890,389bb15fe322617d2a34782dc4ff3622720e8f0bc10c532a6c0b352f8bde5397 +_next/static/chunks/c3110d36-a7f5a75547d2754b.js,1768708056930,c6f94895a614d996dcd3733502b10910142e5528b983de8d41ef15a70e22aebf +_next/static/chunks/18-63cc162f64059731.js,1768708056982,e607ca174a7d251cdee00056f04286b4369e4197e45f27fb10a688cc6a69bd8d +_next/static/media/slide1.f133cdb4.jpg,1768708021876,a4efbbc9c3b91bd3fc2825f9cca88b2f26f1bc3c101c61e0d8dbf35f69e72fe7 +_next/static/chunks/87c73c54-24122e7b92478d00.js,1768708056973,985e59d795f31f3b06c2d8160c095b89482c97f1cb818a7b8c0ce0ba2a13de5d +_next/static/media/jake.0864e5b5.png,1768708021863,614dcd549127c320fddd39b2ca528b6900165e9de5ac4424a32c81d591603172 +_next/static/media/vidhi.ce363692.jpeg,1768708021888,49cad84fd0da2e2153012f6d84c21c082960f9c77f704235bb88f5d9e8a86338 +_next/static/media/glenne.edd0c12b.png,1768708021862,3bbf2bc89221e96c0692271b78e920ac61e41782214e83b8c7293ba06c9ab4ca +_next/static/media/aryan.bd018210.jpeg,1768708021848,9a52fccffd26cd4450758c86ef9fa9038a36bbe4408e1d2ba187dead318816d9 +_next/static/media/anika.09d741f4.jpg,1768708021863,2ecf43df467962740bfee3d4542b533d44078b5d7e3622992b494ef03eb2c50d +_next/static/media/nitika.e02fb00c.jpg,1768708021857,a78a264b1092a2d0ad221e38dc05b3c41f7570f1ef8f6268320a64f7a6376798 +_next/static/media/stock.5069275c.png,1768708021917,305debac9897de35133db980e481ed646355f3af8c84aa111e5e78e12d9edee4 +_next/static/media/aamogh.d983fd4b.png,1768708021860,635d4527701ec0d20f20b07553f822973404318cef573172178ff177f157fb09 +_next/static/media/anushka.94e3b405.jpg,1768708021858,a00e0c86a798d053352b2f75c39da92557743a9addd0206f1de7c68d4721c565 +_next/static/media/squad.04ba39fe.jpg,1768708021891,4f80fded3e892e7066dce70fd331dfbbc05b9f35fb478dcf16025b5670895a76 +_next/static/media/diya.3fcfa32b.jpeg,1768708021862,54f1ecd202837c6bf2baa160b495e7a6c994a9d953e8ab1f24e59e76d3067452 +_next/static/media/aditi.6e5dd091.jpg,1768708021851,bba26c574f86bf90a44f6558f369a08d91aeb4c5709da7f523f1370401989750 +_next/static/media/smera.a263dea9.png,1768708021866,43a3f97edab937f3407546d41fd85dc254e964d2ab680d752a666e0e3d6bcc1f +_next/static/media/trading.472a7ca9.png,1768708021925,187df50fb7e40b3e7c7d1a0c9b678b588a11a90c6415265cb326f284a0a75764 +_next/static/media/slide9.1a72b7f1.jpg,1768708021933,2c3ee5fa094edfc8ff10d8f209a3bd8909d37f8e95827e40e47bf35a1f38e95a +_next/static/media/slide6.51ff6d7a.jpg,1768708022250,b416386125adb61d55800836777af4e7c90eb8c557901b8411fb2305cd10545e diff --git a/.firebase/hosting.c2l0ZXNccG9ydGFsXHB1YmxpYw.cache b/.firebase/hosting.c2l0ZXNccG9ydGFsXHB1YmxpYw.cache new file mode 100644 index 0000000..a4d811e --- /dev/null +++ b/.firebase/hosting.c2l0ZXNccG9ydGFsXHB1YmxpYw.cache @@ -0,0 +1,25 @@ +images/logos/trading.png,1767556163597,187df50fb7e40b3e7c7d1a0c9b678b588a11a90c6415265cb326f284a0a75764 +images/logos/storm.png,1767556163185,49090aaf8b26eac91881de46e7c90d9eda8228af55f40b1a46f1bc0f969f2966 +images/logos/stock.png,1767556163164,305debac9897de35133db980e481ed646355f3af8c84aa111e5e78e12d9edee4 +images/logos/shepcenter.jpeg,1767556163025,a57a0bf31aabaab7285fcfdb7637bf513c40bc29ec5e82fd79468d741d37f127 +images/logos/Mentra.png,1767556162851,69597ac2d91b8290d657c207b49b1a322c6066b020e1ae41f6127264c81eeaee +images/logos/gtaa.png,1767556163001,615cfea20fb380d6528b2a63fab185030efc89273855b1f3202f4a301b45ceba +images/logos/furnichanter.png,1767556162972,156f59d5d1dfd47a3d57212c16846f3ce0848e7268dabba3971873f685d2caf0 +images/logos/dlp4.png,1767556162950,14e1e01f0d3436e9c13f64f400c16d6fb52471bdb2dfccaf7bcd7acc1f579cc3 +images/logos/blueconduit.png,1767556162930,726e598bbc52b99c907087d2ddced839c61df4ab32bf95d5aa00cccd3bfd140a +images/logos/birdclef.png,1767556162911,463d632fcd787259c8aceb0f8ad53c89414eacd776b8b566e4fe1bcee70072da +images/logos/arc-logo-v3.png,1767556162894,7d0f48acde15ea2c9dd41653ddc8a52e2b0937df99fd0f501edec6686acb9e68 +images/dsgt/square-logo.png,1767556162820,5af85cd153427f89d01c1109e64d5110163bf32b6d199ea1817bd41048d106ed +images/dsgt/sports_icon.png,1767556162796,aee29d6323a3222c9393642dfd1a6b61b23890ffba2bbe484a3b3876c56615ef +images/dsgt/logos-20.png,1767556162775,233de3c083e7fff89c44d462e56e0a813dc878b29b0329d141ebd5dc27398896 +images/dsgt/logos-20.ico,1767556162747,f283f6e06664b8a6710adb6f120c5b6a7bfd7967b95ca2205c3376a51641f852 +images/dsgt/logo20.ico,1767556162724,11235ade4b9531c843a4c099f92e7d282ba0b3e2d70c5761b84d9ef057620871 +images/dsgt/Icon-512.png,1767556162586,54e168ed5dcdc6fdc61bbac8c99342f016c185708d8d04efe3f0b063fb691315 +images/dsgt/Icon-196.png,1767556162563,d785daa440354422616e9889f35616604710e9dd70313c6915b06fdd630b5b5a +images/dsgt/Icon-128.png,1767556162546,b7909633f01379164dde11e53e42af2d12f7ad104762e36ebb5c8e05af0707f6 +images/dsgt/heathcare_icon.png,1767556162703,b6af8ce1284fff38bca3f01dc825bd6ec20923910b4f5ac1298e1096bb0bb236 +images/dsgt/financial_icon.png,1767556162685,560a6e3ba0ab30fea54596721cf16acb68c6410aeaa42e285a7cd3c8bcae72de +images/dsgt/favicon.ico,1767556162666,9960c28b7d6ac352cf1ba11369584c8247271cb77e217561c647620d67872eea +images/dsgt/favicon-32x32.png,1767556162650,1c59159f4c69cb8812c8e0b6963252b011a634f2613024785f340890175b68e0 +images/dsgt/favicon-16x16.png,1767556162630,96e40605151630d7b4ef81deb967a1175580f4bb9c5fb332960c92af95efcc9a +images/dsgt/apple-touch-icon.png,1767556162607,d60615679e857bfba6b5e33b60dd6b92f06af5a1b51c1ecb18aac24efec1e51d diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000..195f99d --- /dev/null +++ b/.firebaserc @@ -0,0 +1,14 @@ +{ + "projects": { + "default": "dsgt-website" + }, + "targets": { + "dsgt-website": { + "hosting": { + "judging": ["dsgt-judging"], + "portal": ["dsgt-portal"], + "mainweb": ["dsgt-website"] + } + } + } +} diff --git a/.github/workflows/cloudrun-deploy.yml b/.github/workflows/cloudrun-deploy.yml new file mode 100644 index 0000000..9286e70 --- /dev/null +++ b/.github/workflows/cloudrun-deploy.yml @@ -0,0 +1,72 @@ +name: Deploy Portal to Cloud Run + +on: + push: + branches: [main] + paths: + - 'sites/portal/**' + - 'packages/**' + - 'sites/portal/Dockerfile' + workflow_dispatch: + +env: + PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} + REGION: us-central1 + SERVICE_NAME: portal + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.GCP_SA_KEY }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + with: + project_id: ${{ env.PROJECT_ID }} + + - name: Configure Docker for GCR + run: gcloud auth configure-docker --quiet + + - name: Build Docker image + run: | + docker build \ + -t gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE_NAME }}:${{ github.sha }} \ + -t gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE_NAME }}:latest \ + -f sites/portal/Dockerfile . + + - name: Push to Container Registry + run: | + docker push gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE_NAME }}:${{ github.sha }} + docker push gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE_NAME }}:latest + + - name: Deploy to Cloud Run + run: | + gcloud run deploy ${{ env.SERVICE_NAME }} \ + --image gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE_NAME }}:${{ github.sha }} \ + --platform managed \ + --region ${{ env.REGION }} \ + --allow-unauthenticated \ + --memory 1Gi \ + --cpu 1 \ + --min-instances 0 \ + --max-instances 10 \ + --port 8080 \ + --timeout 60s + + - name: Get Service URL + run: | + URL=$(gcloud run services describe ${{ env.SERVICE_NAME }} \ + --region ${{ env.REGION }} \ + --format 'value(status.url)') + echo "::notice::Deployed to $URL" diff --git a/build_log.txt b/build_log.txt deleted file mode 100644 index a606201..0000000 Binary files a/build_log.txt and /dev/null differ diff --git a/build_log_2.txt b/build_log_2.txt deleted file mode 100644 index 0d79148..0000000 Binary files a/build_log_2.txt and /dev/null differ diff --git a/deploy-cloudrun.bat b/deploy-cloudrun.bat new file mode 100644 index 0000000..9ffbc1f --- /dev/null +++ b/deploy-cloudrun.bat @@ -0,0 +1,105 @@ +@echo off +REM ========================================= +REM Deploy Portal to Google Cloud Run +REM ========================================= +setlocal enabledelayedexpansion + +REM Configuration +if "%GCP_PROJECT_ID%"=="" ( + set PROJECT_ID=dsgt-website +) else ( + set PROJECT_ID=%GCP_PROJECT_ID% +) + +if "%GCP_REGION%"=="" ( + set REGION=us-central1 +) else ( + set REGION=%GCP_REGION% +) + +set SERVICE_NAME=portal +set IMAGE_NAME=gcr.io/%PROJECT_ID%/%SERVICE_NAME% + +echo ============================================ +echo Deploying Portal to Cloud Run +echo ============================================ +echo Project: %PROJECT_ID% +echo Region: %REGION% +echo Service: %SERVICE_NAME% +echo Image: %IMAGE_NAME% +echo ============================================ + +REM Check if gcloud is installed +where gcloud >nul 2>nul +if %ERRORLEVEL% neq 0 ( + echo ERROR: gcloud CLI is not installed + echo Install from: https://cloud.google.com/sdk/docs/install + exit /b 1 +) + +REM Check if Docker is installed +where docker >nul 2>nul +if %ERRORLEVEL% neq 0 ( + echo ERROR: Docker is not installed + echo Install from: https://docs.docker.com/get-docker/ + exit /b 1 +) + +REM Authenticate Docker with GCR +echo. +echo Configuring Docker for GCR... +call gcloud auth configure-docker --quiet + +REM Build the Docker image +echo. +echo Building Docker image... +docker build -t %IMAGE_NAME% -f sites\portal\Dockerfile . +if %ERRORLEVEL% neq 0 ( + echo ERROR: Docker build failed + exit /b 1 +) + +REM Push to Container Registry +echo. +echo Pushing image to Container Registry... +docker push %IMAGE_NAME% +if %ERRORLEVEL% neq 0 ( + echo ERROR: Docker push failed + exit /b 1 +) + +REM Deploy to Cloud Run +echo. +echo Deploying to Cloud Run... +call gcloud run deploy %SERVICE_NAME% ^ + --image %IMAGE_NAME% ^ + --platform managed ^ + --region %REGION% ^ + --allow-unauthenticated ^ + --memory 1Gi ^ + --cpu 1 ^ + --min-instances 0 ^ + --max-instances 10 ^ + --port 8080 ^ + --timeout 60s ^ + --set-env-vars "NODE_ENV=production" + +if %ERRORLEVEL% neq 0 ( + echo ERROR: Cloud Run deployment failed + exit /b 1 +) + +REM Get the service URL +echo. +echo ============================================ +echo Deployment complete! +echo ============================================ +for /f "tokens=*" %%i in ('gcloud run services describe %SERVICE_NAME% --region %REGION% --format "value(status.url)"') do set SERVICE_URL=%%i +echo Service URL: %SERVICE_URL% +echo. +echo Next steps: +echo 1. Set environment variables in Cloud Run console or via Secret Manager +echo 2. Run: firebase deploy --only hosting:dsgt-portal +echo to update hosting rewrites to Cloud Run + +endlocal diff --git a/deploy-cloudrun.sh b/deploy-cloudrun.sh new file mode 100644 index 0000000..264b783 --- /dev/null +++ b/deploy-cloudrun.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# ========================================= +# Deploy Portal to Google Cloud Run +# ========================================= +set -e + +# Configuration +PROJECT_ID="${GCP_PROJECT_ID:-dsgt-website}" +REGION="${GCP_REGION:-us-central1}" +SERVICE_NAME="portal" +IMAGE_NAME="gcr.io/$PROJECT_ID/$SERVICE_NAME" + +echo "============================================" +echo "Deploying Portal to Cloud Run" +echo "============================================" +echo "Project: $PROJECT_ID" +echo "Region: $REGION" +echo "Service: $SERVICE_NAME" +echo "Image: $IMAGE_NAME" +echo "============================================" + +# Check if gcloud is installed +if ! command -v gcloud &> /dev/null; then + echo "ERROR: gcloud CLI is not installed" + echo "Install from: https://cloud.google.com/sdk/docs/install" + exit 1 +fi + +# Check if Docker is installed +if ! command -v docker &> /dev/null; then + echo "ERROR: Docker is not installed" + echo "Install from: https://docs.docker.com/get-docker/" + exit 1 +fi + +# Authenticate Docker with GCR +echo "" +echo "Configuring Docker for GCR..." +gcloud auth configure-docker --quiet + +# Build the Docker image +echo "" +echo "Building Docker image..." +docker build -t $IMAGE_NAME -f sites/portal/Dockerfile . + +# Push to Container Registry +echo "" +echo "Pushing image to Container Registry..." +docker push $IMAGE_NAME + +# Deploy to Cloud Run +echo "" +echo "Deploying to Cloud Run..." +gcloud run deploy $SERVICE_NAME \ + --image $IMAGE_NAME \ + --platform managed \ + --region $REGION \ + --allow-unauthenticated \ + --memory 1Gi \ + --cpu 1 \ + --min-instances 0 \ + --max-instances 10 \ + --port 8080 \ + --timeout 60s \ + --set-env-vars "NODE_ENV=production" + +# Get the service URL +echo "" +echo "============================================" +echo "Deployment complete!" +echo "============================================" +SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --region $REGION --format 'value(status.url)') +echo "Service URL: $SERVICE_URL" +echo "" +echo "Next steps:" +echo "1. Set environment variables in Cloud Run console or via Secret Manager" +echo "2. Run: firebase deploy --only hosting:dsgt-portal" +echo " to update hosting rewrites to Cloud Run" diff --git a/docker_build_log.txt b/docker_build_log.txt new file mode 100644 index 0000000..3006bcc --- /dev/null +++ b/docker_build_log.txt @@ -0,0 +1,255 @@ +docker : #0 building with "desktop-linux" instance using docker driver +At line:1 char:1 ++ docker build --no-cache -t portal-cloudrun-test -f sites\portal\Docke ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: (#0 building wit...g docker driver:Strin + g) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + + +#1 [internal] load build definition from Dockerfile +#1 transferring dockerfile: 2.44kB done +#1 DONE 0.0s + +#2 [auth] library/node:pull token for registry-1.docker.io +#2 DONE 0.0s + +#3 [internal] load metadata for docker.io/library/node:20-alpine +#3 DONE 1.0s + +#4 [internal] load .dockerignore +#4 transferring context: 1.17kB done +#4 DONE 0.0s + +#5 [base 1/3] FROM docker.io/library/node:20-alpine@sha256:3960ed74dfe320a67bf8da9555 +b6bade25ebda2b22b6081d2f60fd7d5d430e9c +#5 resolve docker.io/library/node:20-alpine@sha256:3960ed74dfe320a67bf8da9555b6bade25 +ebda2b22b6081d2f60fd7d5d430e9c 0.0s done +#5 CACHED + +#6 [runner 2/8] WORKDIR /app +#6 CACHED + +#7 [internal] load build context +#7 transferring context: 10.76kB 0.1s done +#7 DONE 0.1s + +#8 [base 2/3] RUN apk add --no-cache libc6-compat +#8 ... + +#9 [runner 3/8] RUN addgroup --system --gid 1001 nodejs +#9 DONE 0.4s + +#10 [runner 4/8] RUN adduser --system --uid 1001 nextjs +#10 DONE 0.5s + +#8 [base 2/3] RUN apk add --no-cache libc6-compat +#8 1.138 (1/3) Installing musl-obstack (1.2.3-r2) +#8 1.160 (2/3) Installing libucontext (1.3.3-r0) +#8 1.180 (3/3) Installing gcompat (1.1.0-r4) +#8 1.204 OK: 11.0 MiB in 21 packages +#8 DONE 1.3s + +#11 [base 3/3] RUN corepack enable && corepack prepare pnpm@latest --activate +#11 0.921 Preparing pnpm@latest for immediate activation... +#11 DONE 2.8s + +#12 [builder 1/14] WORKDIR /app +#12 DONE 0.0s + +#13 [builder 2/14] COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +#13 DONE 0.1s + +#14 [builder 3/14] COPY packages/api/package.json ./packages/api/ +#14 DONE 0.0s + +#15 [builder 4/14] COPY packages/auth/package.json ./packages/auth/ +#15 DONE 0.0s + +#16 [builder 5/14] COPY packages/db/package.json ./packages/db/ +#16 DONE 0.0s + +#17 [builder 6/14] COPY packages/ui/package.json ./packages/ui/ +#17 DONE 0.0s + +#18 [builder 7/14] COPY sites/portal/package.json ./sites/portal/ +#18 DONE 0.0s + +#19 [builder 8/14] RUN echo "shamefully-hoist=true" > .npmrc +#19 DONE 0.3s + +#20 [builder 9/14] RUN pnpm install --frozen-lockfile +#20 0.518 ! Corepack is about to download +https://registry.npmjs.org/pnpm/-/pnpm-10.26.1.tgz +#20 3.439 Scope: all 6 workspace projects +#20 3.607 Lockfile is up to date, resolution step is skipped +#20 3.984 Progress: resolved 1, reused 0, downloaded 0, added 0 +#20 4.326 Packages: +645 +#20 4.326 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +#20 4.989 Progress: resolved 645, reused 0, downloaded 0, added 0 +#20 6.017 Progress: resolved 645, reused 0, downloaded 13, added 1 +#20 7.023 Progress: resolved 645, reused 0, downloaded 64, added 38 +#20 8.072 Progress: resolved 645, reused 0, downloaded 103, added 95 +#20 8.301 +#20 8.301 Γò¡ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ +ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓò« +#20 8.301 Γöé Γöé +#20 8.301 Γöé Update available! 10.26.1 ΓåÆ 10.28.1. Γöé +#20 8.301 Γöé Changelog: https://pnpm.io/v/10.28.1 Γöé +#20 8.301 Γöé To update, run: corepack use pnpm@10.28.1 Γöé +#20 8.301 Γöé Γöé +#20 8.301 Γò░ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ +ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓò» +#20 8.301 +#20 9.137 Progress: resolved 645, reused 0, downloaded 156, added 155 +#20 10.14 Progress: resolved 645, reused 0, downloaded 223, added 223 +#20 11.18 Progress: resolved 645, reused 0, downloaded 275, added 269 +#20 12.20 Progress: resolved 645, reused 0, downloaded 368, added 367 +#20 13.20 Progress: resolved 645, reused 0, downloaded 433, added 421 +#20 14.23 Progress: resolved 645, reused 0, downloaded 487, added 477 +#20 15.24 Progress: resolved 645, reused 0, downloaded 556, added 515 +#20 16.26 Progress: resolved 645, reused 0, downloaded 623, added 561 +#20 17.27 Progress: resolved 645, reused 0, downloaded 639, added 639 +#20 18.27 Progress: resolved 645, reused 0, downloaded 640, added 640 +#20 20.25 Progress: resolved 645, reused 0, downloaded 641, added 640 +#20 21.25 Progress: resolved 645, reused 0, downloaded 642, added 642 +#20 23.21 Progress: resolved 645, reused 0, downloaded 643, added 642 +#20 23.50 Progress: resolved 645, reused 0, downloaded 645, added 645, done +#20 24.55 +#20 24.55 dependencies: +#20 24.55 + @tanstack/react-router 1.141.2 +#20 24.55 + autoprefixer 10.4.22 +#20 24.55 + chart.js 4.5.1 +#20 24.55 + minimatch 10.1.1 +#20 24.55 + postcss 8.5.6 +#20 24.55 +#20 24.55 devDependencies: +#20 24.55 + @query/eslint-config 0.0.0 <- tooling/eslint +#20 24.55 + @query/prettier-config 0.0.0 <- tooling/prettier +#20 24.55 + @query/tailwind-config 0.0.0 <- tooling/tailwind +#20 24.55 + @query/tsconfig 0.0.0 <- tooling/typescript +#20 24.55 + @tailwindcss/postcss 4.1.18 +#20 24.55 + @tanstack/react-query 5.90.12 +#20 24.55 + @trpc/client 11.7.2 +#20 24.55 + @trpc/react-query 11.7.2 +#20 24.55 + @trpc/server 11.8.0 +#20 24.55 + @turbo/gen 2.6.3 +#20 24.55 + prettier 3.7.4 +#20 24.55 + tsx 4.21.0 +#20 24.55 + turbo 2.6.3 +#20 24.55 + typescript 5.9.3 +#20 24.55 + zod 3.25.76 +#20 24.55 +#20 24.91 Γò¡ Warning ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ +ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓ +öÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓò« +#20 24.91 Γöé + Γöé +#20 24.91 Γöé Ignored build scripts: @parcel/watcher@2.5.1, core-js-pure@3.47.0, + Γöé +#20 24.91 Γöé esbuild@0.18.20, esbuild@0.19.12, esbuild@0.27.2, sharp@0.34.5. + Γöé +#20 24.91 Γöé Run "pnpm approve-builds" to pick which dependencies should be +allowed Γöé +#20 24.91 Γöé to run scripts. + Γöé +#20 24.91 Γöé + Γöé +#20 24.91 Γò░ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ +ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓ +öÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓò» +#20 25.00 Done in 22.4s using pnpm v10.26.1 +#20 DONE 26.1s + +#21 [builder 10/14] COPY packages ./packages +#21 DONE 0.6s + +#22 [builder 11/14] COPY sites/portal ./sites/portal +#22 DONE 0.1s + +#23 [builder 12/14] COPY tooling ./tooling +#23 DONE 0.1s + +#24 [builder 13/14] WORKDIR /app/sites/portal +#24 DONE 0.0s + +#25 [builder 14/14] RUN pnpm run build +#25 0.866 +#25 0.866 > portal@1.0.0 build /app/sites/portal +#25 0.866 > next build +#25 0.866 +#25 1.998 Γû▓ Next.js 15.5.9 +#25 1.999 +#25 2.046 Creating an optimized production build ... +#25 20.38 Γ£ô Compiled successfully in 17.8s +#25 20.39 Linting and checking validity of types ... +#25 30.75 Collecting page data ... +#25 31.85 DATABASE_URL not set - database operations will fail +#25 31.85 Auth adapter: No database connection, using JWT sessions +#25 33.52 Generating static pages (0/2) ... +#25 34.07 Γ£ô Generating static pages (2/2) +#25 34.73 Finalizing page optimization ... +#25 34.73 Collecting build traces ... +#25 72.02 +#25 72.02 Route (app) Size First Load JS +#25 72.02 Γöî ╞Æ / 2.68 kB 133 kB +#25 72.02 Γö£ ╞Æ /_not-found 126 B 102 kB +#25 72.02 Γö£ ╞Æ /admin 12.4 kB 143 kB +#25 72.02 Γö£ ╞Æ /admin-judging 2.7 kB 133 kB +#25 72.02 Γö£ ╞Æ /api/auth/[...nextauth] 126 B 102 kB +#25 72.02 Γö£ ╞Æ /club 58.3 kB 188 kB +#25 72.02 Γö£ ╞Æ /dashboard 7.59 kB 141 kB +#25 72.02 Γöö ╞Æ /judge 2.61 kB 133 kB +#25 72.02 + First Load JS shared by all 102 kB +#25 72.02 Γö£ chunks/129-c7294427343a534d.js 46 kB +#25 72.02 Γö£ chunks/46c30521-588d767ddb3bc0c5.js 54.2 kB +#25 72.02 Γöö other shared chunks (total) 1.92 kB +#25 72.02 +#25 72.02 Route (pages) Size First Load JS +#25 72.02 ΓöÇ ╞Æ /api/trpc/[trpc] 0 B 82.6 kB +#25 72.02 + First Load JS shared by all 82.6 kB +#25 72.02 Γö£ chunks/framework-1c02a2e60068b586.js 44.8 kB +#25 72.02 Γö£ chunks/main-805d7d6950565494.js 35.9 kB +#25 72.02 Γöö other shared chunks (total) 1.89 kB +#25 72.02 +#25 72.02 ╞Æ (Dynamic) server-rendered on demand +#25 72.02 +#25 DONE 72.2s + +#26 [runner 5/8] COPY --from=builder --chown=nextjs:nodejs +/app/sites/portal/.next/standalone ./ +#26 DONE 0.5s + +#27 [runner 6/8] COPY --from=builder --chown=nextjs:nodejs +/app/sites/portal/.next/static ./sites/portal/.next/static +#27 DONE 0.1s + +#28 [runner 7/8] COPY --from=builder --chown=nextjs:nodejs /app/sites/portal/public +./sites/portal/public +#28 DONE 0.1s + +#29 [runner 8/8] COPY --from=builder --chown=nextjs:nodejs /app/node_modules +./node_modules +#29 DONE 5.6s + +#30 exporting to image +#30 exporting layers +#30 exporting layers 38.4s done +#30 exporting manifest +sha256:433c993b850f9ef6436eac5516bfd741a1fb03e9f93cc6de050b900ba320e22e 0.0s done +#30 exporting config +sha256:bb12a7b126c0aeadbc1a8ea0e57bcd397e592d2cf62d7fa6ecba895503fd9a71 0.0s done +#30 exporting attestation manifest +sha256:dd3d9ccf3de234845f7401950055f046e1e4fb9b1b9182cc9207bbc373012dc6 0.1s done +#30 exporting manifest list +sha256:abfba44ba59b9ff8de8142407fdfc28d3501820c729a2c2b5b820cffebc70169 +#30 exporting manifest list +sha256:abfba44ba59b9ff8de8142407fdfc28d3501820c729a2c2b5b820cffebc70169 0.0s done +#30 naming to docker.io/library/portal-cloudrun-test:latest done +#30 unpacking to docker.io/library/portal-cloudrun-test:latest +#30 unpacking to docker.io/library/portal-cloudrun-test:latest 14.3s done +#30 DONE 52.9s + +View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/5gq0 +ri535tmli6x7bv2frf8hj diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..f49fbad --- /dev/null +++ b/firebase.json @@ -0,0 +1,24 @@ +{ + "hosting": [ + { + "site": "dsgt-portal", + "public": "sites/portal/public", + "cleanUrls": true, + "rewrites": [ + { + "source": "**", + "run": { + "serviceId": "portal", + "region": "us-central1" + } + } + ] + }, + { + "site": "dsgt-website", + "public": "sites/mainweb/out", + "cleanUrls": true, + "trailingSlash": true + } + ] +} \ No newline at end of file diff --git a/packages/api/package.json b/packages/api/package.json index 0650bb3..a04f701 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -27,6 +27,7 @@ "@trpc/next": "^11.7.2", "@trpc/react-query": "^11.7.2", "@trpc/server": "^11.7.2", + "drizzle-orm": "^0.36.4", "minimatch": "^10.1.1", "sanitize-html": "^2.17.0", "superjson": "^2.2.6", diff --git a/packages/api/src/context.ts b/packages/api/src/context.ts index d2caf55..354f635 100644 --- a/packages/api/src/context.ts +++ b/packages/api/src/context.ts @@ -4,9 +4,23 @@ import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch"; import { cache } from "./middleware/cache"; export async function createContext( - opts?: FetchCreateContextFnOptions & { clientIp?: string } + opts?: FetchCreateContextFnOptions & { clientIp?: string; req?: Request } ) { - const session = await auth(); + let session = null; + + // Extract request from opts if available (priority to explicit req) + const req = opts?.req || opts?.req; + + // Only attempt auth if database is available + if (db) { + try { + // Pass the request to auth() if available, which helps in some Next.js environments + // casting simplified as usually auth() picks up headers context automatically in Server Components/Actions + session = await auth(); + } catch (error) { + console.warn("Failed to fetch auth session:", error); + } + } return { db, @@ -14,6 +28,7 @@ export async function createContext( userId: session?.user?.id, cache, clientIp: opts?.clientIp || 'unknown', + req }; } diff --git a/packages/api/src/middleware/http-security.ts b/packages/api/src/middleware/http-security.ts index 6e0f42c..60bbe70 100644 --- a/packages/api/src/middleware/http-security.ts +++ b/packages/api/src/middleware/http-security.ts @@ -163,7 +163,7 @@ function simpleHash(str: string): string { /** * Apply all headers to a Response object */ -export function applySecurityHeaders( +export async function applySecurityHeaders( response: Response, options?: { cacheable?: boolean; @@ -175,7 +175,7 @@ export function applySecurityHeaders( retryAfter?: number; }; } -): Response { +): Promise { const headers = new Headers(response.headers); // Apply security headers @@ -208,7 +208,10 @@ export function applySecurityHeaders( }); } - return new Response(response.body, { + // Clone and read the body to avoid "body already consumed" errors + const body = await response.clone().arrayBuffer(); + + return new Response(body, { status: response.status, statusText: response.statusText, headers, diff --git a/packages/api/src/routers/admin.ts b/packages/api/src/routers/admin.ts index 7869cab..ea590db 100644 --- a/packages/api/src/routers/admin.ts +++ b/packages/api/src/routers/admin.ts @@ -6,7 +6,7 @@ import { eq, and } from "drizzle-orm"; import { CacheKeys } from "../middleware/cache"; const isAdmin = protectedProcedure.use(async ({ ctx, next }) => { - const admin = await ctx.db.query.admins.findFirst({ + const admin = await ctx.db!.query.admins.findFirst({ where: and( eq(admins.userId, ctx.userId!), eq(admins.isActive, true) @@ -44,7 +44,7 @@ export const adminRouter = createTRPCRouter({ }>(cacheKey); if (cached) return cached; - const admin = await ctx.db.query.admins.findFirst({ + const admin = await ctx.db!.query.admins.findFirst({ where: and( eq(admins.userId, ctx.userId!), eq(admins.isActive, true) @@ -64,7 +64,7 @@ export const adminRouter = createTRPCRouter({ }), list: isAdmin.query(async ({ ctx }) => { - const allAdmins = await ctx.db.query.admins.findMany({ + const allAdmins = await ctx.db!.query.admins.findMany({ with: { user: { columns: { @@ -91,7 +91,7 @@ export const adminRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - const user = await ctx.db.query.users.findFirst({ + const user = await ctx.db!.query.users.findFirst({ where: eq(users.id, input.userId), }); @@ -102,7 +102,7 @@ export const adminRouter = createTRPCRouter({ }); } - const existingAdmin = await ctx.db.query.admins.findFirst({ + const existingAdmin = await ctx.db!.query.admins.findFirst({ where: eq(admins.userId, input.userId), }); @@ -113,7 +113,7 @@ export const adminRouter = createTRPCRouter({ }); } - const result = await ctx.db + const result = await ctx.db! .insert(admins) .values({ userId: input.userId, @@ -144,7 +144,7 @@ export const adminRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - const targetAdmin = await ctx.db.query.admins.findFirst({ + const targetAdmin = await ctx.db!.query.admins.findFirst({ where: eq(admins.id, input.adminId), }); @@ -161,7 +161,7 @@ export const adminRouter = createTRPCRouter({ }); } - const result = await ctx.db + const result = await ctx.db! .update(admins) .set({ role: input.role, @@ -187,7 +187,7 @@ export const adminRouter = createTRPCRouter({ remove: isSuperAdmin .input(z.object({ adminId: z.string().uuid() })) .mutation(async ({ ctx, input }) => { - const targetAdmin = await ctx.db.query.admins.findFirst({ + const targetAdmin = await ctx.db!.query.admins.findFirst({ where: eq(admins.id, input.adminId), }); @@ -205,7 +205,7 @@ export const adminRouter = createTRPCRouter({ }); } - await ctx.db.delete(admins).where(eq(admins.id, input.adminId)); + await ctx.db!.delete(admins).where(eq(admins.id, input.adminId)); return { success: true }; }), diff --git a/packages/api/src/routers/events.ts b/packages/api/src/routers/events.ts index caa073d..61766ff 100644 --- a/packages/api/src/routers/events.ts +++ b/packages/api/src/routers/events.ts @@ -7,7 +7,7 @@ import { randomUUID } from "crypto"; // Admin middleware const isAdmin = protectedProcedure.use(async ({ ctx, next }) => { - const admin = await ctx.db.query.admins.findFirst({ + const admin = await ctx.db!.query.admins.findFirst({ where: and( eq(admins.userId, ctx.userId!), eq(admins.isActive, true) @@ -39,7 +39,7 @@ export const eventRouter = createTRPCRouter({ .mutation(async ({ ctx, input }) => { const qrCode = randomUUID(); - const [newEvent] = await ctx.db + const [newEvent] = await ctx.db! .insert(events) .values({ ...input, @@ -57,7 +57,7 @@ export const eventRouter = createTRPCRouter({ .mutation(async ({ ctx, input }) => { const newQrCode = randomUUID(); - const [updatedEvent] = await ctx.db + const [updatedEvent] = await ctx.db! .update(events) .set({ qrCode: newQrCode, @@ -78,7 +78,7 @@ export const eventRouter = createTRPCRouter({ // ADMIN: List all events listAll: isAdmin.query(async ({ ctx }) => { - const allEvents = await ctx.db.query.events.findMany({ + const allEvents = await ctx.db!.query.events.findMany({ orderBy: (events, { desc }) => [desc(events.eventDate)], with: { createdBy: { @@ -98,7 +98,7 @@ export const eventRouter = createTRPCRouter({ getById: isAdmin .input(z.object({ id: z.string().uuid() })) .query(async ({ ctx, input }) => { - const event = await ctx.db.query.events.findFirst({ + const event = await ctx.db!.query.events.findFirst({ where: eq(events.id, input.id), with: { checkIns: { @@ -142,7 +142,7 @@ export const eventRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - const [updatedEvent] = await ctx.db + const [updatedEvent] = await ctx.db! .update(events) .set({ checkInEnabled: input.enabled, @@ -158,7 +158,7 @@ export const eventRouter = createTRPCRouter({ delete: isAdmin .input(z.object({ eventId: z.string().uuid() })) .mutation(async ({ ctx, input }) => { - await ctx.db.delete(events).where(eq(events.id, input.eventId)); + await ctx.db!.delete(events).where(eq(events.id, input.eventId)); return { success: true }; }), @@ -167,7 +167,7 @@ export const eventRouter = createTRPCRouter({ .input(z.object({ qrCode: z.string().uuid() })) .mutation(async ({ ctx, input }) => { // Single transaction with all checks and inserts - return await ctx.db.transaction(async (tx) => { + return await ctx.db!.transaction(async (tx) => { // 1. Get event and validate in one query const event = await tx.query.events.findFirst({ where: eq(events.qrCode, input.qrCode), @@ -247,7 +247,7 @@ export const eventRouter = createTRPCRouter({ // MEMBER: Get my attended events myEvents: protectedProcedure.query(async ({ ctx }) => { - const checkIns = await ctx.db.query.eventCheckIns.findMany({ + const checkIns = await ctx.db!.query.eventCheckIns.findMany({ where: eq(eventCheckIns.userId, ctx.userId!), with: { event: { @@ -269,7 +269,7 @@ export const eventRouter = createTRPCRouter({ // MEMBER: Get my stats (OPTIMIZED - Single Aggregation Query) myStats: protectedProcedure.query(async ({ ctx }) => { - const result = await ctx.db + const result = await ctx.db! .select({ totalEvents: sql`count(*)::int`, }) diff --git a/packages/api/src/routers/hackathon.ts b/packages/api/src/routers/hackathon.ts index 69e30ad..95e8f65 100644 --- a/packages/api/src/routers/hackathon.ts +++ b/packages/api/src/routers/hackathon.ts @@ -14,7 +14,7 @@ import { CacheKeys } from "../middleware/cache"; // Admin check middleware for hackathon management const isAdmin = protectedProcedure.use(async ({ ctx, next }) => { - const admin = await ctx.db.query.admins.findFirst({ + const admin = await ctx.db!.query.admins.findFirst({ where: and( eq(admins.userId, ctx.userId!), eq(admins.isActive, true) @@ -51,7 +51,7 @@ export const hackathonRouter = createTRPCRouter({ const now = new Date(); - const allHackathons = await ctx.db.query.hackathons.findMany({ + const allHackathons = await ctx.db!.query.hackathons.findMany({ where: and( eq(hackathons.isPublic, true), input.status ? eq(hackathons.status, input.status) : undefined, @@ -76,7 +76,7 @@ export const hackathonRouter = createTRPCRouter({ const cached = ctx.cache.get(cacheKey); if (cached) return cached; - const hackathon = await ctx.db.query.hackathons.findFirst({ + const hackathon = await ctx.db!.query.hackathons.findFirst({ where: eq(hackathons.id, input.id), }); @@ -120,7 +120,7 @@ export const hackathonRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - const [newHackathon] = await ctx.db + const [newHackathon] = await ctx.db! .insert(hackathons) .values({ ...input, @@ -163,7 +163,7 @@ export const hackathonRouter = createTRPCRouter({ const { id, ...updateData } = input; // Verify hackathon exists - const existing = await ctx.db.query.hackathons.findFirst({ + const existing = await ctx.db!.query.hackathons.findFirst({ where: eq(hackathons.id, id), }); @@ -174,7 +174,7 @@ export const hackathonRouter = createTRPCRouter({ }); } - const [updatedHackathon] = await ctx.db + const [updatedHackathon] = await ctx.db! .update(hackathons) .set({ ...updateData, @@ -202,7 +202,7 @@ export const hackathonRouter = createTRPCRouter({ ) .mutation(async ({ ctx, input }) => { // Use transaction to prevent race conditions - return await ctx.db.transaction(async (tx) => { + return await ctx.db!.transaction(async (tx) => { const hackathon = await tx.query.hackathons.findFirst({ where: eq(hackathons.id, input.hackathonId), }); @@ -272,7 +272,7 @@ export const hackathonRouter = createTRPCRouter({ }), myRegistrations: protectedProcedure.query(async ({ ctx }) => { - const registrations = await ctx.db.query.hackathonParticipants.findMany({ + const registrations = await ctx.db!.query.hackathonParticipants.findMany({ where: eq(hackathonParticipants.userId, ctx.userId!), with: { hackathon: true, @@ -287,7 +287,7 @@ export const hackathonRouter = createTRPCRouter({ participants: publicProcedure .input(z.object({ hackathonId: z.string().uuid("Invalid hackathon ID") })) .query(async ({ ctx, input }) => { - const participants = await ctx.db.query.hackathonParticipants.findMany({ + const participants = await ctx.db!.query.hackathonParticipants.findMany({ where: eq(hackathonParticipants.hackathonId, input.hackathonId), with: { user: { @@ -314,7 +314,7 @@ export const hackathonRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - return await ctx.db.transaction(async (tx) => { + return await ctx.db!.transaction(async (tx) => { const participant = await tx.query.hackathonParticipants.findFirst({ where: and( eq(hackathonParticipants.hackathonId, input.hackathonId), @@ -367,7 +367,7 @@ export const hackathonRouter = createTRPCRouter({ projects: publicProcedure .input(z.object({ hackathonId: z.string().uuid("Invalid hackathon ID") })) .query(async ({ ctx, input }) => { - const projects = await ctx.db.query.hackathonProjects.findMany({ + const projects = await ctx.db!.query.hackathonProjects.findMany({ where: eq(hackathonProjects.hackathonId, input.hackathonId), with: { team: { diff --git a/packages/api/src/routers/hello-procedures.ts b/packages/api/src/routers/hello-procedures.ts new file mode 100644 index 0000000..1613664 --- /dev/null +++ b/packages/api/src/routers/hello-procedures.ts @@ -0,0 +1,39 @@ +import { z } from "zod"; +import { publicProcedure, protectedProcedure } from "../trpc"; + +export const sayHello = publicProcedure.mutation(() => { + return { + message: "You should sign in", + timestamp: new Date().toISOString(), + }; +}); + +export const greetPublic = publicProcedure + .input(z.object({ name: z.string().min(1) })) + .query(({ input }) => { + return { + message: `Hello ${input.name}! Welcome to the app.`, + }; + }); + +export const sayHelloAuth = protectedProcedure.mutation(({ ctx }) => { + const user = ctx.session.user; + + return { + message: `Hello ${user.name ?? user.email}!`, + user: { + id: user.id, + email: user.email, + name: user.name, + }, + }; +}); + +export const greet = protectedProcedure + .input(z.object({ name: z.string().min(1) })) + .mutation(({ ctx, input }) => { + return { + message: `Hello ${input.name}, from ${ctx.session.user.email}`, + userId: ctx.userId, + }; + }); diff --git a/packages/api/src/routers/hello.ts b/packages/api/src/routers/hello.ts index 087311a..96228d9 100644 --- a/packages/api/src/routers/hello.ts +++ b/packages/api/src/routers/hello.ts @@ -1,45 +1,9 @@ -import { z } from "zod"; -import { createTRPCRouter, publicProcedure, protectedProcedure } from "../trpc"; +import { createTRPCRouter } from "../trpc"; +import { sayHello, greetPublic, sayHelloAuth, greet } from "./hello-procedures"; export const helloRouter = createTRPCRouter({ - // Public endpoint - sayHello: publicProcedure.mutation(() => { - return { - message: "You should sign in", - timestamp: new Date().toISOString(), - }; - }), - - // Public endpoint with input validation - greetPublic: publicProcedure - .input(z.object({ name: z.string().min(1) })) - .query(({ input }) => { - return { - message: `Hello ${input.name}! Welcome to the app.`, - }; - }), - - // Requires authentication - sayHelloAuth: protectedProcedure.mutation(({ ctx }) => { - const user = ctx.session.user; - - return { - message: `Hello ${user.name ?? user.email}!`, - user: { - id: user.id, - email: user.email, - name: user.name, - }, - }; - }), - - // Authenticated endpoint with input - greet: protectedProcedure - .input(z.object({ name: z.string().min(1) })) - .mutation(({ ctx, input }) => { - return { - message: `Hello ${input.name}, from ${ctx.session.user.email}`, - userId: ctx.userId, - }; - }), + sayHello, + greetPublic, + sayHelloAuth, + greet, }); diff --git a/packages/api/src/routers/judge.ts b/packages/api/src/routers/judge.ts index 807c5dd..98213c0 100644 --- a/packages/api/src/routers/judge.ts +++ b/packages/api/src/routers/judge.ts @@ -16,7 +16,7 @@ import { CacheKeys } from "../middleware/cache"; // Middleware to check if user is a judge const isJudge = protectedProcedure.use(async ({ ctx, next }) => { - const judge = await ctx.db.query.judges.findFirst({ + const judge = await ctx.db!.query.judges.findFirst({ where: and( eq(judges.userId, ctx.userId!), eq(judges.isActive, true) @@ -35,7 +35,7 @@ const isJudge = protectedProcedure.use(async ({ ctx, next }) => { // Middleware to check if user is admin (for judge management) const isAdmin = protectedProcedure.use(async ({ ctx, next }) => { - const admin = await ctx.db.query.admins.findFirst({ + const admin = await ctx.db!.query.admins.findFirst({ where: and( eq(admins.userId, ctx.userId!), eq(admins.isActive, true) @@ -64,7 +64,7 @@ export const judgeRouter = createTRPCRouter({ }>(cacheKey); if (cached) return cached; - const judge = await ctx.db.query.judges.findFirst({ + const judge = await ctx.db!.query.judges.findFirst({ where: and( eq(judges.userId, ctx.userId!), eq(judges.isActive, true) @@ -85,7 +85,7 @@ export const judgeRouter = createTRPCRouter({ // Get hackathons assigned to current judge getMyAssignments: isJudge.query(async ({ ctx }) => { - const assignments = await ctx.db.query.judgeAssignments.findMany({ + const assignments = await ctx.db!.query.judgeAssignments.findMany({ where: eq(judgeAssignments.judgeId, ctx.judge.id), with: { hackathon: true, @@ -101,7 +101,7 @@ export const judgeRouter = createTRPCRouter({ .input(z.object({ hackathonId: z.string().uuid() })) .query(async ({ ctx, input }) => { // Get the next uncompleted item in the queue - const nextInQueue = await ctx.db.query.judgeQueue.findFirst({ + const nextInQueue = await ctx.db!.query.judgeQueue.findFirst({ where: and( eq(judgeQueue.judgeId, ctx.judge.id), eq(judgeQueue.hackathonId, input.hackathonId), @@ -118,7 +118,7 @@ export const judgeRouter = createTRPCRouter({ } // Count remaining - const remainingCount = await ctx.db + const remainingCount = await ctx.db! .select({ count: sql`count(*)` }) .from(judgeQueue) .where( @@ -141,13 +141,13 @@ export const judgeRouter = createTRPCRouter({ getProjects: isJudge .input(z.object({ hackathonId: z.string().uuid() })) .query(async ({ ctx, input }) => { - const projects = await ctx.db.query.judgingProjects.findMany({ + const projects = await ctx.db!.query.judgingProjects.findMany({ where: eq(judgingProjects.hackathonId, input.hackathonId), orderBy: [asc(judgingProjects.tableNumber)], }); // Get this judge's votes - const myVotes = await ctx.db.query.judgeVotes.findMany({ + const myVotes = await ctx.db!.query.judgeVotes.findMany({ where: eq(judgeVotes.judgeId, ctx.judge.id), }); @@ -164,7 +164,7 @@ export const judgeRouter = createTRPCRouter({ getMaps: isJudge .input(z.object({ hackathonId: z.string().uuid() })) .query(async ({ ctx, input }) => { - const maps = await ctx.db.query.hackathonMaps.findMany({ + const maps = await ctx.db!.query.hackathonMaps.findMany({ where: eq(hackathonMaps.hackathonId, input.hackathonId), orderBy: [asc(hackathonMaps.order)], }); @@ -183,7 +183,7 @@ export const judgeRouter = createTRPCRouter({ ) .mutation(async ({ ctx, input }) => { // Check if vote already exists - const existing = await ctx.db.query.judgeVotes.findFirst({ + const existing = await ctx.db!.query.judgeVotes.findFirst({ where: and( eq(judgeVotes.judgeId, ctx.judge.id), eq(judgeVotes.projectId, input.projectId) @@ -192,7 +192,7 @@ export const judgeRouter = createTRPCRouter({ if (existing) { // Update existing vote - const result = await ctx.db + const result = await ctx.db! .update(judgeVotes) .set({ score: input.score, @@ -206,7 +206,7 @@ export const judgeRouter = createTRPCRouter({ } // Create new vote - const result = await ctx.db + const result = await ctx.db! .insert(judgeVotes) .values({ judgeId: ctx.judge.id, @@ -231,7 +231,7 @@ export const judgeRouter = createTRPCRouter({ ) .mutation(async ({ ctx, input }) => { // Submit the vote - const existing = await ctx.db.query.judgeVotes.findFirst({ + const existing = await ctx.db!.query.judgeVotes.findFirst({ where: and( eq(judgeVotes.judgeId, ctx.judge.id), eq(judgeVotes.projectId, input.projectId) @@ -239,7 +239,7 @@ export const judgeRouter = createTRPCRouter({ }); if (existing) { - await ctx.db + await ctx.db! .update(judgeVotes) .set({ score: input.score, @@ -248,7 +248,7 @@ export const judgeRouter = createTRPCRouter({ }) .where(eq(judgeVotes.id, existing.id)); } else { - await ctx.db.insert(judgeVotes).values({ + await ctx.db!.insert(judgeVotes).values({ judgeId: ctx.judge.id, projectId: input.projectId, score: input.score, @@ -257,7 +257,7 @@ export const judgeRouter = createTRPCRouter({ } // Mark queue item as completed - await ctx.db + await ctx.db! .update(judgeQueue) .set({ isCompleted: true, @@ -266,7 +266,7 @@ export const judgeRouter = createTRPCRouter({ .where(eq(judgeQueue.id, input.queueId)); // Get next in queue - const queueItem = await ctx.db.query.judgeQueue.findFirst({ + const queueItem = await ctx.db!.query.judgeQueue.findFirst({ where: eq(judgeQueue.id, input.queueId), }); @@ -274,7 +274,7 @@ export const judgeRouter = createTRPCRouter({ return { done: true, nextProject: null }; } - const nextInQueue = await ctx.db.query.judgeQueue.findFirst({ + const nextInQueue = await ctx.db!.query.judgeQueue.findFirst({ where: and( eq(judgeQueue.judgeId, ctx.judge.id), eq(judgeQueue.hackathonId, queueItem.hackathonId), @@ -301,7 +301,7 @@ export const judgeRouter = createTRPCRouter({ getProgress: isJudge .input(z.object({ hackathonId: z.string().uuid() })) .query(async ({ ctx, input }) => { - const total = await ctx.db + const total = await ctx.db! .select({ count: sql`count(*)` }) .from(judgeQueue) .where( @@ -311,7 +311,7 @@ export const judgeRouter = createTRPCRouter({ ) ); - const completed = await ctx.db + const completed = await ctx.db! .select({ count: sql`count(*)` }) .from(judgeQueue) .where( @@ -338,7 +338,7 @@ export const judgeRouter = createTRPCRouter({ .input(z.object({ hackathonId: z.string().uuid() })) .query(async ({ ctx, input }) => { // Get all projects with their votes - const projects = await ctx.db.query.judgingProjects.findMany({ + const projects = await ctx.db!.query.judgingProjects.findMany({ where: eq(judgingProjects.hackathonId, input.hackathonId), with: { votes: { @@ -413,7 +413,7 @@ export const judgeRouter = createTRPCRouter({ // List all judges list: isAdmin.query(async ({ ctx }) => { - const allJudges = await ctx.db.query.judges.findMany({ + const allJudges = await ctx.db!.query.judges.findMany({ with: { user: { columns: { @@ -449,7 +449,7 @@ export const judgeRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - const user = await ctx.db.query.users.findFirst({ + const user = await ctx.db!.query.users.findFirst({ where: eq(users.id, input.userId), }); @@ -460,7 +460,7 @@ export const judgeRouter = createTRPCRouter({ }); } - const existing = await ctx.db.query.judges.findFirst({ + const existing = await ctx.db!.query.judges.findFirst({ where: eq(judges.userId, input.userId), }); @@ -471,7 +471,7 @@ export const judgeRouter = createTRPCRouter({ }); } - const result = await ctx.db + const result = await ctx.db! .insert(judges) .values({ userId: input.userId, @@ -492,7 +492,7 @@ export const judgeRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - const existing = await ctx.db.query.judgeAssignments.findFirst({ + const existing = await ctx.db!.query.judgeAssignments.findFirst({ where: and( eq(judgeAssignments.judgeId, input.judgeId), eq(judgeAssignments.hackathonId, input.hackathonId) @@ -506,7 +506,7 @@ export const judgeRouter = createTRPCRouter({ }); } - const result = await ctx.db + const result = await ctx.db! .insert(judgeAssignments) .values({ judgeId: input.judgeId, @@ -516,13 +516,13 @@ export const judgeRouter = createTRPCRouter({ .returning(); // Create queue entries for all projects - const projects = await ctx.db.query.judgingProjects.findMany({ + const projects = await ctx.db!.query.judgingProjects.findMany({ where: eq(judgingProjects.hackathonId, input.hackathonId), orderBy: [asc(judgingProjects.tableNumber)], }); if (projects.length > 0) { - await ctx.db.insert(judgeQueue).values( + await ctx.db!.insert(judgeQueue).values( projects.map((p, idx) => ({ judgeId: input.judgeId, hackathonId: input.hackathonId, @@ -549,7 +549,7 @@ export const judgeRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - const result = await ctx.db + const result = await ctx.db! .insert(judgingProjects) .values(input) .returning(); @@ -573,7 +573,7 @@ export const judgeRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - const result = await ctx.db + const result = await ctx.db! .insert(judgingProjects) .values( input.projects.map((p) => ({ @@ -597,7 +597,7 @@ export const judgeRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - const result = await ctx.db + const result = await ctx.db! .insert(hackathonMaps) .values(input) .returning(); @@ -616,7 +616,7 @@ export const judgeRouter = createTRPCRouter({ ) .mutation(async ({ ctx, input }) => { // Delete existing queue - await ctx.db + await ctx.db! .delete(judgeQueue) .where( and( @@ -626,7 +626,7 @@ export const judgeRouter = createTRPCRouter({ ); // Get all projects - let projects = await ctx.db.query.judgingProjects.findMany({ + let projects = await ctx.db!.query.judgingProjects.findMany({ where: eq(judgingProjects.hackathonId, input.hackathonId), orderBy: [asc(judgingProjects.tableNumber)], }); @@ -638,7 +638,7 @@ export const judgeRouter = createTRPCRouter({ // Create queue entries if (projects.length > 0) { - await ctx.db.insert(judgeQueue).values( + await ctx.db!.insert(judgeQueue).values( projects.map((p, idx) => ({ judgeId: input.judgeId, hackathonId: input.hackathonId, @@ -655,7 +655,7 @@ export const judgeRouter = createTRPCRouter({ remove: isAdmin .input(z.object({ judgeId: z.string().uuid() })) .mutation(async ({ ctx, input }) => { - await ctx.db.delete(judges).where(eq(judges.id, input.judgeId)); + await ctx.db!.delete(judges).where(eq(judges.id, input.judgeId)); return { success: true }; }), @@ -663,7 +663,7 @@ export const judgeRouter = createTRPCRouter({ getAllVotes: isAdmin .input(z.object({ hackathonId: z.string().uuid() })) .query(async ({ ctx, input }) => { - const projects = await ctx.db.query.judgingProjects.findMany({ + const projects = await ctx.db!.query.judgingProjects.findMany({ where: eq(judgingProjects.hackathonId, input.hackathonId), with: { votes: { diff --git a/packages/api/src/routers/member.ts b/packages/api/src/routers/member.ts index 9abc578..ebfba71 100644 --- a/packages/api/src/routers/member.ts +++ b/packages/api/src/routers/member.ts @@ -12,7 +12,7 @@ const phoneSchema = z.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number export const memberRouter = createTRPCRouter({ me: protectedProcedure.query(async ({ ctx }) => { - const member = await ctx.db.query.members.findFirst({ + const member = await ctx.db!.query.members.findFirst({ where: eq(members.userId, ctx.userId!), }); @@ -36,7 +36,7 @@ export const memberRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - const existingMember = await ctx.db.query.members.findFirst({ + const existingMember = await ctx.db!.query.members.findFirst({ where: eq(members.userId, ctx.userId!), }); @@ -51,7 +51,7 @@ export const memberRouter = createTRPCRouter({ const membershipEndDate = new Date(); membershipEndDate.setFullYear(membershipEndDate.getFullYear() + 1); - const result = await ctx.db + const result = await ctx.db! .insert(members) .values({ userId: ctx.userId!, @@ -81,7 +81,7 @@ export const memberRouter = createTRPCRouter({ }); } - await ctx.db.insert(membershipHistory).values({ + await ctx.db!.insert(membershipHistory).values({ memberId: newMember.id, action: "joined", startDate: membershipStartDate, @@ -92,7 +92,7 @@ export const memberRouter = createTRPCRouter({ }), renew: protectedProcedure.mutation(async ({ ctx }) => { - const member = await ctx.db.query.members.findFirst({ + const member = await ctx.db!.query.members.findFirst({ where: eq(members.userId, ctx.userId!), }); @@ -106,7 +106,7 @@ export const memberRouter = createTRPCRouter({ const newEndDate = new Date(member.membershipEndDate || new Date()); newEndDate.setFullYear(newEndDate.getFullYear() + 1); - const result = await ctx.db + const result = await ctx.db! .update(members) .set({ memberType: "continuous", @@ -127,7 +127,7 @@ export const memberRouter = createTRPCRouter({ }); } - await ctx.db.insert(membershipHistory).values({ + await ctx.db!.insert(membershipHistory).values({ memberId: member.id, action: "renewed", startDate: member.membershipEndDate || new Date(), @@ -154,7 +154,7 @@ export const memberRouter = createTRPCRouter({ }) ) .mutation(async ({ ctx, input }) => { - const member = await ctx.db.query.members.findFirst({ + const member = await ctx.db!.query.members.findFirst({ where: eq(members.userId, ctx.userId!), }); @@ -165,7 +165,7 @@ export const memberRouter = createTRPCRouter({ }); } - const result = await ctx.db + const result = await ctx.db! .update(members) .set({ ...input, @@ -195,7 +195,7 @@ export const memberRouter = createTRPCRouter({ }) ) .query(async ({ ctx, input }) => { - const allMembers = await ctx.db.query.members.findMany({ + const allMembers = await ctx.db!.query.members.findMany({ where: and( eq(members.isActive, true), input.memberType ? eq(members.memberType, input.memberType) : undefined @@ -231,7 +231,7 @@ export const memberRouter = createTRPCRouter({ getById: publicProcedure .input(z.object({ id: z.string().uuid() })) .query(async ({ ctx, input }) => { - const member = await ctx.db.query.members.findFirst({ + const member = await ctx.db!.query.members.findFirst({ where: eq(members.id, input.id), with: { user: { @@ -267,7 +267,7 @@ export const memberRouter = createTRPCRouter({ }), history: protectedProcedure.query(async ({ ctx }) => { - const member = await ctx.db.query.members.findFirst({ + const member = await ctx.db!.query.members.findFirst({ where: eq(members.userId, ctx.userId!), }); @@ -278,7 +278,7 @@ export const memberRouter = createTRPCRouter({ }); } - const history = await ctx.db.query.membershipHistory.findMany({ + const history = await ctx.db!.query.membershipHistory.findMany({ where: eq(membershipHistory.memberId, member.id), orderBy: (membershipHistory, { desc }) => [desc(membershipHistory.createdAt)], limit: 50, @@ -300,7 +300,7 @@ export const memberRouter = createTRPCRouter({ }>(cacheKey); if (cached) return cached; - const member = await ctx.db.query.members.findFirst({ + const member = await ctx.db!.query.members.findFirst({ where: eq(members.userId, ctx.userId!), }); diff --git a/packages/api/src/routers/user.ts b/packages/api/src/routers/user.ts index 3e2bb6a..16ea7dc 100644 --- a/packages/api/src/routers/user.ts +++ b/packages/api/src/routers/user.ts @@ -6,7 +6,7 @@ import { eq } from "drizzle-orm"; export const userRouter = createTRPCRouter({ me: protectedProcedure.query(async ({ ctx }) => { - const user = await ctx.db.query.users.findFirst({ + const user = await ctx.db!.query.users.findFirst({ where: eq(users.id, ctx.userId!), with: { profile: true, @@ -40,7 +40,7 @@ export const userRouter = createTRPCRouter({ .mutation(async ({ ctx, input }) => { // Update user name/image if provided if (input.name !== undefined || input.image !== undefined) { - await ctx.db + await ctx.db! .update(users) .set({ name: input.name, @@ -49,12 +49,12 @@ export const userRouter = createTRPCRouter({ .where(eq(users.id, ctx.userId!)); } if (input.bio !== undefined) { - const existingProfile = await ctx.db.query.userProfiles.findFirst({ + const existingProfile = await ctx.db!.query.userProfiles.findFirst({ where: eq(userProfiles.userId, ctx.userId!), }); if (existingProfile) { - await ctx.db + await ctx.db! .update(userProfiles) .set({ bio: input.bio, @@ -62,14 +62,14 @@ export const userRouter = createTRPCRouter({ }) .where(eq(userProfiles.userId, ctx.userId!)); } else { - await ctx.db.insert(userProfiles).values({ + await ctx.db!.insert(userProfiles).values({ userId: ctx.userId!, bio: input.bio, }); } } - const updatedUser = await ctx.db.query.users.findFirst({ + const updatedUser = await ctx.db!.query.users.findFirst({ where: eq(users.id, ctx.userId!), with: { profile: true, diff --git a/packages/api/src/trpc.ts b/packages/api/src/trpc.ts index 5cf63ad..2c9295d 100644 --- a/packages/api/src/trpc.ts +++ b/packages/api/src/trpc.ts @@ -20,6 +20,23 @@ const t = initTRPC.context().create({ export const createTRPCRouter = t.router; +// Middleware that ensures database is available and narrows the type +const requiresDb = t.middleware(async ({ ctx, next }) => { + if (!ctx.db) { + throw new TRPCError({ + code: "PRECONDITION_FAILED", + message: "Database unavailable", + }); + } + + return next({ + ctx: { + ...ctx, + db: ctx.db, + }, + }); +}); + export const publicProcedure = t.procedure.use(async ({ ctx, next, type }) => { // DDoS Protection - check IP-based limits first const ddosCheck = ddosProtection(ctx.clientIp); @@ -155,11 +172,13 @@ const cacheInvalidationMiddleware = t.middleware(async ({ ctx, next, type, path }); export const protectedProcedure = t.procedure + .use(requiresDb) .use(isAuthed) .use(sanitizeInputs) .use(cacheInvalidationMiddleware); export const judgeProcedure = t.procedure + .use(requiresDb) .use(async ({ ctx, next, type }) => { if (!ctx.session?.user || !ctx.userId) { throw new TRPCError({ @@ -192,6 +211,7 @@ export const judgeProcedure = t.procedure .use(cacheInvalidationMiddleware); export const adminProcedure = t.procedure + .use(requiresDb) .use(async ({ ctx, next, type }) => { if (!ctx.session?.user || !ctx.userId) { throw new TRPCError({ diff --git a/packages/auth/src/adapter.ts b/packages/auth/src/adapter.ts index f8b0a81..61d74a9 100644 --- a/packages/auth/src/adapter.ts +++ b/packages/auth/src/adapter.ts @@ -1,9 +1,26 @@ import { DrizzleAdapter } from "@auth/drizzle-adapter"; import { db, users, accounts, sessions, verificationTokens } from "@query/db"; +import type { Adapter } from "next-auth/adapters"; -export const adapter = DrizzleAdapter(db, { - usersTable: users, - accountsTable: accounts, - sessionsTable: sessions, - verificationTokensTable: verificationTokens, -}); \ No newline at end of file +// Only create adapter if database is available and properly initialized +function createAdapter(): Adapter | undefined { + // Check both that db exists and that DATABASE_URL was set + if (!db || !process.env.DATABASE_URL) { + console.warn("Auth adapter: No database connection, using JWT sessions"); + return undefined; + } + + try { + return DrizzleAdapter(db, { + usersTable: users, + accountsTable: accounts, + sessionsTable: sessions, + verificationTokensTable: verificationTokens, + }); + } catch (error) { + console.error("Auth adapter: Failed to create Drizzle adapter:", error); + return undefined; + } +} + +export const adapter: Adapter | undefined = createAdapter(); \ No newline at end of file diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts index cbe57dd..9160fcb 100644 --- a/packages/auth/src/auth.ts +++ b/packages/auth/src/auth.ts @@ -2,7 +2,13 @@ import NextAuth from "next-auth"; import { authConfig } from "./config"; import { adapter } from "./adapter"; +// If no adapter (no database), fall back to JWT sessions +const sessionConfig = adapter + ? authConfig.session + : { strategy: "jwt" as const }; + export const { handlers, auth, signIn, signOut } = NextAuth({ ...authConfig, adapter, + session: sessionConfig, }); \ No newline at end of file diff --git a/packages/auth/src/config.ts b/packages/auth/src/config.ts index 2314e09..2124b5e 100644 --- a/packages/auth/src/config.ts +++ b/packages/auth/src/config.ts @@ -2,6 +2,7 @@ import type { NextAuthConfig } from "next-auth"; import GoogleProvider from "next-auth/providers/google"; export const authConfig: NextAuthConfig = { + trustHost: true, providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, diff --git a/packages/db/package.json b/packages/db/package.json index 16098e3..a6fbb44 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -18,6 +18,7 @@ "@t3-oss/env-nextjs": "^0.13.10", "drizzle-orm": "^0.36.4", "minimatch": "^10.1.1", + "next-auth": "^5.0.0-beta.25", "pg": "^8.11.3", "postgres": "^3.4.3", "zod": "^3.25.76" diff --git a/packages/db/src/client.ts b/packages/db/src/client.ts index d7871ff..3a0b302 100644 --- a/packages/db/src/client.ts +++ b/packages/db/src/client.ts @@ -1,13 +1,22 @@ import { drizzle } from "drizzle-orm/node-postgres"; import { Pool } from "pg"; import * as schema from "./schemas"; -import * as dotenv from "dotenv"; -import path from "path"; -dotenv.config({ path: path.resolve(__dirname, "../../../.env") }); +// DATABASE_URL should be set via Next.js env loading or Firebase Functions config +const DATABASE_URL = process.env.DATABASE_URL; -const pool = new Pool({ - connectionString: process.env.DATABASE_URL, -}); +type DrizzleDB = ReturnType>; -export const db = drizzle(pool, { schema }); \ No newline at end of file +// Create database connection only if DATABASE_URL is provided +let db: DrizzleDB | null = null; + +if (DATABASE_URL) { + const pool = new Pool({ + connectionString: DATABASE_URL, + }); + db = drizzle(pool, { schema }); +} else { + console.warn("DATABASE_URL not set - database operations will fail"); +} + +export { db }; \ No newline at end of file diff --git a/packages/db/src/env.ts b/packages/db/src/env.ts index 2ee340e..b62a727 100644 --- a/packages/db/src/env.ts +++ b/packages/db/src/env.ts @@ -3,7 +3,7 @@ import { z } from "zod"; export const env = createEnv({ server: { - DATABASE_URL: z.string().url(), + DATABASE_URL: z.string().url().optional(), }, client: {}, runtimeEnv: process.env as Record, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7254211..f955e5b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,6 +101,9 @@ importers: '@trpc/server': specifier: ^11.7.2 version: 11.8.0(typescript@5.9.3) + drizzle-orm: + specifier: ^0.36.4 + version: 0.36.4(@types/pg@8.16.0)(@types/react@18.3.27)(pg@8.16.3)(postgres@3.4.7)(react@18.3.1) minimatch: specifier: ^10.1.1 version: 10.1.1 @@ -166,6 +169,9 @@ importers: minimatch: specifier: ^10.1.1 version: 10.1.1 + next-auth: + specifier: ^5.0.0-beta.25 + version: 5.0.0-beta.30(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react@18.3.1) pg: specifier: ^8.11.3 version: 8.16.3 @@ -235,82 +241,6 @@ importers: specifier: ^5.6.3 version: 5.9.3 - sites/judging: - dependencies: - '@query/api': - specifier: workspace:* - version: link:../../packages/api - '@query/auth': - specifier: workspace:* - version: link:../../packages/auth - '@query/db': - specifier: workspace:* - version: link:../../packages/db - '@tanstack/react-query': - specifier: ^5.90.12 - version: 5.90.12(react@18.3.1) - '@trpc/client': - specifier: ^11.7.2 - version: 11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3) - '@trpc/next': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) - '@trpc/react-query': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) - '@trpc/server': - specifier: ^11.8.0 - version: 11.8.0(typescript@5.9.3) - '@yudiel/react-qr-scanner': - specifier: ^2.5.0 - version: 2.5.0(@types/emscripten@1.41.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - minimatch: - specifier: ^10.1.1 - version: 10.1.1 - next: - specifier: 15.5.9 - version: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) - next-auth: - specifier: ^5.0.0-beta.25 - version: 5.0.0-beta.30(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react@18.3.1) - qrcode: - specifier: ^1.5.4 - version: 1.5.4 - react: - specifier: ^18.3.1 - version: 18.3.1 - react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) - sanitize-html: - specifier: ^2.17.0 - version: 2.17.0 - superjson: - specifier: ^2.2.1 - version: 2.2.6 - three: - specifier: ^0.128.0 - version: 0.128.0 - devDependencies: - '@types/node': - specifier: ^22.10.1 - version: 22.19.2 - '@types/qrcode': - specifier: ^1.5.6 - version: 1.5.6 - '@types/react': - specifier: ^18.3.1 - version: 18.3.27 - '@types/react-dom': - specifier: ^18.3.1 - version: 18.3.7(@types/react@18.3.27) - '@types/three': - specifier: ^0.128.0 - version: 0.128.0 - typescript: - specifier: ^5.6.3 - version: 5.9.3 - sites/mainweb: dependencies: '@query/ui': @@ -417,17 +347,17 @@ importers: specifier: ^5.90.12 version: 5.90.12(react@18.3.1) '@trpc/client': - specifier: ^11.7.2 - version: 11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3) + specifier: latest + version: 11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3) '@trpc/next': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + specifier: latest + version: 11.8.1(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.8.1(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.1(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@trpc/server@11.8.1(typescript@5.9.3))(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) '@trpc/react-query': - specifier: ^11.7.2 - version: 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + specifier: latest + version: 11.8.1(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.1(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) '@trpc/server': - specifier: ^11.8.0 - version: 11.8.0(typescript@5.9.3) + specifier: latest + version: 11.8.1(typescript@5.9.3) '@yudiel/react-qr-scanner': specifier: ^2.5.0 version: 2.5.0(@types/emscripten@1.41.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -455,6 +385,9 @@ importers: superjson: specifier: ^2.2.1 version: 2.2.6 + tailwindcss: + specifier: ^4.1.0 + version: 4.1.18 three: specifier: ^0.128.0 version: 0.128.0 @@ -1705,6 +1638,12 @@ packages: '@trpc/server': 11.7.2 typescript: '>=5.7.2' + '@trpc/client@11.8.1': + resolution: {integrity: sha512-L/SJFGanr9xGABmuDoeXR4xAdHJmsXsiF9OuH+apecJ+8sUITzVT1EPeqp0ebqA6lBhEl5pPfg3rngVhi/h60Q==} + peerDependencies: + '@trpc/server': 11.8.1 + typescript: '>=5.7.2' + '@trpc/next@11.7.2': resolution: {integrity: sha512-eZQeZag+/aJMxV6ucfyVdcgE7I6o88tldyBi+AFVCD8fPUjssRT7qOqR/THSaTLHjDl20Ok9gN0Sn1bmMEa+1w==} peerDependencies: @@ -1722,6 +1661,23 @@ packages: '@trpc/react-query': optional: true + '@trpc/next@11.8.1': + resolution: {integrity: sha512-nn9e7+k4uWaZnB2fruH1qoBKni2LG+FRNxvddp1pFzjsyNFdPWFiHWKTM8/McDFw6GQ2CorXs+ceCfgFNA+9zQ==} + peerDependencies: + '@tanstack/react-query': ^5.59.15 + '@trpc/client': 11.8.1 + '@trpc/react-query': 11.8.1 + '@trpc/server': 11.8.1 + next: 15.5.9 + react: ^18.3.1 + react-dom: ^18.3.1 + typescript: '>=5.7.2' + peerDependenciesMeta: + '@tanstack/react-query': + optional: true + '@trpc/react-query': + optional: true + '@trpc/react-query@11.7.2': resolution: {integrity: sha512-IcLDMqx2mvlGRxkr0/m37TtPvRQ8nonITH3EwYv436x0Igx8eduR9z4tdgGBsjJY9e5W1G7cZ4zKCwrizSimFQ==} peerDependencies: @@ -1732,11 +1688,26 @@ packages: react-dom: ^18.3.1 typescript: '>=5.7.2' + '@trpc/react-query@11.8.1': + resolution: {integrity: sha512-0Vu55ld/oINb4U6nIPPi7eZMhxUop6K+4QUK90RVsfSD5r+957sM80M4c8bjh/JBZUxMFv9JOhxxlWcrgHxHow==} + peerDependencies: + '@tanstack/react-query': ^5.80.3 + '@trpc/client': 11.8.1 + '@trpc/server': 11.8.1 + react: ^18.3.1 + react-dom: ^18.3.1 + typescript: '>=5.7.2' + '@trpc/server@11.8.0': resolution: {integrity: sha512-DphyQnLuyX2nwJCQGWQ9zYz4hZGvRhSBqDhQ0SH3tDhQ3PU4u68xofA0pJ741Ir4InEAFD+TtJVLAQy+wVOkiQ==} peerDependencies: typescript: '>=5.7.2' + '@trpc/server@11.8.1': + resolution: {integrity: sha512-P4rzZRpEL7zDFgjxK65IdyH0e41FMFfTkQkuq0BA5tKcr7E6v9/v38DEklCpoDN6sPiB1Sigy/PUEzHENhswDA==} + peerDependencies: + typescript: '>=5.7.2' + '@tsconfig/node10@1.0.12': resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} @@ -5082,6 +5053,11 @@ snapshots: '@trpc/server': 11.8.0(typescript@5.9.3) typescript: 5.9.3 + '@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3)': + dependencies: + '@trpc/server': 11.8.1(typescript@5.9.3) + typescript: 5.9.3 + '@trpc/next@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: '@trpc/client': 11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3) @@ -5094,6 +5070,18 @@ snapshots: '@tanstack/react-query': 5.90.12(react@18.3.1) '@trpc/react-query': 11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@trpc/next@11.8.1(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.8.1(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.1(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@trpc/server@11.8.1(typescript@5.9.3))(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': + dependencies: + '@trpc/client': 11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3) + '@trpc/server': 11.8.1(typescript@5.9.3) + next: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.96.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + typescript: 5.9.3 + optionalDependencies: + '@tanstack/react-query': 5.90.12(react@18.3.1) + '@trpc/react-query': 11.8.1(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.1(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@trpc/react-query@11.7.2(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.7.2(@trpc/server@11.8.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.0(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: '@tanstack/react-query': 5.90.12(react@18.3.1) @@ -5103,10 +5091,23 @@ snapshots: react-dom: 18.3.1(react@18.3.1) typescript: 5.9.3 + '@trpc/react-query@11.8.1(@tanstack/react-query@5.90.12(react@18.3.1))(@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.1(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': + dependencies: + '@tanstack/react-query': 5.90.12(react@18.3.1) + '@trpc/client': 11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3) + '@trpc/server': 11.8.1(typescript@5.9.3) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + typescript: 5.9.3 + '@trpc/server@11.8.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 + '@trpc/server@11.8.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@tsconfig/node10@1.0.12': {} '@tsconfig/node12@1.0.11': {} diff --git a/sites/judging/.gitignore b/sites/judging/.gitignore deleted file mode 100644 index 892067b..0000000 --- a/sites/judging/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/sites/judging/middleware.ts b/sites/judging/middleware.ts deleted file mode 100644 index b4ba127..0000000 --- a/sites/judging/middleware.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NextResponse } from 'next/server'; -import type { NextRequest } from 'next/server'; - -export function middleware(req: NextRequest) { - const { pathname } = req.nextUrl; - const protectedPaths = ['/judge', '/admin']; - const isProtected = protectedPaths.some((p) => pathname.startsWith(p)); - if (!isProtected) return NextResponse.next(); - - const token = req.cookies.get('query_session')?.value || req.cookies.get('authjs.session-token')?.value; - if (!token) { - const url = req.nextUrl.clone(); - url.pathname = '/'; - return NextResponse.redirect(url); - } - return NextResponse.next(); -} - -export const config = { - matcher: ['/judge/:path*', '/admin/:path*'], -}; diff --git a/sites/judging/next.config.mjs b/sites/judging/next.config.mjs deleted file mode 100644 index 8a522ff..0000000 --- a/sites/judging/next.config.mjs +++ /dev/null @@ -1,21 +0,0 @@ -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -/** @type {import('next').NextConfig} */ -const nextConfig = { - transpilePackages: ['@query/db', '@query/auth', '@query/api', '@query/ui'], - output: 'standalone', - outputFileTracingRoot: path.join(__dirname, '../../'), - reactStrictMode: true, - images: { - domains: ['lh3.googleusercontent.com'], - }, - webpack: (config) => { - return config; - }, -}; - -export default nextConfig; diff --git a/sites/judging/package.json b/sites/judging/package.json deleted file mode 100644 index d09122e..0000000 --- a/sites/judging/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "judging", - "version": "1.0.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@query/api": "workspace:*", - "@query/auth": "workspace:*", - "@query/db": "workspace:*", - "@tanstack/react-query": "^5.90.12", - "@trpc/client": "^11.7.2", - "@trpc/next": "^11.7.2", - "@trpc/react-query": "^11.7.2", - "@trpc/server": "^11.8.0", - "@yudiel/react-qr-scanner": "^2.5.0", - "minimatch": "^10.1.1", - "next": "15.5.9", - "next-auth": "^5.0.0-beta.25", - "qrcode": "^1.5.4", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "sanitize-html": "^2.17.0", - "superjson": "^2.2.1", - "three": "^0.128.0" - }, - "devDependencies": { - "@types/node": "^22.10.1", - "@types/qrcode": "^1.5.6", - "@types/react": "^18.3.1", - "@types/react-dom": "^18.3.1", - "@types/three": "^0.128.0", - "typescript": "^5.6.3" - } -} diff --git a/sites/judging/postcss.config.mjs b/sites/judging/postcss.config.mjs deleted file mode 100644 index a34a3d5..0000000 --- a/sites/judging/postcss.config.mjs +++ /dev/null @@ -1,5 +0,0 @@ -export default { - plugins: { - '@tailwindcss/postcss': {}, - }, -}; diff --git a/sites/judging/public/images/dsgt/Icon-128.png b/sites/judging/public/images/dsgt/Icon-128.png deleted file mode 100644 index 853ddf3..0000000 Binary files a/sites/judging/public/images/dsgt/Icon-128.png and /dev/null differ diff --git a/sites/judging/public/images/dsgt/Icon-196.png b/sites/judging/public/images/dsgt/Icon-196.png deleted file mode 100644 index 78c07fb..0000000 Binary files a/sites/judging/public/images/dsgt/Icon-196.png and /dev/null differ diff --git a/sites/judging/public/images/dsgt/Icon-512.png b/sites/judging/public/images/dsgt/Icon-512.png deleted file mode 100644 index e460b94..0000000 Binary files a/sites/judging/public/images/dsgt/Icon-512.png and /dev/null differ diff --git a/sites/judging/public/images/dsgt/apple-touch-icon.png b/sites/judging/public/images/dsgt/apple-touch-icon.png deleted file mode 100644 index 590fcd7..0000000 Binary files a/sites/judging/public/images/dsgt/apple-touch-icon.png and /dev/null differ diff --git a/sites/judging/public/images/dsgt/favicon-16x16.png b/sites/judging/public/images/dsgt/favicon-16x16.png deleted file mode 100644 index e549cc2..0000000 Binary files a/sites/judging/public/images/dsgt/favicon-16x16.png and /dev/null differ diff --git a/sites/judging/public/images/dsgt/favicon-32x32.png b/sites/judging/public/images/dsgt/favicon-32x32.png deleted file mode 100644 index 698f518..0000000 Binary files a/sites/judging/public/images/dsgt/favicon-32x32.png and /dev/null differ diff --git a/sites/judging/public/images/dsgt/favicon.ico b/sites/judging/public/images/dsgt/favicon.ico deleted file mode 100644 index 24ef5de..0000000 Binary files a/sites/judging/public/images/dsgt/favicon.ico and /dev/null differ diff --git a/sites/judging/public/images/dsgt/financial_icon.png b/sites/judging/public/images/dsgt/financial_icon.png deleted file mode 100644 index 40c3ca9..0000000 Binary files a/sites/judging/public/images/dsgt/financial_icon.png and /dev/null differ diff --git a/sites/judging/public/images/dsgt/heathcare_icon.png b/sites/judging/public/images/dsgt/heathcare_icon.png deleted file mode 100644 index 8af175b..0000000 Binary files a/sites/judging/public/images/dsgt/heathcare_icon.png and /dev/null differ diff --git a/sites/judging/public/images/dsgt/logo20.ico b/sites/judging/public/images/dsgt/logo20.ico deleted file mode 100644 index c107d5d..0000000 Binary files a/sites/judging/public/images/dsgt/logo20.ico and /dev/null differ diff --git a/sites/judging/public/images/dsgt/logos-20.ico b/sites/judging/public/images/dsgt/logos-20.ico deleted file mode 100644 index c361dc4..0000000 Binary files a/sites/judging/public/images/dsgt/logos-20.ico and /dev/null differ diff --git a/sites/judging/public/images/dsgt/logos-20.png b/sites/judging/public/images/dsgt/logos-20.png deleted file mode 100644 index 7d181c5..0000000 Binary files a/sites/judging/public/images/dsgt/logos-20.png and /dev/null differ diff --git a/sites/judging/public/images/dsgt/sports_icon.png b/sites/judging/public/images/dsgt/sports_icon.png deleted file mode 100644 index 312680a..0000000 Binary files a/sites/judging/public/images/dsgt/sports_icon.png and /dev/null differ diff --git a/sites/judging/public/images/dsgt/square-logo.png b/sites/judging/public/images/dsgt/square-logo.png deleted file mode 100644 index 7fb3016..0000000 Binary files a/sites/judging/public/images/dsgt/square-logo.png and /dev/null differ diff --git a/sites/judging/public/images/logos/Mentra.png b/sites/judging/public/images/logos/Mentra.png deleted file mode 100644 index 8145a23..0000000 Binary files a/sites/judging/public/images/logos/Mentra.png and /dev/null differ diff --git a/sites/judging/public/images/logos/arc-logo-v3.png b/sites/judging/public/images/logos/arc-logo-v3.png deleted file mode 100644 index ce6669b..0000000 Binary files a/sites/judging/public/images/logos/arc-logo-v3.png and /dev/null differ diff --git a/sites/judging/public/images/logos/birdclef.png b/sites/judging/public/images/logos/birdclef.png deleted file mode 100644 index 7221a66..0000000 Binary files a/sites/judging/public/images/logos/birdclef.png and /dev/null differ diff --git a/sites/judging/public/images/logos/blueconduit.png b/sites/judging/public/images/logos/blueconduit.png deleted file mode 100644 index e5a1bf8..0000000 Binary files a/sites/judging/public/images/logos/blueconduit.png and /dev/null differ diff --git a/sites/judging/public/images/logos/dlp4.png b/sites/judging/public/images/logos/dlp4.png deleted file mode 100644 index 6714415..0000000 Binary files a/sites/judging/public/images/logos/dlp4.png and /dev/null differ diff --git a/sites/judging/public/images/logos/furnichanter.png b/sites/judging/public/images/logos/furnichanter.png deleted file mode 100644 index b923e9f..0000000 Binary files a/sites/judging/public/images/logos/furnichanter.png and /dev/null differ diff --git a/sites/judging/public/images/logos/gtaa.png b/sites/judging/public/images/logos/gtaa.png deleted file mode 100644 index 750ac7e..0000000 Binary files a/sites/judging/public/images/logos/gtaa.png and /dev/null differ diff --git a/sites/judging/public/images/logos/shepcenter.jpeg b/sites/judging/public/images/logos/shepcenter.jpeg deleted file mode 100644 index 9fddaa2..0000000 Binary files a/sites/judging/public/images/logos/shepcenter.jpeg and /dev/null differ diff --git a/sites/judging/public/images/logos/stock.png b/sites/judging/public/images/logos/stock.png deleted file mode 100644 index f4a46c4..0000000 Binary files a/sites/judging/public/images/logos/stock.png and /dev/null differ diff --git a/sites/judging/public/images/logos/storm.png b/sites/judging/public/images/logos/storm.png deleted file mode 100644 index 2621897..0000000 Binary files a/sites/judging/public/images/logos/storm.png and /dev/null differ diff --git a/sites/judging/public/images/logos/trading.png b/sites/judging/public/images/logos/trading.png deleted file mode 100644 index 9b7ac3a..0000000 Binary files a/sites/judging/public/images/logos/trading.png and /dev/null differ diff --git a/sites/judging/src/app/api/auth/[...nextauth]/route.ts b/sites/judging/src/app/api/auth/[...nextauth]/route.ts deleted file mode 100644 index e560c50..0000000 --- a/sites/judging/src/app/api/auth/[...nextauth]/route.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { handlers } from "@query/auth"; - -export const { GET, POST } = handlers; \ No newline at end of file diff --git a/sites/judging/src/app/api/trpc/[trpc]/route.ts b/sites/judging/src/app/api/trpc/[trpc]/route.ts deleted file mode 100644 index 63ad5f9..0000000 --- a/sites/judging/src/app/api/trpc/[trpc]/route.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; -import { appRouter, createContext } from '@query/api'; -import { applySecurityHeaders, getClientIp } from '@query/api/middleware/http-security'; - -const handler = async (req: Request) => { - // Get client IP for logging/monitoring - const clientIp = getClientIp(req); - - // Create TRPC response - const response = await fetchRequestHandler({ - endpoint: '/api/trpc', - req, - router: appRouter, - createContext: (opts) => createContext({ ...opts, clientIp }), - onError: ({ error, path }) => { - console.error(`[TRPC Error] ${path}:`, error.message); - }, - }); - - // Apply security headers to response - return applySecurityHeaders(response, { - cacheable: false, // TRPC responses are dynamic - }); -}; - -export { handler as GET, handler as POST }; \ No newline at end of file diff --git a/sites/judging/src/app/globals.css b/sites/judging/src/app/globals.css deleted file mode 100644 index 54953d7..0000000 --- a/sites/judging/src/app/globals.css +++ /dev/null @@ -1,48 +0,0 @@ -@import "tailwindcss"; - -* { - box-sizing: border-box; - -webkit-tap-highlight-color: transparent; -} - -html, body { - margin: 0; - padding: 0; - background: #050505; - color: #9ca3af; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - -webkit-font-smoothing: antialiased; -} - -input, textarea, button { - font-family: inherit; - border: none; - outline: none; -} - -input[type="range"] { - -webkit-appearance: none; - background: transparent; -} - -input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - height: 24px; - width: 24px; - border-radius: 50%; - background: #00A8A8; - cursor: pointer; - margin-top: -10px; - box-shadow: 0 0 15px rgba(0, 168, 168, 0.5); -} - -input[type="range"]::-webkit-slider-runnable-track { - width: 100%; - height: 4px; - background: rgba(255, 255, 255, 0.1); - border-radius: 2px; -} - -::selection { - background: rgba(0, 168, 168, 0.3); -} diff --git a/sites/judging/src/app/layout.tsx b/sites/judging/src/app/layout.tsx deleted file mode 100644 index eaf15fd..0000000 --- a/sites/judging/src/app/layout.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Providers } from './providers'; -import './globals.css'; - -export const dynamic = 'force-dynamic'; -export const revalidate = 0; - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - - {children} - - - ); -} \ No newline at end of file diff --git a/sites/judging/src/app/not-found.tsx b/sites/judging/src/app/not-found.tsx deleted file mode 100644 index 18ac4d5..0000000 --- a/sites/judging/src/app/not-found.tsx +++ /dev/null @@ -1,52 +0,0 @@ -// src/app/not-found.tsx -import Link from "next/link"; -import Background from "@/components/Background"; - -export default function NotFound() { - return ( -
- - -
-
- - {/* Visual depth glow */} -
- -
-
- Error 404 // Lost in Space -
- -

- You fell
- - out of place. - -

-
- -

- The path you followed doesn't exist in our current deployment. Let's get you back to familiar territory. -

- -
- - Back to Home - - - - View Projects - -
-
-
-
- ); -} diff --git a/sites/judging/src/app/page.tsx b/sites/judging/src/app/page.tsx deleted file mode 100644 index 0857eaa..0000000 --- a/sites/judging/src/app/page.tsx +++ /dev/null @@ -1,118 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; -import { useSession, signIn } from 'next-auth/react'; -import { useRouter } from 'next/navigation'; - -export default function LoginPage() { - const { status } = useSession(); - const router = useRouter(); - const [mounted, setMounted] = useState(false); - const [logs, setLogs] = useState([ - "Initializing judging terminal...", - "System check: OK", - "Loading authentication modules..." - ]); - - useEffect(() => { - setMounted(true); - const timeout = setTimeout(() => { - setLogs(prev => [...prev.slice(-4), "> Network: Established", "> Session: Awaiting judge..."]); - }, 800); - return () => clearTimeout(timeout); - }, []); - - useEffect(() => { - if (status === 'authenticated') { - setLogs(prev => [...prev.slice(-4), "> Auth success. Verifying credentials...", "> Redirecting to judging interface..."]); - const timeout = setTimeout(() => router.push('/judge'), 1000); - return () => clearTimeout(timeout); - } - }, [status, router]); - - const handleSignIn = () => { - setLogs(prev => [...prev.slice(-4), "> Initializing OAuth..."]); - signIn('google', { callbackUrl: '/judge' }); - }; - - if (!mounted) return
; - - const isRedirecting = status === 'authenticated'; - - return ( -
- {/* Background effects */} -
-
-
-
- -
-
- {/* Header */} -
-
-
- DSGT // Judging -
-
- -

- Hackathon
- - Judging - -

- -

- Project Evaluation Terminal -

-
- - {/* Terminal box */} -
-
- {logs.map((log, i) => ( -

- {log} -

- ))} - {(isRedirecting || status === 'loading') && ( -

- {'>'} {status === 'loading' ? 'Syncing_Identity...' : 'Processing request...'} -

- )} -
- - {/* Sign in button */} - {status === 'loading' ? ( -
- Initializing... -
- ) : ( - - )} -
-
- - {/* Footer */} -
-

- Data Science @ Georgia Tech -

-
-
- ); -} diff --git a/sites/judging/src/app/providers.tsx b/sites/judging/src/app/providers.tsx deleted file mode 100644 index f0c7414..0000000 --- a/sites/judging/src/app/providers.tsx +++ /dev/null @@ -1,44 +0,0 @@ -'use client'; - -import { SessionProvider } from 'next-auth/react'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { httpBatchLink } from '@trpc/client'; -import { useState } from 'react'; -import superjson from 'superjson'; -import { trpc } from '@/lib/trpc'; - -export function Providers({ children }: { children: React.ReactNode }) { - const [queryClient] = useState(() => new QueryClient({ - defaultOptions: { - queries: { - staleTime: 60 * 1000, // 1 minute - }, - }, - })); - - const [trpcClient] = useState(() => - trpc.createClient({ - links: [ - httpBatchLink({ - url: '/api/trpc', - transformer: superjson, - headers() { - return { - // Cookies are automatically sent by the browser - }; - }, - }), - ], - }) - ); - - return ( - - - - {children} - - - - ); -} \ No newline at end of file diff --git a/sites/judging/src/components/Background.tsx b/sites/judging/src/components/Background.tsx deleted file mode 100644 index 95ec237..0000000 --- a/sites/judging/src/components/Background.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; - -const Background = ({ className = "" }: { className?: string }) => { - return ( -
- {/* Primary Glow */} -
- - {/* Secondary Orbital Glows */} -
-
- - {/* Technical Grid Overlay */} -
-
- ); -}; - -export default Background; diff --git a/sites/judging/src/lib/trpc.tsx b/sites/judging/src/lib/trpc.tsx deleted file mode 100644 index 2988363..0000000 --- a/sites/judging/src/lib/trpc.tsx +++ /dev/null @@ -1,44 +0,0 @@ -"use client"; - -import { createTRPCReact } from "@trpc/react-query"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { httpBatchLink } from "@trpc/client"; -import { useState } from "react"; -import type { AppRouter } from "@query/api"; -import superjson from "superjson"; - -export const trpc = createTRPCReact(); - -export function TRPCProvider({ children }: { children: React.ReactNode }) { - const [queryClient] = useState(() => new QueryClient({ - defaultOptions: { - queries: { - staleTime: 60 * 1000, // 1 minute - }, - }, - })); - - const [trpcClient] = useState(() => - trpc.createClient({ - links: [ - httpBatchLink({ - url: "/api/trpc", - transformer: superjson, - headers() { - return { - // Cookies are automatically sent by the browser - }; - }, - }), - ], - }) - ); - - return ( - - - {children} - - - ); -} \ No newline at end of file diff --git a/sites/judging/start.sh b/sites/judging/start.sh deleted file mode 100644 index 9d9d64a..0000000 --- a/sites/judging/start.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -# Exit immediately if any command fails -set -e - -echo "Running Turbo Build from root..." -(cd ../.. && pnpm turbo run build --concurrency=2) -echo "Starting development server..." -pnpm run dev \ No newline at end of file diff --git a/sites/judging/tsconfig.json b/sites/judging/tsconfig.json deleted file mode 100644 index cea8229..0000000 --- a/sites/judging/tsconfig.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "compilerOptions": { - "jsx": "preserve", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "incremental": true, - "types": [], - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": [ - "./src/*" - ] - }, - "target": "ES2017" - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts" - ], - "exclude": [ - "node_modules" - ] -} diff --git a/sites/portal/Dockerfile b/sites/portal/Dockerfile new file mode 100644 index 0000000..344da6f --- /dev/null +++ b/sites/portal/Dockerfile @@ -0,0 +1,83 @@ +# ========================================= +# Next.js Portal - Cloud Run Dockerfile +# ========================================= +# Using pnpm with shamefully-hoist for flat node_modules + +FROM node:20-alpine AS base + +# Install dependencies needed for native modules +RUN apk add --no-cache libc6-compat + +# Enable corepack for pnpm +RUN corepack enable && corepack prepare pnpm@latest --activate + +# ========================================= +# Stage 1: Install dependencies AND build +# ========================================= +FROM base AS builder +WORKDIR /app + +# Copy workspace configuration first +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ + +# Copy all package.json files for workspace packages +COPY packages/api/package.json ./packages/api/ +COPY packages/auth/package.json ./packages/auth/ +COPY packages/db/package.json ./packages/db/ +COPY packages/ui/package.json ./packages/ui/ +COPY sites/portal/package.json ./sites/portal/ + +# Create .npmrc with node-linker=hoisted for flat node_modules (no symlinks) +# This avoids ERR_MODULE_NOT_FOUND issues in standalone builds +RUN echo "node-linker=hoisted" > .npmrc + +# Install all dependencies with hoisting +RUN pnpm install --frozen-lockfile + +# Copy all source code after dependencies are installed +COPY packages ./packages +COPY sites/portal ./sites/portal +COPY tooling ./tooling + +# Build the portal +WORKDIR /app/sites/portal +ENV NEXT_TELEMETRY_DISABLED=1 +RUN pnpm run build + +# ========================================= +# Stage 2: Production runner +# ========================================= +FROM node:20-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV PORT=8080 +ENV HOSTNAME="0.0.0.0" + +# Create non-root user for security +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy the standalone build from builder +COPY --from=builder --chown=nextjs:nodejs /app/sites/portal/.next/standalone ./ + +# Copy static assets +COPY --from=builder --chown=nextjs:nodejs /app/sites/portal/.next/static ./sites/portal/.next/static + +# Copy public folder +COPY --from=builder --chown=nextjs:nodejs /app/sites/portal/public ./sites/portal/public + +# Copy packages to ensure symlinks in node_modules work +COPY --from=builder --chown=nextjs:nodejs /app/packages ./packages + +# Copy the entire node_modules from the root to fix module resolution +COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules + +# Switch to non-root user +USER nextjs + +# Expose the port +EXPOSE 8080 + +# Start the server +CMD ["node", "sites/portal/server.js"] diff --git a/sites/portal/build.log b/sites/portal/build.log new file mode 100644 index 0000000..98e8594 --- /dev/null +++ b/sites/portal/build.log @@ -0,0 +1,25 @@ + +> portal@1.0.0 build C:\Users\bootcamp\Desktop\passion\query\sites\portal +> next build + + ▲ Next.js 15.5.9 + - Environments: .env.local + + Creating an optimized production build ... + ✓ Compiled successfully in 4.2s + Linting and checking validity of types ... +Failed to compile. + +./src/app/api/trpc/[trpc]/route.ts:9:40 +Type error: Argument of type '{ req: Request; }' is not assignable to parameter of type 'FetchCreateContextFnOptions & { clientIp?: string | undefined; req?: Request | undefined; }'. + Type '{ req: Request; }' is missing the following properties from type 'FetchCreateContextFnOptions': resHeaders, info + +  7 | req, +  8 | router: appRouter, +> 9 | createContext: () => createContext({ req }), +  | ^ +  10 | onError: ({ error, path }) => { +  11 | console.error(`[TRPC Error] ${path}:`, error.message); +  12 | }, +Next.js build worker exited with code: 1 and signal: null + ELIFECYCLE  Command failed with exit code 1. diff --git a/sites/portal/package.json b/sites/portal/package.json index 97cae9c..1038189 100644 --- a/sites/portal/package.json +++ b/sites/portal/package.json @@ -13,19 +13,20 @@ "@query/auth": "workspace:*", "@query/db": "workspace:*", "@tanstack/react-query": "^5.90.12", - "@trpc/client": "^11.7.2", - "@trpc/next": "^11.7.2", - "@trpc/react-query": "^11.7.2", - "@trpc/server": "^11.8.0", + "@trpc/client": "latest", + "@trpc/next": "latest", + "@trpc/react-query": "latest", + "@trpc/server": "latest", "@yudiel/react-qr-scanner": "^2.5.0", "minimatch": "^10.1.1", "next": "15.5.9", "next-auth": "^5.0.0-beta.25", "qrcode": "^1.5.4", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "latest", + "react-dom": "latest", "sanitize-html": "^2.17.0", "superjson": "^2.2.1", + "tailwindcss": "^4.1.0", "three": "^0.128.0" }, "devDependencies": { @@ -36,4 +37,4 @@ "@types/three": "^0.128.0", "typescript": "^5.6.3" } -} +} \ No newline at end of file diff --git a/sites/judging/src/app/admin/page.tsx b/sites/portal/src/app/admin-judging/page.tsx similarity index 100% rename from sites/judging/src/app/admin/page.tsx rename to sites/portal/src/app/admin-judging/page.tsx diff --git a/sites/portal/src/app/api/trpc/[trpc]/route.ts b/sites/portal/src/app/api/trpc/[trpc]/route.ts deleted file mode 100644 index 63ad5f9..0000000 --- a/sites/portal/src/app/api/trpc/[trpc]/route.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; -import { appRouter, createContext } from '@query/api'; -import { applySecurityHeaders, getClientIp } from '@query/api/middleware/http-security'; - -const handler = async (req: Request) => { - // Get client IP for logging/monitoring - const clientIp = getClientIp(req); - - // Create TRPC response - const response = await fetchRequestHandler({ - endpoint: '/api/trpc', - req, - router: appRouter, - createContext: (opts) => createContext({ ...opts, clientIp }), - onError: ({ error, path }) => { - console.error(`[TRPC Error] ${path}:`, error.message); - }, - }); - - // Apply security headers to response - return applySecurityHeaders(response, { - cacheable: false, // TRPC responses are dynamic - }); -}; - -export { handler as GET, handler as POST }; \ No newline at end of file diff --git a/sites/judging/src/app/judge/page.tsx b/sites/portal/src/app/judge/page.tsx similarity index 100% rename from sites/judging/src/app/judge/page.tsx rename to sites/portal/src/app/judge/page.tsx diff --git a/sites/portal/src/app/page.tsx b/sites/portal/src/app/page.tsx index cc3b471..9c4613d 100644 --- a/sites/portal/src/app/page.tsx +++ b/sites/portal/src/app/page.tsx @@ -84,10 +84,19 @@ export default function Home() { useEffect(() => { if (status === 'authenticated' && session) { setLogs(prev => [...prev.slice(-4), "> Auth success. Handshaking...", "> Redirecting to secure node..."]); - const redirectTimeout = setTimeout(() => router.push('/dashboard'), 1200); + + const redirectTimeout = setTimeout(() => { + // Redirection Logic + if (judgeStatus?.isJudge || adminStatus?.isAdmin) { + router.push('/judge'); + } else { + router.push('/dashboard'); + } + }, 1200); + return () => clearTimeout(redirectTimeout); } - }, [status, session, router]); + }, [status, session, router, judgeStatus, adminStatus]); const handleTestEndpoint = () => { setLogs(prev => [...prev.slice(-4), "> Executing: public.sayHello()"]); diff --git a/sites/portal/src/components/Background.tsx b/sites/portal/src/components/Background.tsx index d7225fd..95ec237 100644 --- a/sites/portal/src/components/Background.tsx +++ b/sites/portal/src/components/Background.tsx @@ -1,4 +1,3 @@ -// components/Background.tsx import React from 'react'; const Background = ({ className = "" }: { className?: string }) => { @@ -25,4 +24,4 @@ const Background = ({ className = "" }: { className?: string }) => { ); }; -export default Background; \ No newline at end of file +export default Background; diff --git a/sites/portal/src/lib/trpc.tsx b/sites/portal/src/lib/trpc.tsx index 2988363..b8204ac 100644 --- a/sites/portal/src/lib/trpc.tsx +++ b/sites/portal/src/lib/trpc.tsx @@ -9,6 +9,12 @@ import superjson from "superjson"; export const trpc = createTRPCReact(); +function getBaseUrl() { + if (typeof window !== "undefined") return ""; + if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; + return `http://localhost:${process.env.PORT ?? 3000}`; +} + export function TRPCProvider({ children }: { children: React.ReactNode }) { const [queryClient] = useState(() => new QueryClient({ defaultOptions: { @@ -22,11 +28,11 @@ export function TRPCProvider({ children }: { children: React.ReactNode }) { trpc.createClient({ links: [ httpBatchLink({ - url: "/api/trpc", + url: `${getBaseUrl()}/api/trpc`, transformer: superjson, headers() { return { - // Cookies are automatically sent by the browser + 'x-trpc-source': 'react', }; }, }), diff --git a/sites/portal/src/pages/api/trpc/[trpc].ts b/sites/portal/src/pages/api/trpc/[trpc].ts new file mode 100644 index 0000000..e3d2c21 --- /dev/null +++ b/sites/portal/src/pages/api/trpc/[trpc].ts @@ -0,0 +1,23 @@ +import { createNextApiHandler } from '@trpc/server/adapters/next'; +import { appRouter, createContext } from '@query/api'; +import type { NextApiRequest, NextApiResponse } from 'next'; + +// Create the TRPC handler for Pages Router (Node.js HTTP) +// The createContext function will be called with the proper options +export default createNextApiHandler({ + router: appRouter, + createContext: async ({ req, res }) => { + // Extract client IP from headers (for Firebase Cloud Functions) + const forwarded = req.headers['x-forwarded-for']; + const clientIp = typeof forwarded === 'string' + ? forwarded.split(',')[0].trim() + : req.socket?.remoteAddress || 'unknown'; + + // Call createContext with just the clientIp since we're in Pages Router + // The context function will handle the rest + return createContext({ clientIp } as any); + }, + onError: ({ error, path }) => { + console.error(`[TRPC Error] ${path}:`, error.message); + }, +});