1 Commits

Author SHA1 Message Date
yohlo
6ed77dd471 idk 2025-09-24 07:59:13 -05:00
29 changed files with 1316 additions and 1444 deletions

View File

@@ -6,7 +6,7 @@
"scripts": { "scripts": {
"dev": "vite dev --host 0.0.0.0", "dev": "vite dev --host 0.0.0.0",
"build": "vite build && tsc --noEmit", "build": "vite build && tsc --noEmit",
"start": "vite start --host 0.0.0.0" "start": "node .output/server/index.mjs"
}, },
"dependencies": { "dependencies": {
"@hello-pangea/dnd": "^18.0.1", "@hello-pangea/dnd": "^18.0.1",
@@ -21,11 +21,10 @@
"@svgmoji/noto": "^3.2.0", "@svgmoji/noto": "^3.2.0",
"@tanstack/react-query": "^5.66.0", "@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-devtools": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0",
"@tanstack/react-router": "^1.130.12", "@tanstack/react-router": "1.130.12",
"@tanstack/react-router-devtools": "^1.130.13", "@tanstack/react-router-devtools": "1.130.13",
"@tanstack/react-router-with-query": "^1.130.12", "@tanstack/react-router-with-query": "1.130.12",
"@tanstack/react-start": "^1.132.2", "@tanstack/react-start": "1.130.15",
"@tanstack/react-virtual": "^3.13.12",
"@tiptap/pm": "^3.4.3", "@tiptap/pm": "^3.4.3",
"@tiptap/react": "^3.4.3", "@tiptap/react": "^3.4.3",
"@tiptap/starter-kit": "^3.4.3", "@tiptap/starter-kit": "^3.4.3",
@@ -52,8 +51,6 @@
"zustand": "^5.0.7" "zustand": "^5.0.7"
}, },
"devDependencies": { "devDependencies": {
"@tanstack/react-router-ssr-query": "^1.132.2",
"@tanstack/router-plugin": "^1.132.2",
"@types/node": "^22.5.4", "@types/node": "^22.5.4",
"@types/pg": "^8.15.5", "@types/pg": "^8.15.5",
"@types/react": "^19.0.8", "@types/react": "^19.0.8",
@@ -66,7 +63,7 @@
"postcss-simple-vars": "^7.0.1", "postcss-simple-vars": "^7.0.1",
"tsx": "^4.20.3", "tsx": "^4.20.3",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"vite": "^7.1.7", "vite": "^6.3.5",
"vite-tsconfig-paths": "^5.1.4" "vite-tsconfig-paths": "^5.1.4"
} }
} }

View File

