fix: resolve multiple runtime and build issues

- Fix network binding to allow external access (0.0.0.0)
- Upgrade to Node.js 20.x for Next.js 16 compatibility
- Fix next-auth v4 configuration and session handling
- Add Steam client secret for Steam OAuth provider
- Fix Prisma schema unique constraint syntax
- Fix database creation script for automated deployment
- Fix game search API to use new IStoreService endpoint
- Fix session auth in API routes for Steam linking
- Add TypeScript types for next-auth session
This commit is contained in:
2026-02-21 22:51:45 +00:00
parent 14890b6875
commit 70726c50dc
13 changed files with 1064 additions and 112 deletions

View File

@@ -1,3 +1,19 @@
import { handlers } from '@/lib/auth'
import NextAuth from 'next-auth'
import { authOptions } from '@/lib/auth'
import SteamProvider from 'next-auth-steam'
import { NextRequest } from 'next/server'
export const { GET, POST } = handlers
async function auth(req: NextRequest, ctx: { params: Promise<{ nextauth: string[] }> }) {
const resolvedParams = await ctx.params
return NextAuth(req, { params: resolvedParams }, {
...authOptions,
providers: [
...authOptions.providers,
SteamProvider(req, {
clientSecret: process.env.STEAM_CLIENT_SECRET || ''
})
]
})
}
export { auth as GET, auth as POST }

View File

