Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions app/api/admin/access-logs/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { NextResponse } from "next/server"
import { getAdmin } from "@/lib/supabase/admin"

const ADMIN_USERNAME = "cipollas"

export async function GET(req: Request) {
try {
const { searchParams } = new URL(req.url)
const adminUsername = searchParams.get("adminUsername")
const date = searchParams.get("date") // Format: YYYY-MM-DD

if (adminUsername !== ADMIN_USERNAME) {
return NextResponse.json({ error: "Non autorizzato" }, { status: 403 })
}

const supabase = getAdmin()

// Get start and end of the requested day (or today)
const targetDate = date ? new Date(date) : new Date()
const startOfDay = new Date(targetDate)
startOfDay.setHours(0, 0, 0, 0)
const endOfDay = new Date(targetDate)
endOfDay.setHours(23, 59, 59, 999)

const { data, error } = await supabase
.from("access_logs")
.select("id, user_id, username, logged_at")
.gte("logged_at", startOfDay.toISOString())
.lte("logged_at", endOfDay.toISOString())
.order("logged_at", { ascending: false })

if (error) {
return NextResponse.json({ error: "Errore database: " + error.message }, { status: 500 })
}

// Count unique users
const uniqueUsers = new Set(data?.map(log => log.user_id) || [])

return NextResponse.json({
logs: data || [],
totalAccesses: data?.length || 0,
uniqueUsers: uniqueUsers.size,
date: targetDate.toISOString().split("T")[0],
})
} catch {
return NextResponse.json({ error: "Errore del server" }, { status: 500 })
}
}
29 changes: 29 additions & 0 deletions app/api/admin/ban/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { NextResponse } from "next/server"
import { getAdmin } from "@/lib/supabase/admin"

export async function POST(req: Request) {
try {
const { userId, adminUsername, reason } = await req.json()
if (adminUsername !== "cipollas") return NextResponse.json({ error: "Non autorizzato" }, { status: 403 })

const supabase = getAdmin()
const { data: profile } = await supabase.from("profiles").select("display_name").eq("id", userId).single()
if (!profile) return NextResponse.json({ error: "Utente non trovato" }, { status: 404 })

const { data: piUser } = await supabase.from("pi_users").select("pi_uid").eq("username", profile.display_name).maybeSingle()
if (!piUser) return NextResponse.json({ error: "Pi user non trovato" }, { status: 404 })

await supabase.from("banned_users").upsert({
pi_uid: piUser.pi_uid,
username: profile.display_name,
reason: reason || "Violazione regole chat",
}, { onConflict: "pi_uid" })

// Delete all messages from banned user
await supabase.from("messages").delete().eq("user_id", userId)

return NextResponse.json({ success: true })
} catch {
return NextResponse.json({ error: "Errore del server" }, { status: 500 })
}
}
90 changes: 90 additions & 0 deletions app/api/messages/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { NextResponse } from "next/server"
import { getAdmin } from "@/lib/supabase/admin"

export async function GET() {
try {
const supabase = getAdmin()
const { data: messages, error } = await supabase
.from("messages")
.select("id, content, created_at, user_id, reply_to, image_url, audio_url")
.order("created_at", { ascending: true })
.limit(200)

if (error) return NextResponse.json({ error: error.message }, { status: 500 })
if (!messages || messages.length === 0) return NextResponse.json([])

const userIds = [...new Set(messages.map((m) => m.user_id))]
const { data: profiles } = await supabase.from("profiles").select("id, display_name").in("id", userIds)
const profileMap = new Map(profiles?.map((p) => [p.id, p.display_name]) || [])

// Get replied-to messages
const replyIds = messages.map((m) => m.reply_to).filter(Boolean)
let replyMap = new Map()
if (replyIds.length > 0) {
const { data: replies } = await supabase.from("messages").select("id, content, user_id").in("id", replyIds)
if (replies) {
for (const r of replies) {
replyMap.set(r.id, {
content: r.content,
display_name: profileMap.get(r.user_id) || "Pioniere",
})
}
}
}

const enriched = messages.map((m) => ({
...m,
display_name: profileMap.get(m.user_id) || "Pioniere",
reply_message: m.reply_to ? replyMap.get(m.reply_to) || null : null,
}))

return NextResponse.json(enriched)
} catch {
return NextResponse.json({ error: "Errore del server" }, { status: 500 })
}
}