@@ -8,6 +8,8 @@
// You should NOT make any changes in this file as it will be overwritten. // You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { createServerRootRoute } from '@tanstack/react-start/server'
import { Route as rootRouteImport } from './routes/__root' import { Route as rootRouteImport } from './routes/__root'
import { Route as RefreshSessionRouteImport } from './routes/refresh-session' import { Route as RefreshSessionRouteImport } from './routes/refresh-session'
import { Route as LogoutRouteImport } from './routes/logout' import { Route as LogoutRouteImport } from './routes/logout'
@@ -19,16 +21,6 @@ import { Route as AuthedSettingsRouteImport } from './routes/_authed/settings'
import { Route as AuthedAdminRouteImport } from './routes/_authed/admin' import { Route as AuthedAdminRouteImport } from './routes/_authed/admin'
import { Route as AuthedTournamentsIndexRouteImport } from './routes/_authed/tournaments/index' import { Route as AuthedTournamentsIndexRouteImport } from './routes/_authed/tournaments/index'
import { Route as AuthedAdminIndexRouteImport } from './routes/_authed/admin/index' import { Route as AuthedAdminIndexRouteImport } from './routes/_authed/admin/index'
import { Route as ApiTournamentsUploadLogoRouteImport } from './routes/api/tournaments/upload-logo'
import { Route as ApiTeamsUploadLogoRouteImport } from './routes/api/teams/upload-logo'
import { Route as ApiSpotifyTokenRouteImport } from './routes/api/spotify/token'
import { Route as ApiSpotifySearchRouteImport } from './routes/api/spotify/search'
import { Route as ApiSpotifyResumeRouteImport } from './routes/api/spotify/resume'
import { Route as ApiSpotifyPlaybackRouteImport } from './routes/api/spotify/playback'
import { Route as ApiSpotifyCaptureRouteImport } from './routes/api/spotify/capture'
import { Route as ApiSpotifyCallbackRouteImport } from './routes/api/spotify/callback'
import { Route as ApiEventsSplatRouteImport } from './routes/api/events.$'
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth.$'
import { Route as AuthedTournamentsTournamentIdRouteImport } from './routes/_authed/tournaments/$tournamentId' import { Route as AuthedTournamentsTournamentIdRouteImport } from './routes/_authed/tournaments/$tournamentId'
import { Route as AuthedTeamsTeamIdRouteImport } from './routes/_authed/teams.$teamId' import { Route as AuthedTeamsTeamIdRouteImport } from './routes/_authed/teams.$teamId'
import { Route as AuthedProfilePlayerIdRouteImport } from './routes/_authed/profile.$playerId' import { Route as AuthedProfilePlayerIdRouteImport } from './routes/_authed/profile.$playerId'
@@ -36,9 +28,21 @@ import { Route as AuthedAdminPreviewRouteImport } from './routes/_authed/admin/p
import { Route as AuthedAdminTournamentsIndexRouteImport } from './routes/_authed/admin/tournaments/index' import { Route as AuthedAdminTournamentsIndexRouteImport } from './routes/_authed/admin/tournaments/index'
import { Route as AuthedTournamentsIdBracketRouteImport } from './routes/_authed/tournaments/$id.bracket' import { Route as AuthedTournamentsIdBracketRouteImport } from './routes/_authed/tournaments/$id.bracket'
import { Route as AuthedAdminTournamentsIdIndexRouteImport } from './routes/_authed/admin/tournaments/$id/index' import { Route as AuthedAdminTournamentsIdIndexRouteImport } from './routes/_authed/admin/tournaments/$id/index'
import { Route as ApiFilesCollectionRecordIdFileRouteImport } from './routes/api/files/$collection/$recordId/$file'
import { Route as AuthedAdminTournamentsRunIdRouteImport } from './routes/_authed/admin/tournaments/run.$id' import { Route as AuthedAdminTournamentsRunIdRouteImport } from './routes/_authed/admin/tournaments/run.$id'
import { Route as AuthedAdminTournamentsIdTeamsRouteImport } from './routes/_authed/admin/tournaments/$id/teams' import { Route as AuthedAdminTournamentsIdTeamsRouteImport } from './routes/_authed/admin/tournaments/$id/teams'
import { ServerRoute as ApiTournamentsUploadLogoServerRouteImport } from './routes/api/tournaments/upload-logo'
import { ServerRoute as ApiTeamsUploadLogoServerRouteImport } from './routes/api/teams/upload-logo'
import { ServerRoute as ApiSpotifyTokenServerRouteImport } from './routes/api/spotify/token'
import { ServerRoute as ApiSpotifySearchServerRouteImport } from './routes/api/spotify/search'
import { ServerRoute as ApiSpotifyResumeServerRouteImport } from './routes/api/spotify/resume'
import { ServerRoute as ApiSpotifyPlaybackServerRouteImport } from './routes/api/spotify/playback'
import { ServerRoute as ApiSpotifyCaptureServerRouteImport } from './routes/api/spotify/capture'
import { ServerRoute as ApiSpotifyCallbackServerRouteImport } from './routes/api/spotify/callback'
import { ServerRoute as ApiEventsSplatServerRouteImport } from './routes/api/events.$'
import { ServerRoute as ApiAuthSplatServerRouteImport } from './routes/api/auth.$'
import { ServerRoute as ApiFilesCollectionRecordIdFileServerRouteImport } from './routes/api/files/$collection/$recordId/$file'
const rootServerRouteImport = createServerRootRoute()
const RefreshSessionRoute = RefreshSessionRouteImport.update({ const RefreshSessionRoute = RefreshSessionRouteImport.update({
id: '/refresh-session', id: '/refresh-session',
@@ -89,57 +93,6 @@ const AuthedAdminIndexRoute = AuthedAdminIndexRouteImport.update({
path: '/', path: '/',
getParentRoute: () => AuthedAdminRoute, getParentRoute: () => AuthedAdminRoute,
} as any) } as any)
const ApiTournamentsUploadLogoRoute =
ApiTournamentsUploadLogoRouteImport.update({
id: '/api/tournaments/upload-logo',
path: '/api/tournaments/upload-logo',
getParentRoute: () => rootRouteImport,
} as any)
const ApiTeamsUploadLogoRoute = ApiTeamsUploadLogoRouteImport.update({
id: '/api/teams/upload-logo',
path: '/api/teams/upload-logo',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifyTokenRoute = ApiSpotifyTokenRouteImport.update({
id: '/api/spotify/token',
path: '/api/spotify/token',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifySearchRoute = ApiSpotifySearchRouteImport.update({
id: '/api/spotify/search',
path: '/api/spotify/search',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifyResumeRoute = ApiSpotifyResumeRouteImport.update({
id: '/api/spotify/resume',
path: '/api/spotify/resume',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifyPlaybackRoute = ApiSpotifyPlaybackRouteImport.update({
id: '/api/spotify/playback',
path: '/api/spotify/playback',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifyCaptureRoute = ApiSpotifyCaptureRouteImport.update({
id: '/api/spotify/capture',
path: '/api/spotify/capture',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifyCallbackRoute = ApiSpotifyCallbackRouteImport.update({
id: '/api/spotify/callback',
path: '/api/spotify/callback',
getParentRoute: () => rootRouteImport,
} as any)
const ApiEventsSplatRoute = ApiEventsSplatRouteImport.update({
id: '/api/events/$',
path: '/api/events/$',
getParentRoute: () => rootRouteImport,
} as any)
const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
id: '/api/auth/$',
path: '/api/auth/$',
getParentRoute: () => rootRouteImport,
} as any)
const AuthedTournamentsTournamentIdRoute = const AuthedTournamentsTournamentIdRoute =
AuthedTournamentsTournamentIdRouteImport.update({ AuthedTournamentsTournamentIdRouteImport.update({
id: '/tournaments/$tournamentId', id: '/tournaments/$tournamentId',
@@ -179,12 +132,6 @@ const AuthedAdminTournamentsIdIndexRoute =
path: '/tournaments/$id/', path: '/tournaments/$id/',
getParentRoute: () => AuthedAdminRoute, getParentRoute: () => AuthedAdminRoute,
} as any) } as any)
const ApiFilesCollectionRecordIdFileRoute =
ApiFilesCollectionRecordIdFileRouteImport.update({
id: '/api/files/$collection/$recordId/$file',
path: '/api/files/$collection/$recordId/$file',
getParentRoute: () => rootRouteImport,
} as any)
const AuthedAdminTournamentsRunIdRoute = const AuthedAdminTournamentsRunIdRoute =
AuthedAdminTournamentsRunIdRouteImport.update({ AuthedAdminTournamentsRunIdRouteImport.update({
id: '/tournaments/run/$id', id: '/tournaments/run/$id',
@@ -197,6 +144,66 @@ const AuthedAdminTournamentsIdTeamsRoute =
path: '/tournaments/$id/teams', path: '/tournaments/$id/teams',
getParentRoute: () => AuthedAdminRoute, getParentRoute: () => AuthedAdminRoute,
} as any) } as any)
const ApiTournamentsUploadLogoServerRoute =
ApiTournamentsUploadLogoServerRouteImport.update({
id: '/api/tournaments/upload-logo',
path: '/api/tournaments/upload-logo',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiTeamsUploadLogoServerRoute =
ApiTeamsUploadLogoServerRouteImport.update({
id: '/api/teams/upload-logo',
path: '/api/teams/upload-logo',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiSpotifyTokenServerRoute = ApiSpotifyTokenServerRouteImport.update({
id: '/api/spotify/token',
path: '/api/spotify/token',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiSpotifySearchServerRoute = ApiSpotifySearchServerRouteImport.update({
id: '/api/spotify/search',
path: '/api/spotify/search',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiSpotifyResumeServerRoute = ApiSpotifyResumeServerRouteImport.update({
id: '/api/spotify/resume',
path: '/api/spotify/resume',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiSpotifyPlaybackServerRoute =
ApiSpotifyPlaybackServerRouteImport.update({
id: '/api/spotify/playback',
path: '/api/spotify/playback',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiSpotifyCaptureServerRoute = ApiSpotifyCaptureServerRouteImport.update({
id: '/api/spotify/capture',
path: '/api/spotify/capture',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiSpotifyCallbackServerRoute =
ApiSpotifyCallbackServerRouteImport.update({
id: '/api/spotify/callback',
path: '/api/spotify/callback',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiEventsSplatServerRoute = ApiEventsSplatServerRouteImport.update({
id: '/api/events/$',
path: '/api/events/$',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiAuthSplatServerRoute = ApiAuthSplatServerRouteImport.update({
id: '/api/auth/$',
path: '/api/auth/$',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiFilesCollectionRecordIdFileServerRoute =
ApiFilesCollectionRecordIdFileServerRouteImport.update({
id: '/api/files/$collection/$recordId/$file',
path: '/api/files/$collection/$recordId/$file',
getParentRoute: () => rootServerRouteImport,
} as any)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/login': typeof LoginRoute '/login': typeof LoginRoute
@@ -210,23 +217,12 @@ export interface FileRoutesByFullPath {
'/profile/$playerId': typeof AuthedProfilePlayerIdRoute '/profile/$playerId': typeof AuthedProfilePlayerIdRoute
'/teams/$teamId': typeof AuthedTeamsTeamIdRoute '/teams/$teamId': typeof AuthedTeamsTeamIdRoute
'/tournaments/$tournamentId': typeof AuthedTournamentsTournamentIdRoute '/tournaments/$tournamentId': typeof AuthedTournamentsTournamentIdRoute
'/api/auth/$': typeof ApiAuthSplatRoute
'/api/events/$': typeof ApiEventsSplatRoute
'/api/spotify/callback': typeof ApiSpotifyCallbackRoute
'/api/spotify/capture': typeof ApiSpotifyCaptureRoute
'/api/spotify/playback': typeof ApiSpotifyPlaybackRoute
'/api/spotify/resume': typeof ApiSpotifyResumeRoute
'/api/spotify/search': typeof ApiSpotifySearchRoute
'/api/spotify/token': typeof ApiSpotifyTokenRoute
'/api/teams/upload-logo': typeof ApiTeamsUploadLogoRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoRoute
'/admin/': typeof AuthedAdminIndexRoute '/admin/': typeof AuthedAdminIndexRoute
'/tournaments': typeof AuthedTournamentsIndexRoute '/tournaments': typeof AuthedTournamentsIndexRoute
'/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute '/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
'/admin/tournaments': typeof AuthedAdminTournamentsIndexRoute '/admin/tournaments': typeof AuthedAdminTournamentsIndexRoute
'/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute '/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute
'/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute '/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileRoute
'/admin/tournaments/$id': typeof AuthedAdminTournamentsIdIndexRoute '/admin/tournaments/$id': typeof AuthedAdminTournamentsIdIndexRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
@@ -240,23 +236,12 @@ export interface FileRoutesByTo {
'/profile/$playerId': typeof AuthedProfilePlayerIdRoute '/profile/$playerId': typeof AuthedProfilePlayerIdRoute
'/teams/$teamId': typeof AuthedTeamsTeamIdRoute '/teams/$teamId': typeof AuthedTeamsTeamIdRoute
'/tournaments/$tournamentId': typeof AuthedTournamentsTournamentIdRoute '/tournaments/$tournamentId': typeof AuthedTournamentsTournamentIdRoute
'/api/auth/$': typeof ApiAuthSplatRoute
'/api/events/$': typeof ApiEventsSplatRoute
'/api/spotify/callback': typeof ApiSpotifyCallbackRoute
'/api/spotify/capture': typeof ApiSpotifyCaptureRoute
'/api/spotify/playback': typeof ApiSpotifyPlaybackRoute
'/api/spotify/resume': typeof ApiSpotifyResumeRoute
'/api/spotify/search': typeof ApiSpotifySearchRoute
'/api/spotify/token': typeof ApiSpotifyTokenRoute
'/api/teams/upload-logo': typeof ApiTeamsUploadLogoRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoRoute
'/admin': typeof AuthedAdminIndexRoute '/admin': typeof AuthedAdminIndexRoute
'/tournaments': typeof AuthedTournamentsIndexRoute '/tournaments': typeof AuthedTournamentsIndexRoute
'/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute '/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
'/admin/tournaments': typeof AuthedAdminTournamentsIndexRoute '/admin/tournaments': typeof AuthedAdminTournamentsIndexRoute
'/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute '/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute
'/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute '/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileRoute
'/admin/tournaments/$id': typeof AuthedAdminTournamentsIdIndexRoute '/admin/tournaments/$id': typeof AuthedAdminTournamentsIdIndexRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
@@ -273,23 +258,12 @@ export interface FileRoutesById {
'/_authed/profile/$playerId': typeof AuthedProfilePlayerIdRoute '/_authed/profile/$playerId': typeof AuthedProfilePlayerIdRoute
'/_authed/teams/$teamId': typeof AuthedTeamsTeamIdRoute '/_authed/teams/$teamId': typeof AuthedTeamsTeamIdRoute
'/_authed/tournaments/$tournamentId': typeof AuthedTournamentsTournamentIdRoute '/_authed/tournaments/$tournamentId': typeof AuthedTournamentsTournamentIdRoute
'/api/auth/$': typeof ApiAuthSplatRoute
'/api/events/$': typeof ApiEventsSplatRoute
'/api/spotify/callback': typeof ApiSpotifyCallbackRoute
'/api/spotify/capture': typeof ApiSpotifyCaptureRoute
'/api/spotify/playback': typeof ApiSpotifyPlaybackRoute
'/api/spotify/resume': typeof ApiSpotifyResumeRoute
'/api/spotify/search': typeof ApiSpotifySearchRoute
'/api/spotify/token': typeof ApiSpotifyTokenRoute
'/api/teams/upload-logo': typeof ApiTeamsUploadLogoRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoRoute
'/_authed/admin/': typeof AuthedAdminIndexRoute '/_authed/admin/': typeof AuthedAdminIndexRoute
'/_authed/tournaments/': typeof AuthedTournamentsIndexRoute '/_authed/tournaments/': typeof AuthedTournamentsIndexRoute
'/_authed/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute '/_authed/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
'/_authed/admin/tournaments/': typeof AuthedAdminTournamentsIndexRoute '/_authed/admin/tournaments/': typeof AuthedAdminTournamentsIndexRoute
'/_authed/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute '/_authed/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute
'/_authed/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute '/_authed/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileRoute
'/_authed/admin/tournaments/$id/': typeof AuthedAdminTournamentsIdIndexRoute '/_authed/admin/tournaments/$id/': typeof AuthedAdminTournamentsIdIndexRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
@@ -306,23 +280,12 @@ export interface FileRouteTypes {
| '/profile/$playerId' | '/profile/$playerId'
| '/teams/$teamId' | '/teams/$teamId'
| '/tournaments/$tournamentId' | '/tournaments/$tournamentId'
| '/api/auth/$'
| '/api/events/$'
| '/api/spotify/callback'
| '/api/spotify/capture'
| '/api/spotify/playback'
| '/api/spotify/resume'
| '/api/spotify/search'
| '/api/spotify/token'
| '/api/teams/upload-logo'
| '/api/tournaments/upload-logo'
| '/admin/' | '/admin/'
| '/tournaments' | '/tournaments'
| '/tournaments/$id/bracket' | '/tournaments/$id/bracket'
| '/admin/tournaments' | '/admin/tournaments'
| '/admin/tournaments/$id/teams' | '/admin/tournaments/$id/teams'
| '/admin/tournaments/run/$id' | '/admin/tournaments/run/$id'
| '/api/files/$collection/$recordId/$file'
| '/admin/tournaments/$id' | '/admin/tournaments/$id'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: to:
@@ -336,23 +299,12 @@ export interface FileRouteTypes {
| '/profile/$playerId' | '/profile/$playerId'
| '/teams/$teamId' | '/teams/$teamId'
| '/tournaments/$tournamentId' | '/tournaments/$tournamentId'
| '/api/auth/$'
| '/api/events/$'
| '/api/spotify/callback'
| '/api/spotify/capture'
| '/api/spotify/playback'
| '/api/spotify/resume'
| '/api/spotify/search'
| '/api/spotify/token'
| '/api/teams/upload-logo'
| '/api/tournaments/upload-logo'
| '/admin' | '/admin'
| '/tournaments' | '/tournaments'
| '/tournaments/$id/bracket' | '/tournaments/$id/bracket'
| '/admin/tournaments' | '/admin/tournaments'
| '/admin/tournaments/$id/teams' | '/admin/tournaments/$id/teams'
| '/admin/tournaments/run/$id' | '/admin/tournaments/run/$id'
| '/api/files/$collection/$recordId/$file'
| '/admin/tournaments/$id' | '/admin/tournaments/$id'
id: id:
| '__root__' | '__root__'
@@ -368,6 +320,64 @@ export interface FileRouteTypes {
| '/_authed/profile/$playerId' | '/_authed/profile/$playerId'
| '/_authed/teams/$teamId' | '/_authed/teams/$teamId'
| '/_authed/tournaments/$tournamentId' | '/_authed/tournaments/$tournamentId'
| '/_authed/admin/'
| '/_authed/tournaments/'
| '/_authed/tournaments/$id/bracket'
| '/_authed/admin/tournaments/'
| '/_authed/admin/tournaments/$id/teams'
| '/_authed/admin/tournaments/run/$id'
| '/_authed/admin/tournaments/$id/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
AuthedRoute: typeof AuthedRouteWithChildren
LoginRoute: typeof LoginRoute
LogoutRoute: typeof LogoutRoute
RefreshSessionRoute: typeof RefreshSessionRoute
}
export interface FileServerRoutesByFullPath {
'/api/auth/$': typeof ApiAuthSplatServerRoute
'/api/events/$': typeof ApiEventsSplatServerRoute
'/api/spotify/callback': typeof ApiSpotifyCallbackServerRoute
'/api/spotify/capture': typeof ApiSpotifyCaptureServerRoute
'/api/spotify/playback': typeof ApiSpotifyPlaybackServerRoute
'/api/spotify/resume': typeof ApiSpotifyResumeServerRoute
'/api/spotify/search': typeof ApiSpotifySearchServerRoute
'/api/spotify/token': typeof ApiSpotifyTokenServerRoute
'/api/teams/upload-logo': typeof ApiTeamsUploadLogoServerRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoServerRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileServerRoute
}
export interface FileServerRoutesByTo {
'/api/auth/$': typeof ApiAuthSplatServerRoute
'/api/events/$': typeof ApiEventsSplatServerRoute
'/api/spotify/callback': typeof ApiSpotifyCallbackServerRoute
'/api/spotify/capture': typeof ApiSpotifyCaptureServerRoute
'/api/spotify/playback': typeof ApiSpotifyPlaybackServerRoute
'/api/spotify/resume': typeof ApiSpotifyResumeServerRoute
'/api/spotify/search': typeof ApiSpotifySearchServerRoute
'/api/spotify/token': typeof ApiSpotifyTokenServerRoute
'/api/teams/upload-logo': typeof ApiTeamsUploadLogoServerRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoServerRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileServerRoute
}
export interface FileServerRoutesById {
__root__: typeof rootServerRouteImport
'/api/auth/$': typeof ApiAuthSplatServerRoute
'/api/events/$': typeof ApiEventsSplatServerRoute
'/api/spotify/callback': typeof ApiSpotifyCallbackServerRoute
'/api/spotify/capture': typeof ApiSpotifyCaptureServerRoute
'/api/spotify/playback': typeof ApiSpotifyPlaybackServerRoute
'/api/spotify/resume': typeof ApiSpotifyResumeServerRoute
'/api/spotify/search': typeof ApiSpotifySearchServerRoute
'/api/spotify/token': typeof ApiSpotifyTokenServerRoute
'/api/teams/upload-logo': typeof ApiTeamsUploadLogoServerRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoServerRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileServerRoute
}
export interface FileServerRouteTypes {
fileServerRoutesByFullPath: FileServerRoutesByFullPath
fullPaths:
| '/api/auth/$' | '/api/auth/$'
| '/api/events/$' | '/api/events/$'
| '/api/spotify/callback' | '/api/spotify/callback'
@@ -378,32 +388,47 @@ export interface FileRouteTypes {
| '/api/spotify/token' | '/api/spotify/token'
| '/api/teams/upload-logo' | '/api/teams/upload-logo'
| '/api/tournaments/upload-logo' | '/api/tournaments/upload-logo'
| '/_authed/admin/'
| '/_authed/tournaments/'
| '/_authed/tournaments/$id/bracket'
| '/_authed/admin/tournaments/'
| '/_authed/admin/tournaments/$id/teams'
| '/_authed/admin/tournaments/run/$id'
| '/api/files/$collection/$recordId/$file' | '/api/files/$collection/$recordId/$file'
| '/_authed/admin/tournaments/$id/' fileServerRoutesByTo: FileServerRoutesByTo
fileRoutesById: FileRoutesById to:
| '/api/auth/$'
| '/api/events/$'
| '/api/spotify/callback'
| '/api/spotify/capture'
| '/api/spotify/playback'
| '/api/spotify/resume'
| '/api/spotify/search'
| '/api/spotify/token'
| '/api/teams/upload-logo'
| '/api/tournaments/upload-logo'
| '/api/files/$collection/$recordId/$file'
id:
| '__root__'
| '/api/auth/$'
| '/api/events/$'
| '/api/spotify/callback'
| '/api/spotify/capture'
| '/api/spotify/playback'
| '/api/spotify/resume'
| '/api/spotify/search'
| '/api/spotify/token'
| '/api/teams/upload-logo'
| '/api/tournaments/upload-logo'
| '/api/files/$collection/$recordId/$file'
fileServerRoutesById: FileServerRoutesById
} }
export interface RootRouteChildren { export interface RootServerRouteChildren {
AuthedRoute: typeof AuthedRouteWithChildren ApiAuthSplatServerRoute: typeof ApiAuthSplatServerRoute
LoginRoute: typeof LoginRoute ApiEventsSplatServerRoute: typeof ApiEventsSplatServerRoute
LogoutRoute: typeof LogoutRoute ApiSpotifyCallbackServerRoute: typeof ApiSpotifyCallbackServerRoute
RefreshSessionRoute: typeof RefreshSessionRoute ApiSpotifyCaptureServerRoute: typeof ApiSpotifyCaptureServerRoute
ApiAuthSplatRoute: typeof ApiAuthSplatRoute ApiSpotifyPlaybackServerRoute: typeof ApiSpotifyPlaybackServerRoute
ApiEventsSplatRoute: typeof ApiEventsSplatRoute ApiSpotifyResumeServerRoute: typeof ApiSpotifyResumeServerRoute
ApiSpotifyCallbackRoute: typeof ApiSpotifyCallbackRoute ApiSpotifySearchServerRoute: typeof ApiSpotifySearchServerRoute
ApiSpotifyCaptureRoute: typeof ApiSpotifyCaptureRoute ApiSpotifyTokenServerRoute: typeof ApiSpotifyTokenServerRoute
ApiSpotifyPlaybackRoute: typeof ApiSpotifyPlaybackRoute ApiTeamsUploadLogoServerRoute: typeof ApiTeamsUploadLogoServerRoute
ApiSpotifyResumeRoute: typeof ApiSpotifyResumeRoute ApiTournamentsUploadLogoServerRoute: typeof ApiTournamentsUploadLogoServerRoute
ApiSpotifySearchRoute: typeof ApiSpotifySearchRoute ApiFilesCollectionRecordIdFileServerRoute: typeof ApiFilesCollectionRecordIdFileServerRoute
ApiSpotifyTokenRoute: typeof ApiSpotifyTokenRoute
ApiTeamsUploadLogoRoute: typeof ApiTeamsUploadLogoRoute
ApiTournamentsUploadLogoRoute: typeof ApiTournamentsUploadLogoRoute
ApiFilesCollectionRecordIdFileRoute: typeof ApiFilesCollectionRecordIdFileRoute
} }
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
@@ -478,76 +503,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthedAdminIndexRouteImport preLoaderRoute: typeof AuthedAdminIndexRouteImport
parentRoute: typeof AuthedAdminRoute parentRoute: typeof AuthedAdminRoute
} }
'/api/tournaments/upload-logo': {
id: '/api/tournaments/upload-logo'
path: '/api/tournaments/upload-logo'
fullPath: '/api/tournaments/upload-logo'
preLoaderRoute: typeof ApiTournamentsUploadLogoRouteImport
parentRoute: typeof rootRouteImport
}
'/api/teams/upload-logo': {
id: '/api/teams/upload-logo'
path: '/api/teams/upload-logo'
fullPath: '/api/teams/upload-logo'
preLoaderRoute: typeof ApiTeamsUploadLogoRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/token': {
id: '/api/spotify/token'
path: '/api/spotify/token'
fullPath: '/api/spotify/token'
preLoaderRoute: typeof ApiSpotifyTokenRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/search': {
id: '/api/spotify/search'
path: '/api/spotify/search'
fullPath: '/api/spotify/search'
preLoaderRoute: typeof ApiSpotifySearchRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/resume': {
id: '/api/spotify/resume'
path: '/api/spotify/resume'
fullPath: '/api/spotify/resume'
preLoaderRoute: typeof ApiSpotifyResumeRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/playback': {
id: '/api/spotify/playback'
path: '/api/spotify/playback'
fullPath: '/api/spotify/playback'
preLoaderRoute: typeof ApiSpotifyPlaybackRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/capture': {
id: '/api/spotify/capture'
path: '/api/spotify/capture'
fullPath: '/api/spotify/capture'
preLoaderRoute: typeof ApiSpotifyCaptureRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/callback': {
id: '/api/spotify/callback'
path: '/api/spotify/callback'
fullPath: '/api/spotify/callback'
preLoaderRoute: typeof ApiSpotifyCallbackRouteImport
parentRoute: typeof rootRouteImport
}
'/api/events/$': {
id: '/api/events/$'
path: '/api/events/$'
fullPath: '/api/events/$'
preLoaderRoute: typeof ApiEventsSplatRouteImport
parentRoute: typeof rootRouteImport
}
'/api/auth/$': {
id: '/api/auth/$'
path: '/api/auth/$'
fullPath: '/api/auth/$'
preLoaderRoute: typeof ApiAuthSplatRouteImport
parentRoute: typeof rootRouteImport
}
'/_authed/tournaments/$tournamentId': { '/_authed/tournaments/$tournamentId': {
id: '/_authed/tournaments/$tournamentId' id: '/_authed/tournaments/$tournamentId'
path: '/tournaments/$tournamentId' path: '/tournaments/$tournamentId'
@@ -597,13 +552,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthedAdminTournamentsIdIndexRouteImport preLoaderRoute: typeof AuthedAdminTournamentsIdIndexRouteImport
parentRoute: typeof AuthedAdminRoute parentRoute: typeof AuthedAdminRoute
} }
'/api/files/$collection/$recordId/$file': {
id: '/api/files/$collection/$recordId/$file'
path: '/api/files/$collection/$recordId/$file'
fullPath: '/api/files/$collection/$recordId/$file'
preLoaderRoute: typeof ApiFilesCollectionRecordIdFileRouteImport
parentRoute: typeof rootRouteImport
}
'/_authed/admin/tournaments/run/$id': { '/_authed/admin/tournaments/run/$id': {
id: '/_authed/admin/tournaments/run/$id' id: '/_authed/admin/tournaments/run/$id'
path: '/tournaments/run/$id' path: '/tournaments/run/$id'
@@ -620,6 +568,87 @@ declare module '@tanstack/react-router' {
} }
} }
} }
declare module '@tanstack/react-start/server' {
interface ServerFileRoutesByPath {
'/api/tournaments/upload-logo': {
id: '/api/tournaments/upload-logo'
path: '/api/tournaments/upload-logo'
fullPath: '/api/tournaments/upload-logo'
preLoaderRoute: typeof ApiTournamentsUploadLogoServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/teams/upload-logo': {
id: '/api/teams/upload-logo'
path: '/api/teams/upload-logo'
fullPath: '/api/teams/upload-logo'
preLoaderRoute: typeof ApiTeamsUploadLogoServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/spotify/token': {
id: '/api/spotify/token'
path: '/api/spotify/token'
fullPath: '/api/spotify/token'
preLoaderRoute: typeof ApiSpotifyTokenServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/spotify/search': {
id: '/api/spotify/search'
path: '/api/spotify/search'
fullPath: '/api/spotify/search'
preLoaderRoute: typeof ApiSpotifySearchServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/spotify/resume': {
id: '/api/spotify/resume'
path: '/api/spotify/resume'
fullPath: '/api/spotify/resume'
preLoaderRoute: typeof ApiSpotifyResumeServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/spotify/playback': {
id: '/api/spotify/playback'
path: '/api/spotify/playback'
fullPath: '/api/spotify/playback'
preLoaderRoute: typeof ApiSpotifyPlaybackServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/spotify/capture': {
id: '/api/spotify/capture'
path: '/api/spotify/capture'
fullPath: '/api/spotify/capture'
preLoaderRoute: typeof ApiSpotifyCaptureServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/spotify/callback': {
id: '/api/spotify/callback'
path: '/api/spotify/callback'
fullPath: '/api/spotify/callback'
preLoaderRoute: typeof ApiSpotifyCallbackServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/events/$': {
id: '/api/events/$'
path: '/api/events/$'
fullPath: '/api/events/$'
preLoaderRoute: typeof ApiEventsSplatServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/auth/$': {
id: '/api/auth/$'
path: '/api/auth/$'
fullPath: '/api/auth/$'
preLoaderRoute: typeof ApiAuthSplatServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/files/$collection/$recordId/$file': {
id: '/api/files/$collection/$recordId/$file'
path: '/api/files/$collection/$recordId/$file'
fullPath: '/api/files/$collection/$recordId/$file'
preLoaderRoute: typeof ApiFilesCollectionRecordIdFileServerRouteImport
parentRoute: typeof rootServerRouteImport
}
}
}
interface AuthedAdminRouteChildren { interface AuthedAdminRouteChildren {
AuthedAdminPreviewRoute: typeof AuthedAdminPreviewRoute AuthedAdminPreviewRoute: typeof AuthedAdminPreviewRoute
@@ -675,26 +704,24 @@ const rootRouteChildren: RootRouteChildren = {
LoginRoute: LoginRoute, LoginRoute: LoginRoute,
LogoutRoute: LogoutRoute, LogoutRoute: LogoutRoute,
RefreshSessionRoute: RefreshSessionRoute, RefreshSessionRoute: RefreshSessionRoute,
ApiAuthSplatRoute: ApiAuthSplatRoute,
ApiEventsSplatRoute: ApiEventsSplatRoute,
ApiSpotifyCallbackRoute: ApiSpotifyCallbackRoute,
ApiSpotifyCaptureRoute: ApiSpotifyCaptureRoute,
ApiSpotifyPlaybackRoute: ApiSpotifyPlaybackRoute,
ApiSpotifyResumeRoute: ApiSpotifyResumeRoute,
ApiSpotifySearchRoute: ApiSpotifySearchRoute,
ApiSpotifyTokenRoute: ApiSpotifyTokenRoute,
ApiTeamsUploadLogoRoute: ApiTeamsUploadLogoRoute,
ApiTournamentsUploadLogoRoute: ApiTournamentsUploadLogoRoute,
ApiFilesCollectionRecordIdFileRoute: ApiFilesCollectionRecordIdFileRoute,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>() ._addFileTypes<FileRouteTypes>()
const rootServerRouteChildren: RootServerRouteChildren = {
import type { getRouter } from './router.tsx' ApiAuthSplatServerRoute: ApiAuthSplatServerRoute,
import type { createStart } from '@tanstack/react-start' ApiEventsSplatServerRoute: ApiEventsSplatServerRoute,
declare module '@tanstack/react-start' { ApiSpotifyCallbackServerRoute: ApiSpotifyCallbackServerRoute,
interface Register { ApiSpotifyCaptureServerRoute: ApiSpotifyCaptureServerRoute,
router: Awaited<ReturnType<typeof getRouter>> ApiSpotifyPlaybackServerRoute: ApiSpotifyPlaybackServerRoute,
} ApiSpotifyResumeServerRoute: ApiSpotifyResumeServerRoute,
ApiSpotifySearchServerRoute: ApiSpotifySearchServerRoute,
ApiSpotifyTokenServerRoute: ApiSpotifyTokenServerRoute,
ApiTeamsUploadLogoServerRoute: ApiTeamsUploadLogoServerRoute,
ApiTournamentsUploadLogoServerRoute: ApiTournamentsUploadLogoServerRoute,
ApiFilesCollectionRecordIdFileServerRoute:
ApiFilesCollectionRecordIdFileServerRoute,
} }
export const serverRouteTree = rootServerRouteImport
._addFileChildren(rootServerRouteChildren)
._addFileTypes<FileServerRouteTypes>()

View File

@@ -1,11 +1,15 @@
import { QueryClient } from "@tanstack/react-query"; import { QueryClient } from "@tanstack/react-query";
import { createRouter as createTanStackRouter } from "@tanstack/react-router"; import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query"; import { routerWithQueryClient } from "@tanstack/react-router-with-query";
import { routeTree } from "./routeTree.gen"; import { routeTree } from "./routeTree.gen";
import { DefaultCatchBoundary } from "../components/DefaultCatchBoundary"; import { DefaultCatchBoundary } from "../components/DefaultCatchBoundary";
import { defaultHeaderConfig } from "@/features/core/hooks/use-router-config"; import { defaultHeaderConfig } from "@/features/core/hooks/use-router-config";
export function getRouter() { import dotenv from 'dotenv';
dotenv.config();
export function createRouter() {
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
@@ -18,7 +22,8 @@ export function getRouter() {
}, },
}); });
const router = createTanStackRouter({ return routerWithQueryClient(
createTanStackRouter({
routeTree, routeTree,
context: { context: {
queryClient, queryClient,
@@ -32,18 +37,13 @@ export function getRouter() {
defaultErrorComponent: DefaultCatchBoundary, defaultErrorComponent: DefaultCatchBoundary,
scrollRestoration: true, scrollRestoration: true,
defaultViewTransition: false, defaultViewTransition: false,
}); }),
setupRouterSsrQueryIntegration({
router,
queryClient queryClient
}) );
return router;
} }
declare module "@tanstack/react-router" { declare module "@tanstack/react-router" {
interface Register { interface Register {
router: ReturnType<typeof getRouter>; router: ReturnType<typeof createRouter>;
} }
} }

View File

@@ -1,5 +1,5 @@
// API file that handles all supertokens auth routes // API file that handles all supertokens auth routes
import { createFileRoute } from '@tanstack/react-router'; import { createServerFileRoute } from '@tanstack/react-start/server';
import { handleAuthAPIRequest } from 'supertokens-node/custom' import { handleAuthAPIRequest } from 'supertokens-node/custom'
import { ensureSuperTokensBackend } from '@/lib/supertokens/server' import { ensureSuperTokensBackend } from '@/lib/supertokens/server'
@@ -12,9 +12,7 @@ const handleRequest = async ({ request }: {request: Request}) => {
console.log("Handling auth request:", request.method, request.url); console.log("Handling auth request:", request.method, request.url);
return superTokensHandler(request); return superTokensHandler(request);
}; };
export const Route = createFileRoute('/api/auth/$')({ export const ServerRoute = createServerFileRoute('/api/auth/$').methods({
server: {
handlers: {
GET: handleRequest, GET: handleRequest,
POST: handleRequest, POST: handleRequest,
PUT: handleRequest, PUT: handleRequest,
@@ -22,6 +20,4 @@ export const Route = createFileRoute('/api/auth/$')({
PATCH: handleRequest, PATCH: handleRequest,
OPTIONS: handleRequest, OPTIONS: handleRequest,
HEAD: handleRequest, HEAD: handleRequest,
}
}
}) })

View File

@@ -1,14 +1,11 @@
import { createFileRoute } from "@tanstack/react-router"; import { createServerFileRoute } from "@tanstack/react-start/server";
import { serverEvents, type ServerEvent } from "@/lib/events/emitter"; import { serverEvents, type ServerEvent } from "@/lib/events/emitter";
import { logger } from "@/lib/logger"; import { logger } from "@/lib/logger";
import { superTokensRequestMiddleware } from "@/utils/supertokens"; import { superTokensRequestMiddleware } from "@/utils/supertokens";
export const Route = createFileRoute("/api/events/$")({ export const ServerRoute = createServerFileRoute("/api/events/$").middleware([superTokensRequestMiddleware]).methods({
server: {
middleware: [superTokensRequestMiddleware],
handlers: {
GET: ({ request, context }) => { GET: ({ request, context }) => {
logger.info("ServerEvents | New connection", context?.userAuthId); logger.info('ServerEvents | New connection', context?.userAuthId);
const stream = new ReadableStream({ const stream = new ReadableStream({
start(controller) { start(controller) {
@@ -16,7 +13,7 @@ export const Route = createFileRoute("/api/events/$")({
controller.enqueue(new TextEncoder().encode(connectMessage)); controller.enqueue(new TextEncoder().encode(connectMessage));
const handleEvent = (event: ServerEvent) => { const handleEvent = (event: ServerEvent) => {
logger.info("ServerEvents | Event received", event); logger.info('ServerEvents | Event received', event);
const message = `data: ${JSON.stringify(event)}\n\n`; const message = `data: ${JSON.stringify(event)}\n\n`;
try { try {
controller.enqueue(new TextEncoder().encode(message)); controller.enqueue(new TextEncoder().encode(message));
@@ -43,16 +40,15 @@ export const Route = createFileRoute("/api/events/$")({
serverEvents.off("test", handleEvent); serverEvents.off("test", handleEvent);
clearInterval(pingInterval); clearInterval(pingInterval);
try { try {
logger.info( logger.info('ServerEvents | Closing connection', context?.userAuthId);
"ServerEvents | Closing connection", controller.close();
context?.userAuthId
);
} catch (e) { } catch (e) {
logger.error("ServerEvents | Error closing controller", e); logger.error('ServerEvents | Error closing controller', e);
} }
}; };
request.signal?.addEventListener("abort", cleanup); request.signal?.addEventListener("abort", cleanup);
return cleanup; return cleanup;
}, },
}); });
@@ -67,6 +63,4 @@ export const Route = createFileRoute("/api/events/$")({
}, },
}); });
}, },
},
},
}); });

View File

@@ -1,100 +1,84 @@
import { createFileRoute } from "@tanstack/react-router"; import { createServerFileRoute } from "@tanstack/react-start/server";
import { logger } from "@/lib/logger"; import { logger } from "@/lib/logger";
export const Route = createFileRoute( export const ServerRoute = createServerFileRoute("/api/files/$collection/$recordId/$file").methods({
"/api/files/$collection/$recordId/$file"
)({
server: {
handlers: {
GET: async ({ params, request }) => { GET: async ({ params, request }) => {
try { try {
const { collection, recordId, file } = params; const { collection, recordId, file } = params;
const pocketbaseUrl = const pocketbaseUrl = process.env.POCKETBASE_URL || 'http://127.0.0.1:8090';
process.env.POCKETBASE_URL || "http://127.0.0.1:8090";
const fileUrl = `${pocketbaseUrl}/api/files/${collection}/${recordId}/${file}`; const fileUrl = `${pocketbaseUrl}/api/files/${collection}/${recordId}/${file}`;
logger.info("File proxy", { logger.info('File proxy', {
collection, collection,
recordId, recordId,
file, file,
targetUrl: fileUrl, targetUrl: fileUrl
}); });
const response = await fetch(fileUrl, { const response = await fetch(fileUrl, {
method: "GET", method: 'GET',
headers: { headers: {
...(request.headers.get("range") && { ...(request.headers.get('range') && { 'Range': request.headers.get('range')! }),
Range: request.headers.get("range")!, ...(request.headers.get('if-none-match') && { 'If-None-Match': request.headers.get('if-none-match')! }),
}), ...(request.headers.get('if-modified-since') && { 'If-Modified-Since': request.headers.get('if-modified-since')! }),
...(request.headers.get("if-none-match") && {
"If-None-Match": request.headers.get("if-none-match")!,
}),
...(request.headers.get("if-modified-since") && {
"If-Modified-Since": request.headers.get("if-modified-since")!,
}),
}, },
}); });
if (!response.ok) { if (!response.ok) {
logger.error("PocketBase file request failed", { logger.error('PocketBase file request failed', {
status: response.status, status: response.status,
statusText: response.statusText, statusText: response.statusText,
url: fileUrl, url: fileUrl
}); });
if (response.status === 404) { if (response.status === 404) {
return new Response("File not found", { status: 404 }); return new Response('File not found', { status: 404 });
} }
return new Response(`PocketBase error: ${response.statusText}`, { return new Response(`PocketBase error: ${response.statusText}`, {
status: response.status, status: response.status
}); });
} }
const body = response.body; const body = response.body;
const responseHeaders = new Headers(); const responseHeaders = new Headers();
const headers = [ const headers = [
"content-type", 'content-type',
"content-length", 'content-length',
"content-disposition", 'content-disposition',
"etag", 'etag',
"last-modified", 'last-modified',
"cache-control", 'cache-control',
"accept-ranges", 'accept-ranges',
"content-range", 'content-range'
]; ];
headers.forEach((header) => { headers.forEach(header => {
const value = response.headers.get(header); const value = response.headers.get(header);
if (value) { if (value) {
responseHeaders.set(header, value); responseHeaders.set(header, value);
} }
}); });
responseHeaders.set("Access-Control-Allow-Origin", "*"); responseHeaders.set('Access-Control-Allow-Origin', '*');
responseHeaders.set( responseHeaders.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
"Access-Control-Allow-Methods", responseHeaders.set('Access-Control-Allow-Headers', 'Range, If-None-Match, If-Modified-Since');
"GET, HEAD, OPTIONS"
);
responseHeaders.set(
"Access-Control-Allow-Headers",
"Range, If-None-Match, If-Modified-Since"
);
logger.info("File proxy response", { logger.info('File proxy response', {
status: response.status, status: response.status,
contentType: response.headers.get("content-type"), contentType: response.headers.get('content-type'),
contentLength: response.headers.get("content-length"), contentLength: response.headers.get('content-length')
}); });
return new Response(body, { return new Response(body, {
status: response.status, status: response.status,
statusText: response.statusText, statusText: response.statusText,
headers: responseHeaders, headers: responseHeaders
}); });
} catch (error) { } catch (error) {
logger.error("File proxy error", error); logger.error('File proxy error', error);
return new Response("Internal server error", { status: 500 }); return new Response('Internal server error', { status: 500 });
} }
}, },
@@ -102,12 +86,10 @@ export const Route = createFileRoute(
return new Response(null, { return new Response(null, {
status: 200, status: 200,
headers: { headers: {
"Access-Control-Allow-Origin": "*", 'Access-Control-Allow-Origin': '*',
"Access-Control-Allow-Methods": "GET, OPTIONS", 'Access-Control-Allow-Methods': 'GET, OPTIONS',
"Access-Control-Max-Age": "86400", 'Access-Control-Max-Age': '86400',
}, }
}); });
}, }
},
},
}); });

View File

@@ -1,146 +1,127 @@
import { createFileRoute } from "@tanstack/react-router"; import { createServerFileRoute } from '@tanstack/react-start/server'
import { SpotifyAuth } from "@/lib/spotify/auth"; import { SpotifyAuth } from '@/lib/spotify/auth'
const SPOTIFY_CLIENT_ID = import.meta.env.VITE_SPOTIFY_CLIENT_ID!; const SPOTIFY_CLIENT_ID = import.meta.env.VITE_SPOTIFY_CLIENT_ID!
const SPOTIFY_CLIENT_SECRET = import.meta.env.SPOTIFY_CLIENT_SECRET!; const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET!
const SPOTIFY_REDIRECT_URI = import.meta.env.VITE_SPOTIFY_REDIRECT_URI!; const SPOTIFY_REDIRECT_URI = import.meta.env.VITE_SPOTIFY_REDIRECT_URI!
export const Route = createFileRoute("/api/spotify/callback")({ export const ServerRoute = createServerFileRoute('/api/spotify/callback').methods({
server: {
handlers: {
GET: async ({ request }: { request: Request }) => { GET: async ({ request }: { request: Request }) => {
const getReturnPath = (state: string | null): string => { const getReturnPath = (state: string | null): string => {
if (!state) return "/"; if (!state) return '/';
try { try {
const decodedState = JSON.parse(atob(state)); const decodedState = JSON.parse(atob(state));
return decodedState.returnPath || "/"; return decodedState.returnPath || '/';
} catch { } catch {
return "/"; return '/';
} }
}; };
try { try {
const url = new URL(request.url); const url = new URL(request.url)
const code = url.searchParams.get("code"); const code = url.searchParams.get('code')
const state = url.searchParams.get("state"); const state = url.searchParams.get('state')
const error = url.searchParams.get("error"); const error = url.searchParams.get('error')
const returnPath = getReturnPath(state); const returnPath = getReturnPath(state);
if (error) { if (error) {
console.error("Spotify OAuth error:", error); console.error('Spotify OAuth error:', error)
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { headers: {
Location: 'Location': returnPath + '?spotify_error=' + encodeURIComponent(error),
returnPath + "?spotify_error=" + encodeURIComponent(error),
}, },
}); })
} }
if (!code || !state) { if (!code || !state) {
console.error("Missing code or state:", { console.error('Missing code or state:', { code: !!code, state: !!state })
code: !!code,
state: !!state,
});
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { headers: {
Location: returnPath + "?spotify_error=missing_code_or_state", 'Location': returnPath + '?spotify_error=missing_code_or_state',
}, },
}); })
} }
console.log("Token exchange attempt:", { console.log('Token exchange attempt:', {
client_id: SPOTIFY_CLIENT_ID, client_id: SPOTIFY_CLIENT_ID,
redirect_uri: SPOTIFY_REDIRECT_URI, redirect_uri: SPOTIFY_REDIRECT_URI,
has_code: !!code, has_code: !!code,
has_state: !!state, has_state: !!state,
}); })
const tokenResponse = await fetch( const tokenResponse = await fetch('https://accounts.spotify.com/api/token', {
"https://accounts.spotify.com/api/token", method: 'POST',
{
method: "POST",
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded", 'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString("base64")}`, 'Authorization': `Basic ${Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString('base64')}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
grant_type: "authorization_code", grant_type: 'authorization_code',
code, code,
redirect_uri: SPOTIFY_REDIRECT_URI, redirect_uri: SPOTIFY_REDIRECT_URI,
}), }),
} })
);
if (!tokenResponse.ok) { if (!tokenResponse.ok) {
const errorText = await tokenResponse.text(); const errorText = await tokenResponse.text()
console.error("Token exchange error:", { console.error('Token exchange error:', {
status: tokenResponse.status, status: tokenResponse.status,
statusText: tokenResponse.statusText, statusText: tokenResponse.statusText,
body: errorText, body: errorText,
redirect_uri: SPOTIFY_REDIRECT_URI, redirect_uri: SPOTIFY_REDIRECT_URI,
}); })
const errorParam = encodeURIComponent( const errorParam = encodeURIComponent(`${tokenResponse.status}: ${errorText}`)
`${tokenResponse.status}: ${errorText}`
);
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { headers: {
Location: `${returnPath}?spotify_error=token_exchange_failed&details=${errorParam}`, 'Location': `${returnPath}?spotify_error=token_exchange_failed&details=${errorParam}`,
}, },
}); })
} }
const tokens = await tokenResponse.json(); const tokens = await tokenResponse.json()
console.log("Token exchange successful:", { console.log('Token exchange successful:', {
has_access_token: !!tokens.access_token, has_access_token: !!tokens.access_token,
has_refresh_token: !!tokens.refresh_token, has_refresh_token: !!tokens.refresh_token,
expires_in: tokens.expires_in, expires_in: tokens.expires_in,
}); })
console.log("Decoded return path:", returnPath); console.log('Decoded return path:', returnPath);
const response = new Response(null, { const response = new Response(null, {
status: 302, status: 302,
headers: { headers: {
Location: returnPath + "?spotify_auth=success", 'Location': returnPath + '?spotify_auth=success',
}, },
}); })
const isSecure = import.meta.env.NODE_ENV === "production"; const isSecure = process.env.NODE_ENV === 'production'
const cookieOptions = `HttpOnly; Secure=${isSecure}; SameSite=Strict; Path=/; Max-Age=${tokens.expires_in}`; const cookieOptions = `HttpOnly; Secure=${isSecure}; SameSite=Strict; Path=/; Max-Age=${tokens.expires_in}`
response.headers.append( response.headers.append('Set-Cookie', `spotify_access_token=${tokens.access_token}; ${cookieOptions}`)
"Set-Cookie",
`spotify_access_token=${tokens.access_token}; ${cookieOptions}`
);
if (tokens.refresh_token) { if (tokens.refresh_token) {
const refreshCookieOptions = `HttpOnly; Secure=${isSecure}; SameSite=Strict; Path=/; Max-Age=${60 * 60 * 24 * 30}`; // 30 days const refreshCookieOptions = `HttpOnly; Secure=${isSecure}; SameSite=Strict; Path=/; Max-Age=${60 * 60 * 24 * 30}` // 30 days
response.headers.append( response.headers.append('Set-Cookie', `spotify_refresh_token=${tokens.refresh_token}; ${refreshCookieOptions}`)
"Set-Cookie",
`spotify_refresh_token=${tokens.refresh_token}; ${refreshCookieOptions}`
);
} }
return response; return response
} catch (error) { } catch (error) {
console.error("Spotify callback error:", error); console.error('Spotify callback error:', error)
const url = new URL(request.url); const url = new URL(request.url);
const state = url.searchParams.get("state"); const state = url.searchParams.get('state');
const returnPath = getReturnPath(state); const returnPath = getReturnPath(state);
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { headers: {
Location: returnPath + "?spotify_error=callback_failed", 'Location': returnPath + '?spotify_error=callback_failed',
}, },
}); })
} }
}, },
}, })
},
});

View File

@@ -1,60 +1,59 @@
import { createFileRoute } from "@tanstack/react-router"; import { createServerFileRoute } from '@tanstack/react-start/server'
import { SpotifyWebApiClient } from "@/lib/spotify/client"; import { SpotifyWebApiClient } from '@/lib/spotify/client'
import type { SpotifyPlaybackSnapshot } from "@/lib/spotify/types"; import type { SpotifyPlaybackSnapshot } from '@/lib/spotify/types'
export const Route = createFileRoute("/api/spotify/capture")({ export const ServerRoute = createServerFileRoute('/api/spotify/capture').methods({
server: {
handlers: {
POST: async ({ request }: { request: Request }) => { POST: async ({ request }: { request: Request }) => {
try { try {
const cookies = request.headers.get("Cookie") || ""; // Get access token from cookies
const accessTokenMatch = cookies.match( const cookies = request.headers.get('Cookie') || ''
/spotify_access_token=([^;]+)/ const accessTokenMatch = cookies.match(/spotify_access_token=([^;]+)/)
);
if (!accessTokenMatch) { if (!accessTokenMatch) {
return new Response( return new Response(
JSON.stringify({ error: "No access token found" }), JSON.stringify({ error: 'No access token found' }),
{ {
status: 401, status: 401,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} }
); )
} }
const accessToken = decodeURIComponent(accessTokenMatch[1]); const accessToken = decodeURIComponent(accessTokenMatch[1])
const spotifyClient = new SpotifyWebApiClient(accessToken); const spotifyClient = new SpotifyWebApiClient(accessToken)
const snapshot = await spotifyClient.createPlaybackSnapshot(); // Create a snapshot of the current playback state
const snapshot = await spotifyClient.createPlaybackSnapshot()
if (!snapshot) { if (!snapshot) {
return new Response( return new Response(
JSON.stringify({ error: "No active playback to capture" }), JSON.stringify({ error: 'No active playback to capture' }),
{ {
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} }
); )
} }
return new Response(JSON.stringify({ snapshot }), { return new Response(
JSON.stringify({ snapshot }),
{
status: 200, status: 200,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
}); }
)
} catch (error) { } catch (error) {
console.error("Spotify capture error:", error); console.error('Spotify capture error:', error)
const errorMessage = const errorMessage = error instanceof Error ? error.message : 'Failed to capture playback state'
error instanceof Error
? error.message
: "Failed to capture playback state";
return new Response(JSON.stringify({ error: errorMessage }), { return new Response(
JSON.stringify({ error: errorMessage }),
{
status: 500, status: 500,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
}); }
)
} }
}, },
}, })
},
});

View File

@@ -1,203 +1,202 @@
import { createFileRoute } from "@tanstack/react-router"; import { createServerFileRoute } from '@tanstack/react-start/server'
import { SpotifyWebApiClient } from "@/lib/spotify/client"; import { SpotifyWebApiClient } from '@/lib/spotify/client'
function getAccessTokenFromCookies(request: Request): string | null { function getAccessTokenFromCookies(request: Request): string | null {
const cookieHeader = request.headers.get("cookie"); const cookieHeader = request.headers.get('cookie')
if (!cookieHeader) return null; if (!cookieHeader) return null
const cookies = Object.fromEntries( const cookies = Object.fromEntries(
cookieHeader.split("; ").map((c) => c.split("=")) cookieHeader.split('; ').map(c => c.split('='))
); )
return cookies.spotify_access_token || null; return cookies.spotify_access_token || null
} }
export const Route = createFileRoute("/api/spotify/playback")({ export const ServerRoute = createServerFileRoute('/api/spotify/playback').methods({
server: {
handlers: {
POST: async ({ request }: { request: Request }) => { POST: async ({ request }: { request: Request }) => {
try { try {
const accessToken = getAccessTokenFromCookies(request); const accessToken = getAccessTokenFromCookies(request)
if (!accessToken) { if (!accessToken) {
return new Response( return new Response(
JSON.stringify({ error: "No access token found" }), JSON.stringify({ error: 'No access token found' }),
{ {
status: 401, status: 401,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
const body = await request.json(); const body = await request.json()
const { action, deviceId, volumePercent, trackId, positionMs } = body; const { action, deviceId, volumePercent, trackId, positionMs } = body
const spotifyClient = new SpotifyWebApiClient(accessToken); const spotifyClient = new SpotifyWebApiClient(accessToken)
switch (action) { switch (action) {
case "play": case 'play':
await spotifyClient.play(deviceId); await spotifyClient.play(deviceId)
break; break
case "playTrack": case 'playTrack':
if (!trackId) { if (!trackId) {
return new Response( return new Response(
JSON.stringify({ JSON.stringify({ error: 'trackId is required for playTrack action' }),
error: "trackId is required for playTrack action",
}),
{ {
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
await spotifyClient.playTrack(trackId, deviceId, positionMs); await spotifyClient.playTrack(trackId, deviceId, positionMs)
break; break
case "pause": case 'pause':
await spotifyClient.pause(); await spotifyClient.pause()
break; break
case "next": case 'next':
await spotifyClient.skipToNext(); await spotifyClient.skipToNext()
break; break
case "previous": case 'previous':
await spotifyClient.skipToPrevious(); await spotifyClient.skipToPrevious()
break; break
case "volume": case 'volume':
if (typeof volumePercent !== "number") { if (typeof volumePercent !== 'number') {
return new Response( return new Response(
JSON.stringify({ error: "volumePercent must be a number" }), JSON.stringify({ error: 'volumePercent must be a number' }),
{ {
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
await spotifyClient.setVolume(volumePercent); await spotifyClient.setVolume(volumePercent)
break; break
case "transfer": case 'transfer':
if (!deviceId) { if (!deviceId) {
return new Response( return new Response(
JSON.stringify({ JSON.stringify({ error: 'deviceId is required for transfer action' }),
error: "deviceId is required for transfer action",
}),
{ {
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
await spotifyClient.transferPlayback(deviceId); await spotifyClient.transferPlayback(deviceId)
break; break
default: default:
return new Response(JSON.stringify({ error: "Invalid action" }), { return new Response(
JSON.stringify({ error: 'Invalid action' }),
{
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
}); }
)
} }
return new Response(JSON.stringify({ success: true }), { return new Response(
JSON.stringify({ success: true }),
{
status: 200, status: 200,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
}); }
)
} catch (error) { } catch (error) {
console.error("Playback control error:", error); console.error('Playback control error:', error)
if (error instanceof Error) { if (error instanceof Error) {
if (error.message.includes("NO_ACTIVE_DEVICE")) { if (error.message.includes('NO_ACTIVE_DEVICE')) {
return new Response( return new Response(
JSON.stringify({ JSON.stringify({ error: 'No active device found. Please select a device first.' }),
error:
"No active device found. Please select a device first.",
}),
{ {
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
if (error.message.includes("PREMIUM_REQUIRED")) { if (error.message.includes('PREMIUM_REQUIRED')) {
return new Response( return new Response(
JSON.stringify({ JSON.stringify({ error: 'Spotify Premium is required for playback control.' }),
error: "Spotify Premium is required for playback control.",
}),
{ {
status: 403, status: 403,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
console.error("Full error details:", { console.error('Full error details:', {
message: error.message, message: error.message,
stack: error.stack, stack: error.stack,
name: error.name, name: error.name,
}); })
} }
return new Response( return new Response(
JSON.stringify({ JSON.stringify({ error: 'Playback control failed', details: error instanceof Error ? error.message : 'Unknown error' }),
error: "Playback control failed",
details: error instanceof Error ? error.message : "Unknown error",
}),
{ {
status: 500, status: 500,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
}, },
GET: async ({ request }: { request: Request }) => { GET: async ({ request }: { request: Request }) => {
try { try {
const accessToken = getAccessTokenFromCookies(request); const accessToken = getAccessTokenFromCookies(request)
if (!accessToken) { if (!accessToken) {
return new Response( return new Response(
JSON.stringify({ error: "No access token found" }), JSON.stringify({ error: 'No access token found' }),
{ {
status: 401, status: 401,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
const url = new URL(request.url); const url = new URL(request.url)
const type = url.searchParams.get("type"); const type = url.searchParams.get('type')
const spotifyClient = new SpotifyWebApiClient(accessToken); const spotifyClient = new SpotifyWebApiClient(accessToken)
if (type === "devices") { if (type === 'devices') {
const devices = await spotifyClient.getDevices(); const devices = await spotifyClient.getDevices()
return new Response(JSON.stringify({ devices }), { return new Response(
JSON.stringify({ devices }),
{
status: 200, status: 200,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
}); }
} else if (type === "state") { )
const playbackState = await spotifyClient.getPlaybackState(); } else if (type === 'state') {
return new Response(JSON.stringify({ playbackState }), { const playbackState = await spotifyClient.getPlaybackState()
return new Response(
JSON.stringify({ playbackState }),
{
status: 200, status: 200,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
}); }
)
} else { } else {
const [devices, playbackState] = await Promise.all([ const [devices, playbackState] = await Promise.all([
spotifyClient.getDevices(), spotifyClient.getDevices(),
spotifyClient.getPlaybackState(), spotifyClient.getPlaybackState(),
]); ])
return new Response(JSON.stringify({ devices, playbackState }), { return new Response(
JSON.stringify({ devices, playbackState }),
{
status: 200, status: 200,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
}); }
)
} }
} catch (error) { } catch (error) {
console.error("Get playback data error:", error); console.error('Get playback data error:', error)
return new Response( return new Response(
JSON.stringify({ error: "Failed to get playback data" }), JSON.stringify({ error: 'Failed to get playback data' }),
{ {
status: 500, status: 500,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
}, },
}, })
},
});

View File

@@ -1,71 +1,72 @@
import { createFileRoute } from "@tanstack/react-router"; import { createServerFileRoute } from '@tanstack/react-start/server'
import { SpotifyWebApiClient } from "@/lib/spotify/client"; import { SpotifyWebApiClient } from '@/lib/spotify/client'
import type { SpotifyPlaybackSnapshot } from "@/lib/spotify/types"; import type { SpotifyPlaybackSnapshot } from '@/lib/spotify/types'
export const Route = createFileRoute("/api/spotify/resume")({ export const ServerRoute = createServerFileRoute('/api/spotify/resume').methods({
server: {
handlers: {
POST: async ({ request }: { request: Request }) => { POST: async ({ request }: { request: Request }) => {
try { try {
const cookies = request.headers.get("Cookie") || ""; // Get access token from cookies
const accessTokenMatch = cookies.match( const cookies = request.headers.get('Cookie') || ''
/spotify_access_token=([^;]+)/ const accessTokenMatch = cookies.match(/spotify_access_token=([^;]+)/)
);
if (!accessTokenMatch) { if (!accessTokenMatch) {
return new Response( return new Response(
JSON.stringify({ error: "No access token found" }), JSON.stringify({ error: 'No access token found' }),
{ {
status: 401, status: 401,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} }
); )
} }
const accessToken = decodeURIComponent(accessTokenMatch[1]); const accessToken = decodeURIComponent(accessTokenMatch[1])
const spotifyClient = new SpotifyWebApiClient(accessToken); const spotifyClient = new SpotifyWebApiClient(accessToken)
const body = await request.json(); // Parse the request body to get the snapshot
const { snapshot } = body as { snapshot: SpotifyPlaybackSnapshot }; const body = await request.json()
const { snapshot } = body as { snapshot: SpotifyPlaybackSnapshot }
if (!snapshot) { if (!snapshot) {
return new Response( return new Response(
JSON.stringify({ error: "No snapshot provided" }), JSON.stringify({ error: 'No snapshot provided' }),
{ {
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} }
); )
} }
await spotifyClient.restorePlaybackSnapshot(snapshot); // Restore the playback state from the snapshot
await spotifyClient.restorePlaybackSnapshot(snapshot)
return new Response(JSON.stringify({ success: true }), { return new Response(
JSON.stringify({ success: true }),
{
status: 200, status: 200,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
}); }
)
} catch (error) { } catch (error) {
console.error("Spotify resume error:", error); console.error('Spotify resume error:', error)
let errorMessage = "Failed to resume playback state"; let errorMessage = 'Failed to resume playback state'
// Handle common Spotify Premium requirement error
if (error instanceof Error) { if (error instanceof Error) {
if ( if (error.message.includes('Premium') || error.message.includes('403')) {
error.message.includes("Premium") || errorMessage = 'Spotify Premium required for playback control'
error.message.includes("403")
) {
errorMessage = "Spotify premium required";
} else { } else {
errorMessage = error.message; errorMessage = error.message
} }
} }
return new Response(JSON.stringify({ error: errorMessage }), { return new Response(
JSON.stringify({ error: errorMessage }),
{
status: 500, status: 500,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
}); }
)
} }
}, },
}, })
},
});

View File

@@ -1,87 +1,81 @@
import { createFileRoute } from "@tanstack/react-router"; import { createServerFileRoute } from '@tanstack/react-start/server'
// Function to get Client Credentials access token
async function getClientCredentialsToken(): Promise<string> { async function getClientCredentialsToken(): Promise<string> {
const clientId = import.meta.env.VITE_SPOTIFY_CLIENT_ID; const clientId = process.env.VITE_SPOTIFY_CLIENT_ID
const clientSecret = import.meta.env.SPOTIFY_CLIENT_SECRET; const clientSecret = process.env.SPOTIFY_CLIENT_SECRET
if (!clientId || !clientSecret) { if (!clientId || !clientSecret) {
throw new Error("Missing Spotify client credentials"); throw new Error('Missing Spotify client credentials')
} }
const response = await fetch("https://accounts.spotify.com/api/token", { const response = await fetch('https://accounts.spotify.com/api/token', {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded", 'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`, 'Authorization': `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`,
}, },
body: "grant_type=client_credentials", body: 'grant_type=client_credentials',
}); })
if (!response.ok) { if (!response.ok) {
throw new Error("Failed to get Spotify access token"); throw new Error('Failed to get Spotify access token')
} }
const data = await response.json(); const data = await response.json()
return data.access_token; return data.access_token
} }
export const Route = createFileRoute("/api/spotify/search")({ export const ServerRoute = createServerFileRoute('/api/spotify/search').methods({
server: {
handlers: {
GET: async ({ request }: { request: Request }) => { GET: async ({ request }: { request: Request }) => {
try { try {
const url = new URL(request.url); const url = new URL(request.url)
const query = url.searchParams.get("q"); const query = url.searchParams.get('q')
if (!query) { if (!query) {
return new Response( return new Response(
JSON.stringify({ error: "Query parameter q is required" }), JSON.stringify({ error: 'Query parameter q is required' }),
{ {
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
// Get client credentials access token // Get client credentials access token
const accessToken = await getClientCredentialsToken(); const accessToken = await getClientCredentialsToken()
// Search using Spotify API directly // Search using Spotify API directly
const searchUrl = `https://api.spotify.com/v1/search?q=${encodeURIComponent(query)}&type=track&limit=20`; const searchUrl = `https://api.spotify.com/v1/search?q=${encodeURIComponent(query)}&type=track&limit=20`
const searchResponse = await fetch(searchUrl, { const searchResponse = await fetch(searchUrl, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, 'Authorization': `Bearer ${accessToken}`,
}, },
}); })
if (!searchResponse.ok) { if (!searchResponse.ok) {
throw new Error("Spotify search request failed"); throw new Error('Spotify search request failed')
} }
const searchResult = await searchResponse.json(); const searchResult = await searchResponse.json()
return new Response( return new Response(
JSON.stringify({ tracks: searchResult.tracks.items }), JSON.stringify({ tracks: searchResult.tracks.items }),
{ {
status: 200, status: 200,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} catch (error) { } catch (error) {
console.error("Search error:", error); console.error('Search error:', error)
return new Response( return new Response(
JSON.stringify({ JSON.stringify({ error: 'Search failed', details: error instanceof Error ? error.message : 'Unknown error' }),
error: "Search failed",
details: error instanceof Error ? error.message : "Unknown error",
}),
{ {
status: 500, status: 500,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
}, },
}, })
},
});

View File

@@ -1,57 +1,49 @@
import { createFileRoute } from "@tanstack/react-router"; import { createServerFileRoute } from '@tanstack/react-start/server'
const SPOTIFY_CLIENT_ID = import.meta.env.VITE_SPOTIFY_CLIENT_ID!; const SPOTIFY_CLIENT_ID = import.meta.env.VITE_SPOTIFY_CLIENT_ID!
const SPOTIFY_CLIENT_SECRET = import.meta.env.SPOTIFY_CLIENT_SECRET!; const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET!
export const Route = createFileRoute("/api/spotify/token")({ export const ServerRoute = createServerFileRoute('/api/spotify/token').methods({
server: {
handlers: {
POST: async ({ request }: { request: Request }) => { POST: async ({ request }: { request: Request }) => {
try { try {
const body = await request.json(); const body = await request.json()
const { refresh_token } = body; const { refresh_token } = body
if (!refresh_token) { if (!refresh_token) {
return new Response( return new Response(
JSON.stringify({ error: "refresh_token is required" }), JSON.stringify({ error: 'refresh_token is required' }),
{ {
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
const tokenResponse = await fetch( const tokenResponse = await fetch('https://accounts.spotify.com/api/token', {
"https://accounts.spotify.com/api/token", method: 'POST',
{
method: "POST",
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded", 'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString("base64")}`, 'Authorization': `Basic ${Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString('base64')}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
grant_type: "refresh_token", grant_type: 'refresh_token',
refresh_token, refresh_token,
}), }),
} })
);
if (!tokenResponse.ok) { if (!tokenResponse.ok) {
const error = await tokenResponse.json(); const error = await tokenResponse.json()
console.error("Token refresh error:", error); console.error('Token refresh error:', error)
return new Response( return new Response(
JSON.stringify({ JSON.stringify({ error: 'Failed to refresh token', details: error }),
error: "Failed to refresh token",
details: error,
}),
{ {
status: tokenResponse.status, status: tokenResponse.status,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
const tokens = await tokenResponse.json(); const tokens = await tokenResponse.json()
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
@@ -62,46 +54,50 @@ export const Route = createFileRoute("/api/spotify/token")({
}), }),
{ {
status: 200, status: 200,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} catch (error) { } catch (error) {
console.error("Token refresh endpoint error:", error); console.error('Token refresh endpoint error:', error)
return new Response( return new Response(
JSON.stringify({ error: "Internal server error" }), JSON.stringify({ error: 'Internal server error' }),
{ {
status: 500, status: 500,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
}, },
// GET endpoint to retrieve current tokens from cookies
GET: async ({ request }: { request: Request }) => { GET: async ({ request }: { request: Request }) => {
try { try {
const cookieHeader = request.headers.get("cookie"); const cookieHeader = request.headers.get('cookie')
if (!cookieHeader) { if (!cookieHeader) {
return new Response(JSON.stringify({ error: "No cookies found" }), { return new Response(
JSON.stringify({ error: 'No cookies found' }),
{
status: 401, status: 401,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
}); }
)
} }
const cookies = Object.fromEntries( const cookies = Object.fromEntries(
cookieHeader.split("; ").map((c: string) => c.split("=")) cookieHeader.split('; ').map((c: string) => c.split('='))
); )
const accessToken = cookies.spotify_access_token; const accessToken = cookies.spotify_access_token
const refreshToken = cookies.spotify_refresh_token; const refreshToken = cookies.spotify_refresh_token
if (!accessToken && !refreshToken) { if (!accessToken && !refreshToken) {
return new Response( return new Response(
JSON.stringify({ error: "No Spotify tokens found" }), JSON.stringify({ error: 'No Spotify tokens found' }),
{ {
status: 401, status: 401,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
return new Response( return new Response(
@@ -112,20 +108,18 @@ export const Route = createFileRoute("/api/spotify/token")({
}), }),
{ {
status: 200, status: 200,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} catch (error) { } catch (error) {
console.error("Get tokens endpoint error:", error); console.error('Get tokens endpoint error:', error)
return new Response( return new Response(
JSON.stringify({ error: "Internal server error" }), JSON.stringify({ error: 'Internal server error' }),
{ {
status: 500, status: 500,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
} }
); )
} }
}, },
}, })
},
});

View File

@@ -1,148 +1,117 @@
import { createFileRoute } from "@tanstack/react-router"; import { createServerFileRoute } from '@tanstack/react-start/server';
import { superTokensRequestMiddleware } from "@/utils/supertokens"; import { superTokensRequestMiddleware } from '@/utils/supertokens';
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from '@/lib/pocketbase/client';
import { logger } from "@/lib/logger"; import { logger } from '@/lib/logger';
import { z } from "zod"; import { z } from 'zod';
const uploadSchema = z.object({ const uploadSchema = z.object({
teamId: z.string().min(1, "Team ID is required"), teamId: z.string().min(1, 'Team ID is required'),
}); });
export const Route = createFileRoute("/api/teams/upload-logo")({ export const ServerRoute = createServerFileRoute('/api/teams/upload-logo')
server: { .middleware([superTokensRequestMiddleware])
middleware: [superTokensRequestMiddleware], .methods({
handlers: {
POST: async ({ request, context }) => { POST: async ({ request, context }) => {
try { try {
const userId = context.userAuthId; const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin"); const isAdmin = context.roles.includes("Admin");
if (!userId) return new Response("Unauthenticated", { status: 401 }); if (!userId) return new Response('Unauthenticated', { status: 401 });
const formData = await request.formData(); const formData = await request.formData();
const teamId = formData.get("teamId") as string; const teamId = formData.get('teamId') as string;
const logoFile = formData.get("logo") as File; const logoFile = formData.get('logo') as File;
const validationResult = uploadSchema.safeParse({ teamId }); const validationResult = uploadSchema.safeParse({ teamId });
if (!validationResult.success) { if (!validationResult.success) {
return new Response( return new Response(JSON.stringify({
JSON.stringify({ error: 'Invalid input',
error: "Invalid input", details: validationResult.error.issues
details: validationResult.error.issues, }), {
}),
{
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} }
if (!logoFile || logoFile.size === 0) { if (!logoFile || logoFile.size === 0) {
return new Response( return new Response(JSON.stringify({
JSON.stringify({ error: 'Logo file is required'
error: "Logo file is required", }), {
}),
{
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} }
const allowedTypes = [ const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
"image/jpeg",
"image/jpg",
"image/png",
"image/gif",
];
if (!allowedTypes.includes(logoFile.type)) { if (!allowedTypes.includes(logoFile.type)) {
return new Response( return new Response(JSON.stringify({
JSON.stringify({ error: 'Invalid file type. Only JPEG, PNG and GIF are allowed.'
error: "Invalid file type. Only JPEG, PNG and GIF are allowed.", }), {
}),
{
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} }
const maxSize = 10 * 1024 * 1024; const maxSize = 10 * 1024 * 1024;
if (logoFile.size > maxSize) { if (logoFile.size > maxSize) {
return new Response( return new Response(JSON.stringify({
JSON.stringify({ error: 'File too large. Maximum size is 10MB.'
error: "File too large. Maximum size is 10MB.", }), {
}),
{
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} }
const team = await pbAdmin.getTeam(teamId); const team = await pbAdmin.getTeam(teamId);
if (!team) { if (!team) {
return new Response( return new Response(JSON.stringify({
JSON.stringify({ error: 'Team not found'
error: "Team not found", }), {
}),
{
status: 404, status: 404,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} }
const user = await pbAdmin.getPlayerByAuthId(userId); const user = await pbAdmin.getPlayerByAuthId(context.userAuthId)
if (!team.players.map((p) => p.id).includes(user?.id!) && !isAdmin) if (!team.players.map(p => p.id).includes(user!.id) && !isAdmin)
return new Response("Unauthorized", { status: 403 }); return new Response('Unauthorized', { status: 403 });
logger.info("Uploading team logo", { logger.info('Uploading team logo', {
teamId, teamId,
fileName: logoFile.name, fileName: logoFile.name,
fileSize: logoFile.size, fileSize: logoFile.size,
userId, userId
}); });
const pbFormData = new FormData(); const pbFormData = new FormData();
pbFormData.append("logo", logoFile); pbFormData.append('logo', logoFile);
const updatedTeam = await pbAdmin.updateTeam( const updatedTeam= await pbAdmin.updateTeam(teamId, pbFormData as any);
teamId,
pbFormData as any
);
logger.info("Team logo uploaded successfully", { logger.info('Team logo uploaded successfully', {
teamId, teamId,
logo: updatedTeam.logo, logo: updatedTeam.logo
}); });
return new Response( return new Response(JSON.stringify({
JSON.stringify({
success: true, success: true,
team: updatedTeam, team: updatedTeam,
message: "Logo uploaded successfully", message: 'Logo uploaded successfully'
}), }), {
{
status: 200, status: 200,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} catch (error: any) {
logger.error("Error uploading team logo:", error);
return new Response( } catch (error: any) {
JSON.stringify({ logger.error('Error uploading team logo:', error);
error: "Failed to upload logo",
message: error.message || "Unknown error occurred", return new Response(JSON.stringify({
}), error: 'Failed to upload logo',
{ message: error.message || 'Unknown error occurred'
}), {
status: 500, status: 500,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
});
} }
);
} }
}, });
},
},
});

View File

@@ -1,145 +1,115 @@
import { createFileRoute } from "@tanstack/react-router"; import { createServerFileRoute } from '@tanstack/react-start/server';
import { superTokensRequestMiddleware } from "@/utils/supertokens"; import { superTokensRequestMiddleware } from '@/utils/supertokens';
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from '@/lib/pocketbase/client';
import { logger } from "@/lib/logger"; import { logger } from '@/lib/logger';
import { z } from "zod"; import { z } from 'zod';
const uploadSchema = z.object({ const uploadSchema = z.object({
tournamentId: z.string().min(1, "Tournament ID is required"), tournamentId: z.string().min(1, 'Tournament ID is required'),
}); });
export const Route = createFileRoute("/api/tournaments/upload-logo")({ export const ServerRoute = createServerFileRoute('/api/tournaments/upload-logo')
server: { .middleware([superTokensRequestMiddleware])
middleware: [superTokensRequestMiddleware], .methods({
handlers: {
POST: async ({ request, context }) => { POST: async ({ request, context }) => {
try { try {
const userId = context.userAuthId; const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin"); const isAdmin = context.roles.includes("Admin");
if (!userId) return new Response("Unauthenticated", { status: 401 }); if (!userId) return new Response('Unauthenticated', { status: 401 });
if (!isAdmin) return new Response("Unauthorized", { status: 403 }); if (!isAdmin) return new Response('Unauthorized', { status: 403 });
const formData = await request.formData(); const formData = await request.formData();
const tournamentId = formData.get("tournamentId") as string; const tournamentId = formData.get('tournamentId') as string;
const logoFile = formData.get("logo") as File; const logoFile = formData.get('logo') as File;
const validationResult = uploadSchema.safeParse({ tournamentId }); const validationResult = uploadSchema.safeParse({ tournamentId });
if (!validationResult.success) { if (!validationResult.success) {
return new Response( return new Response(JSON.stringify({
JSON.stringify({ error: 'Invalid input',
error: "Invalid input", details: validationResult.error.issues
details: validationResult.error.issues, }), {
}),
{
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} }
if (!logoFile || logoFile.size === 0) { if (!logoFile || logoFile.size === 0) {
return new Response( return new Response(JSON.stringify({
JSON.stringify({ error: 'Logo file is required'
error: "Logo file is required", }), {
}),
{
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} }
const allowedTypes = [ const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
"image/jpeg",
"image/jpg",
"image/png",
"image/gif",
];
if (!allowedTypes.includes(logoFile.type)) { if (!allowedTypes.includes(logoFile.type)) {
return new Response( return new Response(JSON.stringify({
JSON.stringify({ error: 'Invalid file type. Only JPEG, PNG and GIF are allowed.'
error: "Invalid file type. Only JPEG, PNG and GIF are allowed.", }), {
}),
{
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} }
const maxSize = 10 * 1024 * 1024; const maxSize = 10 * 1024 * 1024;
if (logoFile.size > maxSize) { if (logoFile.size > maxSize) {
return new Response( return new Response(JSON.stringify({
JSON.stringify({ error: 'File too large. Maximum size is 10MB.'
error: "File too large. Maximum size is 10MB.", }), {
}),
{
status: 400, status: 400,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} }
const tournament = await pbAdmin.getTournament(tournamentId); const tournament = await pbAdmin.getTournament(tournamentId);
if (!tournament) { if (!tournament) {
return new Response( return new Response(JSON.stringify({
JSON.stringify({ error: 'Tournament not found'
error: "Tournament not found", }), {
}),
{
status: 404, status: 404,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} }
logger.info("Uploading tournament logo", {
logger.info('Uploading tournament logo', {
tournamentId, tournamentId,
fileName: logoFile.name, fileName: logoFile.name,
fileSize: logoFile.size, fileSize: logoFile.size,
userId, userId
}); });
const pbFormData = new FormData(); const pbFormData = new FormData();
pbFormData.append("logo", logoFile); pbFormData.append('logo', logoFile);
const updatedTournament = await pbAdmin.updateTournament( const updatedTournament = await pbAdmin.updateTournament(tournamentId, pbFormData as any);
tournamentId,
pbFormData as any
);
logger.info("Tournament logo uploaded successfully", { logger.info('Tournament logo uploaded successfully', {
tournamentId, tournamentId,
logo: updatedTournament.logo, logo: updatedTournament.logo
}); });
return new Response( return new Response(JSON.stringify({
JSON.stringify({
success: true, success: true,
tournament: updatedTournament, tournament: updatedTournament,
message: "Logo uploaded successfully", message: 'Logo uploaded successfully'
}), }), {
{
status: 200, status: 200,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
} });
);
} catch (error: any) {
logger.error("Error uploading tournament logo:", error);
return new Response( } catch (error: any) {
JSON.stringify({ logger.error('Error uploading tournament logo:', error);
error: "Failed to upload logo",
message: error.message || "Unknown error occurred", return new Response(JSON.stringify({
}), error: 'Failed to upload logo',
{ message: error.message || 'Unknown error occurred'
}), {
status: 500, status: 500,
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' }
});
} }
);
} }
}, });
},
},
});

View File

@@ -438,7 +438,6 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
activeDevice, activeDevice,
isLoading, isLoading,
error, error,
// Capture/Resume state
capturedState, capturedState,
isCaptureLoading, isCaptureLoading,
isResumeLoading, isResumeLoading,
@@ -453,11 +452,9 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
getDevices, getDevices,
setActiveDevice, setActiveDevice,
refreshPlaybackState, refreshPlaybackState,
// Capture/Resume methods
capturePlaybackState, capturePlaybackState,
resumePlaybackState, resumePlaybackState,
clearCapturedState, clearCapturedState,
// Search
searchTracks, searchTracks,
}; };

View File

@@ -9,7 +9,7 @@ import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
const logger = new Logger("Bracket Generation"); const logger = new Logger("Bracket Generation");
export const previewBracket = createServerFn() export const previewBracket = createServerFn()
.inputValidator(z.number()) .validator(z.number())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data: teams }) => .handler(async ({ data: teams }) =>
toServerResult(async () => { toServerResult(async () => {

View File

@@ -16,7 +16,7 @@ const orderedTeamsSchema = z.object({
}); });
export const generateTournamentBracket = createServerFn() export const generateTournamentBracket = createServerFn()
.inputValidator(orderedTeamsSchema) .validator(orderedTeamsSchema)
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ data: { tournamentId, orderedTeamIds } }) => .handler(async ({ data: { tournamentId, orderedTeamIds } }) =>
toServerResult(async () => { toServerResult(async () => {
@@ -137,7 +137,7 @@ export const generateTournamentBracket = createServerFn()
); );
export const startMatch = createServerFn() export const startMatch = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult(async () => { toServerResult(async () => {
@@ -170,7 +170,7 @@ const endMatchSchema = z.object({
ot_count: z.number(), ot_count: z.number(),
}); });
export const endMatch = createServerFn() export const endMatch = createServerFn()
.inputValidator(endMatchSchema) .validator(endMatchSchema)
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ data: { matchId, home_cups, away_cups, ot_count } }) => .handler(async ({ data: { matchId, home_cups, away_cups, ot_count } }) =>
toServerResult(async () => { toServerResult(async () => {
@@ -252,7 +252,7 @@ const toggleReactionSchema = z.object({
}); });
export const toggleMatchReaction = createServerFn() export const toggleMatchReaction = createServerFn()
.inputValidator(toggleReactionSchema) .validator(toggleReactionSchema)
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data: { matchId, emoji }, context }) => .handler(async ({ data: { matchId, emoji }, context }) =>
toServerResult(async () => { toServerResult(async () => {
@@ -312,7 +312,7 @@ export interface Reaction {
players: PlayerInfo[]; players: PlayerInfo[];
} }
export const getMatchReactions = createServerFn() export const getMatchReactions = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data: matchId, context }) => .handler(async ({ data: matchId, context }) =>
toServerResult(async () => { toServerResult(async () => {

View File

@@ -60,6 +60,7 @@ export const useMe = () => {
const errorData = error?.response?.data; const errorData = error?.response?.data;
if (errorData?.error === "SESSION_REFRESH_REQUIRED") { if (errorData?.error === "SESSION_REFRESH_REQUIRED") {
const currentUrl = window.location.pathname + window.location.search; const currentUrl = window.location.pathname + window.location.search;
console.log('redirecting 3')
window.location.href = `/refresh-session?redirect=${encodeURIComponent(currentUrl)}`; window.location.href = `/refresh-session?redirect=${encodeURIComponent(currentUrl)}`;
return false; return false;
} }

View File

@@ -5,13 +5,13 @@ import { Match } from "@/features/matches/types";
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from "@/lib/pocketbase/client";
import { z } from "zod"; import { z } from "zod";
import { logger } from "."; import { logger } from ".";
import { getRequest } from "@tanstack/react-start/server"; import { getWebRequest } from "@tanstack/react-start/server";
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result"; import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
export const fetchMe = createServerFn() export const fetchMe = createServerFn()
.handler(async () => .handler(async () =>
toServerResult(async () => { toServerResult(async () => {
const request = getRequest(); const request = getWebRequest();
try { try {
const context = await getSessionContext(request); const context = await getSessionContext(request);
@@ -25,7 +25,7 @@ export const fetchMe = createServerFn()
phone: context.phone phone: context.phone
}; };
} catch (error: any) { } catch (error: any) {
// logger.info("FetchMe: Session error", error) logger.info("FetchMe: Session error", error)
if (error?.response?.status === 401) { if (error?.response?.status === 401) {
const errorData = error?.response?.data; const errorData = error?.response?.data;
if (errorData?.error === "SESSION_REFRESH_REQUIRED") { if (errorData?.error === "SESSION_REFRESH_REQUIRED") {
@@ -38,14 +38,14 @@ export const fetchMe = createServerFn()
); );
export const getPlayer = createServerFn() export const getPlayer = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<Player>(async () => await pbAdmin.getPlayer(data)) toServerResult<Player>(async () => await pbAdmin.getPlayer(data))
); );
export const updatePlayer = createServerFn() export const updatePlayer = createServerFn()
.inputValidator(playerUpdateSchema) .validator(playerUpdateSchema)
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ context, data }) => .handler(async ({ context, data }) =>
toServerResult(async () => { toServerResult(async () => {
@@ -72,7 +72,7 @@ export const updatePlayer = createServerFn()
); );
export const createPlayer = createServerFn() export const createPlayer = createServerFn()
.inputValidator(playerInputSchema) .validator(playerInputSchema)
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ context, data }) => .handler(async ({ context, data }) =>
toServerResult(async () => { toServerResult(async () => {
@@ -97,7 +97,7 @@ export const createPlayer = createServerFn()
); );
export const associatePlayer = createServerFn() export const associatePlayer = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ context, data }) => .handler(async ({ context, data }) =>
toServerResult(async () => { toServerResult(async () => {
@@ -129,7 +129,7 @@ export const getUnassociatedPlayers = createServerFn()
); );
export const getPlayerStats = createServerFn() export const getPlayerStats = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<PlayerStats>(async () => await pbAdmin.getPlayerStats(data)) toServerResult<PlayerStats>(async () => await pbAdmin.getPlayerStats(data))
@@ -142,14 +142,14 @@ export const getAllPlayerStats = createServerFn()
); );
export const getPlayerMatches = createServerFn() export const getPlayerMatches = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<Match[]>(async () => await pbAdmin.getPlayerMatches(data)) toServerResult<Match[]>(async () => await pbAdmin.getPlayerMatches(data))
); );
export const getUnenrolledPlayers = createServerFn() export const getUnenrolledPlayers = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data: tournamentId }) => .handler(async ({ data: tournamentId }) =>
toServerResult(async () => await pbAdmin.getUnenrolledPlayers(tournamentId)) toServerResult(async () => await pbAdmin.getUnenrolledPlayers(tournamentId))

View File

@@ -15,21 +15,21 @@ export const listTeamInfos = createServerFn()
); );
export const getTeam = createServerFn() export const getTeam = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data: teamId }) => .handler(async ({ data: teamId }) =>
toServerResult(() => pbAdmin.getTeam(teamId)) toServerResult(() => pbAdmin.getTeam(teamId))
); );
export const getTeamInfo = createServerFn() export const getTeamInfo = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data: teamId }) => .handler(async ({ data: teamId }) =>
toServerResult(() => pbAdmin.getTeamInfo(teamId)) toServerResult(() => pbAdmin.getTeamInfo(teamId))
); );
export const createTeam = createServerFn() export const createTeam = createServerFn()
.inputValidator(teamInputSchema) .validator(teamInputSchema)
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data, context }) => .handler(async ({ data, context }) =>
toServerResult(async () => { toServerResult(async () => {
@@ -46,7 +46,7 @@ export const createTeam = createServerFn()
); );
export const updateTeam = createServerFn() export const updateTeam = createServerFn()
.inputValidator(z.object({ .validator(z.object({
id: z.string(), id: z.string(),
updates: teamUpdateSchema updates: teamUpdateSchema
})) }))
@@ -72,14 +72,14 @@ export const updateTeam = createServerFn()
); );
export const getTeamStats = createServerFn() export const getTeamStats = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data: teamId }) => .handler(async ({ data: teamId }) =>
toServerResult(() => pbAdmin.getTeamStats(teamId)) toServerResult(() => pbAdmin.getTeamStats(teamId))
); );
export const getTeamMatches = createServerFn() export const getTeamMatches = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<Match[]>(async () => await pbAdmin.getTeamMatches(data)) toServerResult<Match[]>(async () => await pbAdmin.getTeamMatches(data))

View File

@@ -13,14 +13,14 @@ export const listTournaments = createServerFn()
); );
export const createTournament = createServerFn() export const createTournament = createServerFn()
.inputValidator(tournamentInputSchema) .validator(tournamentInputSchema)
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult(() => pbAdmin.createTournament(data)) toServerResult(() => pbAdmin.createTournament(data))
); );
export const updateTournament = createServerFn() export const updateTournament = createServerFn()
.inputValidator(z.object({ .validator(z.object({
id: z.string(), id: z.string(),
updates: tournamentInputSchema.partial() updates: tournamentInputSchema.partial()
})) }))
@@ -30,7 +30,7 @@ export const updateTournament = createServerFn()
); );
export const getTournament = createServerFn() export const getTournament = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data: tournamentId, context }) => { .handler(async ({ data: tournamentId, context }) => {
const isAdmin = context.roles.includes("Admin"); const isAdmin = context.roles.includes("Admin");
@@ -44,7 +44,7 @@ export const getCurrentTournament = createServerFn()
); );
export const enrollTeam = createServerFn() export const enrollTeam = createServerFn()
.inputValidator(z.object({ .validator(z.object({
tournamentId: z.string(), tournamentId: z.string(),
teamId: z.string() teamId: z.string()
})) }))
@@ -70,7 +70,7 @@ export const enrollTeam = createServerFn()
); );
export const unenrollTeam = createServerFn() export const unenrollTeam = createServerFn()
.inputValidator(z.object({ .validator(z.object({
tournamentId: z.string(), tournamentId: z.string(),
teamId: z.string() teamId: z.string()
})) }))
@@ -80,21 +80,21 @@ export const unenrollTeam = createServerFn()
); );
export const getUnenrolledTeams = createServerFn() export const getUnenrolledTeams = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ data: tournamentId }) => .handler(async ({ data: tournamentId }) =>
toServerResult(() => pbAdmin.getUnenrolledTeams(tournamentId)) toServerResult(() => pbAdmin.getUnenrolledTeams(tournamentId))
); );
export const getFreeAgents = createServerFn() export const getFreeAgents = createServerFn()
.inputValidator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ data: tournamentId }) => .handler(async ({ data: tournamentId }) =>
toServerResult(() => pbAdmin.getFreeAgents(tournamentId)) toServerResult(() => pbAdmin.getFreeAgents(tournamentId))
); );
export const enrollFreeAgent = createServerFn() export const enrollFreeAgent = createServerFn()
.inputValidator(z.object({ phone: z.string(), tournamentId: z.string() })) .validator(z.object({ phone: z.string(), tournamentId: z.string() }))
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ context, data }) => .handler(async ({ context, data }) =>
toServerResult(async () => { toServerResult(async () => {
@@ -108,7 +108,7 @@ export const enrollFreeAgent = createServerFn()
); );
export const unenrollFreeAgent = createServerFn() export const unenrollFreeAgent = createServerFn()
.inputValidator(z.object({ tournamentId: z.string() })) .validator(z.object({ tournamentId: z.string() }))
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ context, data }) => .handler(async ({ context, data }) =>
toServerResult(async () => { toServerResult(async () => {

View File

@@ -37,7 +37,6 @@ export function useServerEvents() {
const timeoutRef = useRef<NodeJS.Timeout | null>(null); const timeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => { useEffect(() => {
if (typeof window === 'undefined') return;
if (!user?.id) return; if (!user?.id) return;
shouldConnectRef.current = true; shouldConnectRef.current = true;

View File

@@ -46,7 +46,7 @@ class Logger {
constructor(context?: string, options: LoggerOptions = {}) { constructor(context?: string, options: LoggerOptions = {}) {
this.context = context; this.context = context;
this.options = { this.options = {
enabled: import.meta.env.NODE_ENV !== "production", enabled: process.env.NODE_ENV !== "production",
showTimestamp: true, showTimestamp: true,
collapsed: true, collapsed: true,
colors: true, colors: true,

View File

@@ -4,8 +4,6 @@ import { createTournamentsService } from "./services/tournaments";
import { createTeamsService } from "./services/teams"; import { createTeamsService } from "./services/teams";
import { createMatchesService } from "./services/matches"; import { createMatchesService } from "./services/matches";
import { createReactionsService } from "./services/reactions"; import { createReactionsService } from "./services/reactions";
import dotenv from 'dotenv';
dotenv.config();
class PocketBaseAdminClient { class PocketBaseAdminClient {
private pb: PocketBase; private pb: PocketBase;

View File

@@ -6,7 +6,6 @@ import UserRoles from "supertokens-node/recipe/userroles";
import { appInfo } from "./config"; import { appInfo } from "./config";
import PasswordlessDevelopmentMode from "./recipes/passwordless-development-mode"; import PasswordlessDevelopmentMode from "./recipes/passwordless-development-mode";
import { logger } from "./"; import { logger } from "./";
import passwordlessTwilioVerify from "./recipes/passwordless-twilio-verify";
export const backendConfig = (): TypeInput => { export const backendConfig = (): TypeInput => {
return { return {
@@ -20,10 +19,10 @@ export const backendConfig = (): TypeInput => {
PasswordlessDevelopmentMode.init(), PasswordlessDevelopmentMode.init(),
Session.init({ Session.init({
cookieSameSite: "lax", cookieSameSite: "lax",
cookieSecure: import.meta.env.NODE_ENV === "production", cookieSecure: process.env.NODE_ENV === "production",
cookieDomain: cookieDomain:
import.meta.env.NODE_ENV === "production" ? ".example.com" : undefined, process.env.NODE_ENV === "production" ? ".example.com" : undefined,
antiCsrf: import.meta.env.NODE_ENV === "production" ? "VIA_TOKEN" : "NONE", antiCsrf: process.env.NODE_ENV === "production" ? "VIA_TOKEN" : "NONE",
// Debug only // Debug only
exposeAccessTokenToFrontendInCookieBasedAuth: true, exposeAccessTokenToFrontendInCookieBasedAuth: true,
@@ -31,13 +30,13 @@ export const backendConfig = (): TypeInput => {
Dashboard.init(), Dashboard.init(),
UserRoles.init(), UserRoles.init(),
], ],
telemetry: import.meta.env.NODE_ENV !== "production", telemetry: process.env.NODE_ENV !== "production",
}; };
}; };
let initialized = false; let initialized = false;
export function ensureSuperTokensBackend() { export function ensureSuperTokensBackend() {
if (!initialized && typeof window === 'undefined') { if (!initialized) {
SuperTokens.init(backendConfig()); SuperTokens.init(backendConfig());
initialized = true; initialized = true;
logger.simple("Backend initialized"); logger.simple("Backend initialized");

View File

@@ -44,6 +44,7 @@ export function useServerMutation<TData, TVariables = unknown>(
if (errorData?.error === "SESSION_REFRESH_REQUIRED") { if (errorData?.error === "SESSION_REFRESH_REQUIRED") {
const currentUrl = window.location.pathname + window.location.search; const currentUrl = window.location.pathname + window.location.search;
window.location.href = `/refresh-session?redirect=${encodeURIComponent(currentUrl)}`; window.location.href = `/refresh-session?redirect=${encodeURIComponent(currentUrl)}`;
console.log('redirecting 2')
throw new Error("SESSION_REFRESH_REQUIRED"); throw new Error("SESSION_REFRESH_REQUIRED");
} }
} catch (parseError) {} } catch (parseError) {}

View File

@@ -1,23 +1,13 @@
import type { Twilio } from "twilio"; import twilio from "twilio";
const accountSid = process.env.TWILIO_ACCOUNT_SID!; const accountSid = process.env.TWILIO_ACCOUNT_SID!;
const authToken = process.env.TWILIO_AUTH_TOKEN!; const authToken = process.env.TWILIO_AUTH_TOKEN!;
const serviceSid = process.env.TWILIO_SERVICE_SID!; const serviceSid = process.env.TWILIO_SERVICE_SID!;
let client: Twilio; const client = twilio(accountSid, authToken);
function getTwilioClient() {
if (!client) {
const twilio = require("twilio");
client = twilio(accountSid, authToken);
}
return client;
}
export async function sendVerifyCode(phoneNumber: string, code: string) { export async function sendVerifyCode(phoneNumber: string, code: string) {
const twilioClient = getTwilioClient(); const verification = await client.verify.v2
const verification = await twilioClient!.verify.v2
.services(serviceSid) .services(serviceSid)
.verifications.create({ .verifications.create({
channel: "sms", channel: "sms",
@@ -33,9 +23,7 @@ export async function sendVerifyCode(phoneNumber: string, code: string) {
} }
export async function updateVerify(sid: string) { export async function updateVerify(sid: string) {
const twilioClient = getTwilioClient(); const verification = await client.verify.v2
const verification = await twilioClient!.verify.v2
.services(serviceSid) .services(serviceSid)
.verifications(sid) .verifications(sid)
.update({ status: "approved" }); .update({ status: "approved" });

View File

@@ -1,9 +1,9 @@
import { import {
createMiddleware, createMiddleware,
createServerFn, createServerFn,
createServerOnlyFn, ServerFnResponseType,
} from "@tanstack/react-start"; } from "@tanstack/react-start";
import { getRequest, setResponseHeader } from "@tanstack/react-start/server"; import { getWebRequest } from "@tanstack/react-start/server";
import { redirect as redirect } from "@tanstack/react-router"; import { redirect as redirect } from "@tanstack/react-router";
import UserRoles from "supertokens-node/recipe/userroles"; import UserRoles from "supertokens-node/recipe/userroles";
import UserMetadata from "supertokens-node/recipe/usermetadata"; import UserMetadata from "supertokens-node/recipe/usermetadata";
@@ -14,24 +14,9 @@ import { refreshSession } from "supertokens-node/recipe/session";
const logger = new Logger("Middleware"); const logger = new Logger("Middleware");
function createNodeRequest(request: Request) { export const verifySuperTokensSession = async (
const cookies = request.headers.get('cookie') || ''; request: Request,
response?: ServerFnResponseType
return {
getHeaderValue: (key: string) => {
return request.headers.get(key) || undefined;
},
getCookieValue: (key: string) => {
const match = cookies.match(new RegExp(`(^| )${key}=([^;]+)`));
return match ? match[2] : undefined;
},
getMethod: () => request.method,
getOriginalURL: () => request.url,
};
}
const verifySuperTokensSession = async (
request: Request
) => { ) => {
let session = await getSessionForStart(request, { sessionRequired: false }); let session = await getSessionForStart(request, { sessionRequired: false });
@@ -39,21 +24,13 @@ const verifySuperTokensSession = async (
logger.info("Session needs refresh"); logger.info("Session needs refresh");
try { try {
if (response) {
const nodeRequest = createNodeRequest(request); const refreshedSession = await refreshSession(request, response);
const nodeResponse = {
setHeader: (key: string, value: string) => {
setResponseHeader(key, value);
},
setCookie: (cookie: string) => {
setResponseHeader('Set-Cookie', cookie);
}
};
const refreshedSession = await refreshSession(nodeRequest, nodeResponse);
if (refreshedSession) { if (refreshedSession) {
session = await getSessionForStart(request, { sessionRequired: false }); session = await getSessionForStart(request, { sessionRequired: false });
} }
}
if (session?.needsRefresh) { if (session?.needsRefresh) {
return { context: { session: { tryRefresh: true } } }; return { context: { session: { tryRefresh: true } } };
} }
@@ -92,7 +69,7 @@ const verifySuperTokensSession = async (
}; };
}; };
export const getSessionContext = createServerOnlyFn(async (request: Request, options?: { isServerFunction?: boolean }) => { export const getSessionContext = async (request: Request, options?: { isServerFunction?: boolean }) => {
const session = await verifySuperTokensSession(request); const session = await verifySuperTokensSession(request);
if (session.context.session?.tryRefresh) { if (session.context.session?.tryRefresh) {
@@ -102,6 +79,7 @@ export const getSessionContext = createServerOnlyFn(async (request: Request, opt
const url = new URL(request.url); const url = new URL(request.url);
const from = encodeURIComponent(url.pathname + url.search); const from = encodeURIComponent(url.pathname + url.search);
console.log('redirecting')
throw redirect({ throw redirect({
to: "/refresh-session", to: "/refresh-session",
search: { redirect: from } search: { redirect: from }
@@ -121,7 +99,7 @@ export const getSessionContext = createServerOnlyFn(async (request: Request, opt
}; };
return context; return context;
}); };
export const superTokensRequestMiddleware = createMiddleware({ export const superTokensRequestMiddleware = createMiddleware({
type: "request", type: "request",
@@ -132,8 +110,8 @@ export const superTokensRequestMiddleware = createMiddleware({
export const superTokensFunctionMiddleware = createMiddleware({ export const superTokensFunctionMiddleware = createMiddleware({
type: "function", type: "function",
}).server(async ({ next }) => { }).server(async ({ next, response }) => {
const request = getRequest(); const request = getWebRequest();
try { try {
const context = await getSessionContext(request, { isServerFunction: true }); const context = await getSessionContext(request, { isServerFunction: true });
@@ -158,7 +136,7 @@ export const superTokensFunctionMiddleware = createMiddleware({
export const superTokensAdminFunctionMiddleware = createMiddleware({ export const superTokensAdminFunctionMiddleware = createMiddleware({
type: "function", type: "function",
}).server(async ({ next }) => { }).server(async ({ next }) => {
const request = getRequest(); const request = getWebRequest();
try { try {
const context = await getSessionContext(request, { isServerFunction: true }); const context = await getSessionContext(request, { isServerFunction: true });
@@ -192,7 +170,7 @@ export const fetchUserRoles = async (userAuthId: string) => {
}; };
export const setUserMetadata = createServerFn({ method: "POST" }) export const setUserMetadata = createServerFn({ method: "POST" })
.inputValidator( .validator(
z z
.object({ .object({
first_name: z first_name: z
@@ -235,7 +213,7 @@ export const setUserMetadata = createServerFn({ method: "POST" })
}); });
export const updateUserColorScheme = createServerFn({ method: "POST" }) export const updateUserColorScheme = createServerFn({ method: "POST" })
.inputValidator((data: string) => data) .validator((data: string) => data)
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ context, data }) => { .handler(async ({ context, data }) => {
const { userAuthId, metadata } = context; const { userAuthId, metadata } = context;
@@ -254,7 +232,7 @@ export const updateUserColorScheme = createServerFn({ method: "POST" })
}); });
export const updateUserAccentColor = createServerFn({ method: "POST" }) export const updateUserAccentColor = createServerFn({ method: "POST" })
.inputValidator((data: string) => data) .validator((data: string) => data)
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ context, data }) => { .handler(async ({ context, data }) => {
const { userAuthId, metadata } = context; const { userAuthId, metadata } = context;

View File

@@ -6,15 +6,23 @@ import react from '@vitejs/plugin-react';
export default defineConfig({ export default defineConfig({
server: { server: {
port: 3000, port: 3000,
allowedHosts: ["dev.flexxon.app"]
}, },
plugins: [ plugins: [
tsConfigPaths({ tsConfigPaths({
projects: ['./tsconfig.json'], projects: ['./tsconfig.json'],
}), }),
tanstackStart({ tanstackStart({
customViteReactPlugin: true,
tsr: {
srcDirectory: 'src/app', srcDirectory: 'src/app',
},
}), }),
react() react()
] ],
build: {
target: 'esnext',
},
ssr: {
target: 'node',
}
}) })