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:
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
12
src/types/next-auth.d.ts
vendored
Normal 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']
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user