export async function POST(req: Request) {
try {
const { content, userId, replyTo, imageUrl, audioUrl } = await req.json()
if ((!content?.trim() && !imageUrl && !audioUrl) || !userId) {
return NextResponse.json({ error: "Dati mancanti" }, { status: 400 })
}
const supabase = getAdmin()

// Check if banned
const { data: piUser } = await supabase.from("pi_users").select("pi_uid").eq("username",
(await supabase.from("profiles").select("display_name").eq("id", userId).single()).data?.display_name
).maybeSingle()

if (piUser) {
const { data: banned } = await supabase.from("banned_users").select("id").eq("pi_uid", piUser.pi_uid).maybeSingle()
if (banned) return NextResponse.json({ error: "Utente bannato" }, { status: 403 })
}

const insertData: Record<string, unknown> = { content: content?.trim() || "", user_id: userId }
if (replyTo) insertData.reply_to = replyTo
if (imageUrl) insertData.image_url = imageUrl
if (audioUrl) insertData.audio_url = audioUrl

const { error } = await supabase.from("messages").insert(insertData)
if (error) return NextResponse.json({ error: error.message }, { status: 500 })

return NextResponse.json({ success: true })
} catch {
return NextResponse.json({ error: "Errore del server" }, { status: 500 })
}
}

export async function DELETE(req: Request) {
try {
const { messageId, adminUsername } = await req.json()
if (adminUsername !== "cipollas") return NextResponse.json({ error: "Non autorizzato" }, { status: 403 })

const supabase = getAdmin()
await supabase.from("messages").delete().eq("id", messageId)
return NextResponse.json({ success: true })
} catch {
return NextResponse.json({ error: "Errore del server" }, { status: 500 })
}
}
49 changes: 49 additions & 0 deletions app/api/pi/approve/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NextResponse } from "next/server"
import { getAdmin } from "@/lib/supabase/admin"

export async function POST(req: Request) {
try {
const { paymentId, piUid, username, amount, memo } = await req.json()

if (!process.env.PI_API_KEY) {
console.error("[v0] PI_API_KEY non configurata")
return NextResponse.json({ error: "API key non configurata" }, { status: 500 })
}

console.log("[v0] Approving payment:", paymentId)
const res = await fetch(`https://api.minepi.com/v2/payments/${paymentId}/approve`, {
method: "POST",
headers: {
Authorization: `Key ${process.env.PI_API_KEY}`,
"Content-Type": "application/json",
},
})

const data = await res.text()
console.log("[v0] Approve response:", res.status, data)

if (!res.ok) {
return NextResponse.json({ error: `Errore approvazione: ${data}` }, { status: res.status })
}

// Save donation to database with 'approved' status
const supabase = getAdmin()
try {
await supabase.from("donations").insert({
pi_uid: piUid || "unknown",
username: username || "Anonimo",
pi_payment_id: paymentId,
amount: amount || 0,
memo: memo || "Donazione",
status: "approved",
})
} catch {
// Table might not exist yet, continue
}

return NextResponse.json({ success: true })
} catch (err) {
console.error("[v0] Approve error:", err)
return NextResponse.json({ error: "Errore del server" }, { status: 500 })
}
}
126 changes: 126 additions & 0 deletions app/api/pi/auth/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { NextResponse } from "next/server"
import { getAdmin } from "@/lib/supabase/admin"

const PI_API_KEY = process.env.PI_API_KEY!
const ADMIN_USERNAME = "cipollas"

