test
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
// Shared Express app (no .listen here)
|
// app.js (PURE JS - no TS syntax)
|
||||||
import pinataSDK from '@pinata/sdk'
|
import pinataSDK from '@pinata/sdk'
|
||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
@ -11,19 +11,14 @@ dotenv.config()
|
|||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
// --- DEBUG: log every request (shows up in Vercel function logs)
|
// --- DEBUG: log every request (shows up in Vercel logs)
|
||||||
app.use((req, _res, next) => {
|
app.use((req, _res, next) => {
|
||||||
console.log(`[REQ] ${req.method} ${req.url} origin=${req.headers.origin || 'none'}`)
|
console.log(`[REQ] ${req.method} ${req.url} origin=${req.headers.origin || 'none'}`)
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CORS
|
* CORS
|
||||||
* - Allows localhost dev
|
|
||||||
* - Allows your main frontend(s) via ALLOWED_ORIGINS
|
|
||||||
* - Allows ANY *.vercel.app (so forks work for non-technical founders)
|
|
||||||
*
|
|
||||||
* Optional: set ALLOWED_ORIGINS in Vercel env as comma-separated list
|
* Optional: set ALLOWED_ORIGINS in Vercel env as comma-separated list
|
||||||
* Example:
|
* Example:
|
||||||
* ALLOWED_ORIGINS=https://tokenize-rwa-template.vercel.app,http://localhost:5173
|
* ALLOWED_ORIGINS=https://tokenize-rwa-template.vercel.app,http://localhost:5173
|
||||||
@ -33,33 +28,24 @@ const explicitAllowed = (process.env.ALLOWED_ORIGINS || '')
|
|||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|
||||||
function isAllowedOrigin(origin: string) {
|
function isAllowedOrigin(origin) {
|
||||||
// explicitly allowed
|
|
||||||
if (explicitAllowed.includes('*')) return true
|
if (explicitAllowed.includes('*')) return true
|
||||||
if (explicitAllowed.includes(origin)) return true
|
if (explicitAllowed.includes(origin)) return true
|
||||||
|
|
||||||
// local dev
|
|
||||||
if (origin === 'http://localhost:5173') return true
|
if (origin === 'http://localhost:5173') return true
|
||||||
|
|
||||||
// allow any Vercel preview/prod frontend (great for forks)
|
|
||||||
try {
|
try {
|
||||||
const host = new URL(origin).hostname
|
const host = new URL(origin).hostname
|
||||||
if (host.endsWith('.vercel.app')) return true
|
if (host.endsWith('.vercel.app')) return true
|
||||||
} catch {
|
} catch {
|
||||||
// ignore bad origins
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const corsOptions: cors.CorsOptions = {
|
const corsOptions = {
|
||||||
origin: (origin, cb) => {
|
origin: (origin, cb) => {
|
||||||
// allow server-to-server / curl / same-origin (no Origin header)
|
|
||||||
if (!origin) return cb(null, true)
|
if (!origin) return cb(null, true)
|
||||||
|
|
||||||
if (isAllowedOrigin(origin)) return cb(null, true)
|
if (isAllowedOrigin(origin)) return cb(null, true)
|
||||||
|
|
||||||
// IMPORTANT: return an error (not "false") so it's obvious in logs/debugging
|
|
||||||
return cb(new Error(`CORS blocked for origin: ${origin}`))
|
return cb(new Error(`CORS blocked for origin: ${origin}`))
|
||||||
},
|
},
|
||||||
methods: ['GET', 'POST', 'OPTIONS'],
|
methods: ['GET', 'POST', 'OPTIONS'],
|
||||||
@ -71,7 +57,6 @@ const corsOptions: cors.CorsOptions = {
|
|||||||
// Apply CORS to all routes
|
// Apply CORS to all routes
|
||||||
app.use(cors(corsOptions))
|
app.use(cors(corsOptions))
|
||||||
|
|
||||||
// Handle preflight requests for ALL routes (with the SAME options)
|
|
||||||
app.options('*', cors(corsOptions))
|
app.options('*', cors(corsOptions))
|
||||||
|
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
@ -82,17 +67,17 @@ const pinata =
|
|||||||
? new pinataSDK({ pinataJWTKey: process.env.PINATA_JWT })
|
? new pinataSDK({ pinataJWTKey: process.env.PINATA_JWT })
|
||||||
: new pinataSDK(process.env.PINATA_API_KEY || '', process.env.PINATA_API_SECRET || '')
|
: new pinataSDK(process.env.PINATA_API_KEY || '', process.env.PINATA_API_SECRET || '')
|
||||||
|
|
||||||
// Optional: test credentials at cold start (helps a LOT on Vercel)
|
// Optional: test credentials at cold start
|
||||||
;(async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
const auth = await (pinata as any).testAuthentication?.()
|
const auth = await pinata.testAuthentication?.()
|
||||||
console.log('Pinata auth OK:', auth || 'ok')
|
console.log('Pinata auth OK:', auth || 'ok')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Pinata authentication FAILED. Check env vars.', e)
|
console.error('Pinata authentication FAILED. Check env vars.', e)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
// Uploads (multipart/form-data)
|
// Uploads
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
storage: multer.memoryStorage(),
|
storage: multer.memoryStorage(),
|
||||||
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
|
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
|
||||||
@ -103,11 +88,21 @@ app.get('/health', (_req, res) => {
|
|||||||
res.status(200).json({ ok: true, ts: Date.now() })
|
res.status(200).json({ ok: true, ts: Date.now() })
|
||||||
})
|
})
|
||||||
|
|
||||||
function safeTrim(v: unknown) {
|
// DEBUG ROUTE
|
||||||
|
app.get('/api/debug', (req, res) => {
|
||||||
|
res.status(200).json({
|
||||||
|
ok: true,
|
||||||
|
message: 'Reached Express',
|
||||||
|
url: req.url,
|
||||||
|
origin: req.headers.origin || null,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function safeTrim(v) {
|
||||||
return typeof v === 'string' ? v.trim() : ''
|
return typeof v === 'string' ? v.trim() : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeJsonParse(v: unknown, fallback: any) {
|
function safeJsonParse(v, fallback) {
|
||||||
try {
|
try {
|
||||||
if (typeof v !== 'string' || !v.trim()) return fallback
|
if (typeof v !== 'string' || !v.trim()) return fallback
|
||||||
return JSON.parse(v)
|
return JSON.parse(v)
|
||||||
@ -116,71 +111,16 @@ function safeJsonParse(v: unknown, fallback: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = express()
|
|
||||||
|
|
||||||
// 1️⃣ (optional) DEBUG: log every request
|
|
||||||
app.use((req, _res, next) => {
|
|
||||||
console.log(`[REQ] ${req.method} ${req.url} origin=${req.headers.origin || 'none'}`)
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 2️⃣ CORS
|
|
||||||
app.use(cors(corsOptions))
|
|
||||||
app.options('*', cors(corsOptions))
|
|
||||||
|
|
||||||
// 3️⃣ Body parsers
|
|
||||||
app.use(express.json())
|
|
||||||
|
|
||||||
// 4️⃣ Health
|
|
||||||
app.get('/health', (_req, res) => {
|
|
||||||
res.set('Cache-Control', 'no-store')
|
|
||||||
res.status(200).json({ ok: true, ts: Date.now() })
|
|
||||||
})
|
|
||||||
|
|
||||||
// 🔎 5️⃣ DEBUG ROUTE — ADD IT HERE
|
|
||||||
app.get('/api/debug', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
ok: true,
|
|
||||||
message: 'Reached Express',
|
|
||||||
url: req.url,
|
|
||||||
origin: req.headers.origin || null,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 6️⃣ Upload middleware
|
|
||||||
const upload = multer({
|
|
||||||
storage: multer.memoryStorage(),
|
|
||||||
limits: { fileSize: 10 * 1024 * 1024 },
|
|
||||||
})
|
|
||||||
|
|
||||||
// 7️⃣ Real endpoint
|
|
||||||
app.post('/api/pin-image', upload.single('file'), async (req, res) => {
|
|
||||||
// ... your existing logic
|
|
||||||
})
|
|
||||||
|
|
||||||
// 8️⃣ (optional) DEBUG: catch-all 404 at VERY bottom
|
|
||||||
app.use((req, res) => {
|
|
||||||
console.log(`[MISS] ${req.method} ${req.url}`)
|
|
||||||
res.status(404).json({
|
|
||||||
error: 'NOT_FOUND_IN_EXPRESS',
|
|
||||||
method: req.method,
|
|
||||||
url: req.url,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
app.post('/api/pin-image', upload.single('file'), async (req, res) => {
|
app.post('/api/pin-image', upload.single('file'), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const file = req.file
|
const file = req.file
|
||||||
if (!file) return res.status(400).json({ error: 'No file uploaded' })
|
if (!file) return res.status(400).json({ error: 'No file uploaded' })
|
||||||
|
|
||||||
// Optional form fields (friendly for vibe-coders)
|
|
||||||
const metaName = safeTrim(req.body?.metaName) || 'NFT Example'
|
const metaName = safeTrim(req.body?.metaName) || 'NFT Example'
|
||||||
const metaDescription = safeTrim(req.body?.metaDescription) || 'Pinned via TokenizeRWA template'
|
const metaDescription = safeTrim(req.body?.metaDescription) || 'Pinned via TokenizeRWA template'
|
||||||
const properties = safeJsonParse(req.body?.properties, {})
|
const properties = safeJsonParse(req.body?.properties, {})
|
||||||
|
|
||||||
// Pin image
|
const stream = Readable.from(file.buffer)
|
||||||
const stream = Readable.from(file.buffer) as any
|
|
||||||
stream.path = file.originalname || 'upload'
|
stream.path = file.originalname || 'upload'
|
||||||
|
|
||||||
const imageResult = await pinata.pinFileToIPFS(stream, {
|
const imageResult = await pinata.pinFileToIPFS(stream, {
|
||||||
@ -189,7 +129,6 @@ app.post('/api/pin-image', upload.single('file'), async (req, res) => {
|
|||||||
|
|
||||||
const imageUrl = `ipfs://${imageResult.IpfsHash}`
|
const imageUrl = `ipfs://${imageResult.IpfsHash}`
|
||||||
|
|
||||||
// Pin metadata JSON
|
|
||||||
const metadata = {
|
const metadata = {
|
||||||
name: metaName,
|
name: metaName,
|
||||||
description: metaDescription,
|
description: metaDescription,
|
||||||
@ -201,28 +140,17 @@ app.post('/api/pin-image', upload.single('file'), async (req, res) => {
|
|||||||
pinataMetadata: { name: `${metaName} Metadata` },
|
pinataMetadata: { name: `${metaName} Metadata` },
|
||||||
})
|
})
|
||||||
|
|
||||||
const metadataUrl = `ipfs://${jsonResult.IpfsHash}`
|
return res.status(200).json({ metadataUrl: `ipfs://${jsonResult.IpfsHash}` })
|
||||||
|
} catch (error) {
|
||||||
return res.status(200).json({ metadataUrl })
|
const msg = error?.response?.data?.error || error?.response?.data || error?.message || 'Failed to pin to IPFS.'
|
||||||
} catch (error: any) {
|
|
||||||
const msg =
|
|
||||||
error?.response?.data?.error ||
|
|
||||||
error?.response?.data ||
|
|
||||||
error?.message ||
|
|
||||||
'Failed to pin to IPFS.'
|
|
||||||
return res.status(500).json({ error: msg })
|
return res.status(500).json({ error: msg })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// --- DEBUG: catch-all so we can see if Express is being hit at all
|
// Catch-all 404 (so we KNOW Express is being hit)
|
||||||
app.use((req, res) => {
|
app.use((req, res) => {
|
||||||
console.log(`[MISS] ${req.method} ${req.url} (no route matched)`)
|
console.log(`[MISS] ${req.method} ${req.url}`)
|
||||||
res.status(404).json({
|
res.status(404).json({ error: 'NOT_FOUND_IN_EXPRESS', method: req.method, url: req.url })
|
||||||
error: 'NOT_FOUND_IN_EXPRESS',
|
|
||||||
method: req.method,
|
|
||||||
url: req.url,
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
export default app
|
export default app
|
||||||
|
|||||||
Reference in New Issue
Block a user