@@ -14,7 +14,7 @@ export async function GET(
}
let game = await prisma.game.findUnique({
where: { app_id: appId }
where: { appId: appId }
})
if (!game) {
@@ -26,13 +26,13 @@ export async function GET(
game = await prisma.game.create({
data: {
app_id: appId,
appId: appId,
name: steamData.name,
description: steamData.short_description,
header_image: steamData.header_image,
capsule_image: steamData.capsule_image,
background_image: steamData.background,
release_date: steamData.release_date?.date,
headerImage: steamData.header_image,
capsuleImage: steamData.capsule_image,
backgroundImage: steamData.background,
releaseDate: steamData.release_date?.date,
developers: steamData.developers || [],
publishers: steamData.publishers || [],
genres: steamData.genres?.map(g => g.description) || []
@@ -41,12 +41,12 @@ export async function GET(
}
return NextResponse.json({
app_id: game.app_id,
app_id: game.appId,
name: game.name,
description: game.description,
header_image: game.header_image,
background_image: game.background_image,
release_date: game.release_date,
header_image: game.headerImage,
background_image: game.backgroundImage,
release_date: game.releaseDate,
developers: game.developers,
publishers: game.publishers,
genres: game.genres

View File

@@ -18,15 +18,15 @@ export async function GET(request: NextRequest) {
}
},
select: {
app_id: true,
appId: true,
name: true,
header_image: true
headerImage: true
},
take: 10
})
if (cachedGames.length > 0) {
return NextResponse.json(cachedGames)
return NextResponse.json(cachedGames.map((g: any) => ({ app_id: g.appId, name: g.name, header_image: g.headerImage })))
}
const steamGames = await searchSteamGames(query)

View File

@@ -30,29 +30,29 @@ export async function POST(
const existingVote = await prisma.vote.findUnique({
where: {
user_review_unique: {
user_id: session.user.id,
review_id: resolvedParams.id
userId: session.user.id,
reviewId: resolvedParams.id
}
}
})
if (existingVote) {
if (existingVote.vote_type === voteType) {
if (existingVote.voteType === voteType) {
await prisma.vote.delete({
where: { id: existingVote.id }
})
} else {
await prisma.vote.update({
where: { id: existingVote.id },
data: { vote_type: voteType }
data: { voteType: voteType }
})
}
} else {
await prisma.vote.create({
data: {
user_id: session.user.id,
review_id: resolvedParams.id,
vote_type: voteType
userId: session.user.id,
reviewId: resolvedParams.id,
voteType: voteType
}
})
}

View File

@@ -14,7 +14,7 @@ export async function GET(request: NextRequest) {
const where: Record<string, unknown> = {}
if (appId) {
where.app_id = parseInt(appId)
where.appId = parseInt(appId)
}
const reviews = await prisma.review.findMany({
@@ -34,16 +34,16 @@ export async function GET(request: NextRequest) {
}
},
orderBy: {
created_at: 'desc'
createdAt: 'desc'
},
take: limit
})
const formattedReviews = reviews.map(review => {
const upvotes = review.votes.filter(v => v.vote_type === 1).length
const downvotes = review.votes.filter(v => v.vote_type === -1).length
const formattedReviews = reviews.map((review: any) => {
const upvotes = review.votes.filter((v: any) => v.voteType === 1).length
const downvotes = review.votes.filter((v: any) => v.voteType === -1).length
const userVote = userId
? review.votes.find(v => v.user_id === userId)?.vote_type || null
? review.votes.find((v: any) => v.userId === userId)?.voteType || null
: null
return {
@@ -52,11 +52,11 @@ export async function GET(request: NextRequest) {
avatar_url: review.user.steamAvatar,
content: review.content,
rating: review.rating,
playtime_hours: review.playtime_hours,
playtime_hours: review.playtimeHours,
upvotes,
downvotes,
user_vote: userVote,
created_at: review.created_at.toISOString(),
created_at: review.createdAt.toISOString(),
game_name: review.game?.name
}
})
@@ -89,8 +89,8 @@ export async function POST(request: NextRequest) {
const existingReview = await prisma.review.findFirst({
where: {
user_id: session.user.id,
app_id: appId
userId: session.user.id,
appId: appId
}
})
@@ -102,16 +102,16 @@ export async function POST(request: NextRequest) {
}
const game = await prisma.game.findUnique({
where: { app_id: appId }
where: { appId: appId }
})
const review = await prisma.review.create({
data: {
user_id: session.user.id,
app_id: appId,
userId: session.user.id,
appId: appId,
content,
rating,
playtime_hours: playtimeHours || null
playtimeHours: playtimeHours || null
}
})

View File

@@ -1,17 +1,15 @@
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import Steam from 'next-auth/providers/steam'
import NextAuth, { NextAuthOptions, getServerSession } from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'
import { compare } from 'bcryptjs'
import { prisma } from './db'
import { headers } from 'next/headers'
export const { handlers, signIn, signOut, auth } = NextAuth({
export { getServerSession }
export const authOptions: NextAuthOptions = {
secret: process.env.NEXTAUTH_SECRET,
providers: [
Steam({
clientId: process.env.STEAM_CLIENT_ID,
clientSecret: process.env.STEAM_CLIENT_SECRET,
allowDangerousEmailAccountLinking: true
}),
Credentials({
CredentialsProvider({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email' },
@@ -54,18 +52,18 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
async jwt({ token, user }) {
if (user) {
token.id = user.id
token.steamId = user.steamId
token.steamPersonaname = user.steamPersonaname
token.steamAvatar = user.steamAvatar
token.steamId = (user as any).steamId
token.steamPersonaname = (user as any).steamPersonaname
token.steamAvatar = (user as any).steamAvatar
}
return token
},
async session({ session, token }) {
if (token) {
session.user.id = token.id as string
session.user.steamId = token.steamId as string | null
session.user.steamPersonaname = token.steamPersonaname as string | null
session.user.steamAvatar = token.steamAvatar as string | null
if (session.user) {
(session.user as any).id = token.id
;(session.user as any).steamId = token.steamId
;(session.user as any).steamPersonaname = token.steamPersonaname
;(session.user as any).steamAvatar = token.steamAvatar
}
return session
}
@@ -76,23 +74,10 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
session: {
strategy: 'jwt'
}
})
declare module 'next-auth' {
interface User {
steamId?: string | null
steamPersonaname?: string | null
steamAvatar?: string | null
}
interface Session {
user: {
id: string
email: string
name: string
steamId?: string | null
steamPersonaname?: string | null
steamAvatar?: string | null
}
}
}
export async function auth() {
const headersList = await headers()
const session = await getServerSession(authOptions)
return session
}

View File

@@ -9,7 +9,8 @@ export interface SteamGame {
export interface SteamGameDetails {
[appId: string]: {
data: {
success: boolean
data?: {
name: string
short_description: string
header_image: string
@@ -43,14 +44,14 @@ export interface SteamPlayerStats {
export async function searchSteamGames(query: string): Promise<SteamGame[]> {
try {
const response = await fetch(
`${STEAM_API_BASE}/ISteamGames/GetAppList/v0002/?format=json`
`https://api.steampowered.com/IStoreService/GetAppList/v1/?key=${STEAM_API_KEY}`
)
const data = await response.json()
const apps = data.applist?.apps?.app || []
const apps = data.response?.apps || []
const filtered = apps
.filter((app: { appid: number; name: string }) =>
app.name.toLowerCase().includes(query.toLowerCase())
app.name && app.name.toLowerCase().includes(query.toLowerCase())
)
.slice(0, 20)

12
src/types/next-auth.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
import { DefaultSession } from 'next-auth'
declare module 'next-auth' {
interface Session {
user: {
id: string
steamId?: string | null
steamPersonaname?: string | null
steamAvatar?: string | null
} & DefaultSession['user']
}
}