export async function POST(req: Request) {
try {
const body = await req.json()
const { accessToken, user: piUser } = body

if (!accessToken || !piUser?.uid) {
return NextResponse.json({ error: "Dati mancanti" }, { status: 400 })
}

// Verify with Pi Network
const piRes = await fetch("https://api.minepi.com/v2/me", {
headers: { Authorization: `Bearer ${accessToken}` },
})
if (!piRes.ok) {
return NextResponse.json({ error: "Token Pi non valido" }, { status: 401 })
}
const piData = await piRes.json()

const supabase = getAdmin()
const username = piData.username || piUser.uid
const isAdmin = username === ADMIN_USERNAME

// Log ALL access attempts (before any checks) so admin can see everyone who tries to enter
await supabase.from("access_logs").insert({
user_id: piUser.uid,
username,
})

// Admin bypasses all checks
if (!isAdmin) {
// Check KYC status from multiple possible locations
const credentials = piData.credentials || piUser.credentials || {}

// KYC can be in different formats depending on Pi SDK version
const kycStatus = credentials.kyc_verification_status ||
credentials.kyc_status ||
piUser.kyc_verification_status

const kycVerified = kycStatus === "approved" ||
kycStatus === "provisional" ||
kycStatus === "APPROVED" ||
kycStatus === "PROVISIONAL" ||
piUser.kyc_verified === true ||
credentials.kyc_verified === true

// Check migration status
const hasMigrated = credentials.has_migrated === true ||
piUser.has_migrated === true ||
credentials.migration_status === "completed"

// Only block if we have explicit negative data
if (kycStatus && !kycVerified) {
return NextResponse.json({
error: "KYC non verificato. Devi avere il KYC approvato o provvisorio per accedere."
}, { status: 403 })
}

// Only check migration if KYC status was provided
if (kycStatus && credentials.has_migrated === false) {
return NextResponse.json({
error: "Migrazione non completata. Devi completare la prima migrazione per accedere."
}, { status: 403 })
}
}

// Check if banned
const { data: banned } = await supabase
.from("banned_users")
.select("id")
.eq("pi_uid", piUser.uid)
.maybeSingle()

if (banned) {
return NextResponse.json({ error: "Utente bannato dalla chat" }, { status: 403 })
}

// Upsert pi_users
await supabase.from("pi_users").upsert({
pi_uid: piUser.uid,
username,
access_token: accessToken,
is_admin: isAdmin,
}, { onConflict: "pi_uid" })

// Upsert auth user + profile
const email = `${piUser.uid}@pi.user`
const { data: authData } = await supabase.auth.admin.listUsers()
let userId: string

const existing = authData?.users?.find((u) => u.email === email)
if (existing) {
userId = existing.id
} else {
const { data: newUser, error } = await supabase.auth.admin.createUser({
email,
password: piUser.uid + "_pi_secret_2024",
email_confirm: true,
})
if (error || !newUser.user) {
return NextResponse.json({ error: "Errore creazione utente" }, { status: 500 })
}
userId = newUser.user.id
}

// Upsert profile
await supabase.from("profiles").upsert({
id: userId,
display_name: username,
}, { onConflict: "id" })

return NextResponse.json({
userId,
username,
piUid: piUser.uid,
isAdmin,
})
} catch {
return NextResponse.json({ error: "Errore del server" }, { status: 500 })
}
}
47 changes: 47 additions & 0 deletions app/api/pi/complete/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { NextResponse } from "next/server"
import { getAdmin } from "@/lib/supabase/admin"

export async function POST(req: Request) {
try {
const { paymentId, txid } = await req.json()

// Complete with Pi Network
if (!process.env.PI_API_KEY) {
console.error("[v0] PI_API_KEY non configurata")
return NextResponse.json({ error: "API key non configurata" }, { status: 500 })
}
console.log("[v0] Completing payment:", paymentId, "txid:", txid)
const res = await fetch(`https://api.minepi.com/v2/payments/${paymentId}/complete`, {
method: "POST",
headers: {
Authorization: `Key ${process.env.PI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ txid }),
})
if (!res.ok) return NextResponse.json({ error: "Errore completamento" }, { status: 500 })

// Update donation status to completed
const supabase = getAdmin()
try {
await supabase
.from("donations")
.update({
status: "completed",
tx_id: txid,
completed_at: new Date().toISOString()
})
.eq("pi_payment_id", paymentId)
} catch {
// Table might not exist yet, continue
}

const data = await res.text()
console.log("[v0] Complete response:", res.status, data)
if (!res.ok) return NextResponse.json({ error: `Errore completamento: ${data}` }, { status: res.status })
return NextResponse.json({ success: true })
} catch (err) {
console.error("[v0] Complete error:", err)
return NextResponse.json({ error: "Errore del server" }, { status: 500 })
}
}
Loading