feat: 后端初始化
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return ok({ message: 'cart-add endpoint placeholder' })
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return ok({ message: 'login endpoint placeholder' })
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return ok({ message: 'order-create endpoint placeholder' })
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return ok({ message: 'order-pay endpoint placeholder' })
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['GET'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return ok({ message: 'product-detail endpoint placeholder' })
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['GET'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return ok({ message: 'product-list endpoint placeholder' })
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return ok({ message: 'upload endpoint placeholder' })
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['GET'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return ok({ message: 'user-info endpoint placeholder' })
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
export type ApiSuccess<T> = {
|
||||
success: true
|
||||
data: T
|
||||
}
|
||||
|
||||
export type ApiFailure = {
|
||||
success: false
|
||||
error: {
|
||||
code: string
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type ApiResponse<T> = ApiSuccess<T> | ApiFailure
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { fail } from './http.js'
|
||||
|
||||
export type FunctionHandler = (request: Request, context: Context) => Promise<Response> | Response
|
||||
|
||||
export const withErrorBoundary = (handler: FunctionHandler): FunctionHandler => {
|
||||
return async (request, context) => {
|
||||
try {
|
||||
return await handler(request, context)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return fail('Internal server error', 500, 'INTERNAL_SERVER_ERROR')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
export type JsonBody = Record<string, unknown> | unknown[]
|
||||
|
||||
export const json = (body: JsonBody, init: ResponseInit = {}) =>
|
||||
Response.json(body, {
|
||||
...init,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(init.headers || {})
|
||||
}
|
||||
})
|
||||
|
||||
export const ok = (data: unknown = null) => json({ success: true, data })
|
||||
|
||||
export const fail = (message: string, status = 400, code = 'BAD_REQUEST') =>
|
||||
json({ success: false, error: { code, message } }, { status })
|
||||
|
||||
export const methodNotAllowed = (allowedMethods: string[]) =>
|
||||
json(
|
||||
{ success: false, error: { code: 'METHOD_NOT_ALLOWED', message: 'Method not allowed' } },
|
||||
{
|
||||
status: 405,
|
||||
headers: {
|
||||
Allow: allowedMethods.join(', ')
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export const handleOptions = () =>
|
||||
new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS'
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
import { handleOptions, methodNotAllowed } from './http.js'
|
||||
|
||||
export const guardMethod = (request: Request, allowedMethods: string[]) => {
|
||||
if (request.method === 'OPTIONS') {
|
||||
return handleOptions()
|
||||
}
|
||||
|
||||
if (!allowedMethods.includes(request.method)) {
|
||||
return methodNotAllowed(allowedMethods)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { neonConfig } from '@neondatabase/serverless'
|
||||
import { PrismaNeon } from '@prisma/adapter-neon'
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import ws from 'ws'
|
||||
|
||||
neonConfig.webSocketConstructor = ws
|
||||
|
||||
const connectionString = process.env.DATABASE_URL
|
||||
|
||||
if (!connectionString) {
|
||||
throw new Error('DATABASE_URL is required')
|
||||
}
|
||||
|
||||
const globalForPrisma = globalThis as typeof globalThis & {
|
||||
prisma?: PrismaClient
|
||||
}
|
||||
|
||||
const adapter = new PrismaNeon({ connectionString })
|
||||
|
||||
export const prisma = globalForPrisma.prisma || new PrismaClient({ adapter })
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
globalForPrisma.prisma = prisma
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export const readJson = async <T = unknown>(request: Request): Promise<T> => {
|
||||
const contentType = request.headers.get('content-type') || ''
|
||||
|
||||
if (!contentType.includes('application/json')) {
|
||||
return {} as T
|
||||
}
|
||||
|
||||
return request.json() as Promise<T>
|
||||
}
|
||||
|
||||
export const getBearerToken = (request: Request) => {
|
||||
const authorization = request.headers.get('authorization') || ''
|
||||
const match = authorization.match(/^Bearer\s+(.+)$/i)
|
||||
|
||||
return match?.[1] || ''
|
||||
}
|
||||
Reference in New Issue
Block a user