upd: auth

Deskripsi:
-update login
- update struktur database

No Issues
This commit is contained in:
2026-04-15 11:17:04 +08:00
parent 840a89ea0a
commit c66ce4a39b
14 changed files with 273 additions and 173 deletions

View File

@@ -37,8 +37,8 @@ export function createApp() {
return { error: 'Email atau password salah' }
}
// Auto-promote super admin from env
if (env.SUPER_ADMIN_EMAILS.includes(user.email) && user.role !== 'SUPER_ADMIN') {
user = await prisma.user.update({ where: { id: user.id }, data: { role: 'SUPER_ADMIN' } })
if (env.SUPER_ADMIN_EMAILS.includes(user.email) && user.role !== 'DEVELOPER') {
user = await prisma.user.update({ where: { id: user.id }, data: { role: 'DEVELOPER' } })
}
const token = crypto.randomUUID()
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours
@@ -78,80 +78,7 @@ export function createApp() {
return { user: session.user }
})
// ─── Google OAuth ──────────────────────────────────
.get('/api/auth/google', ({ request, set }) => {
const origin = new URL(request.url).origin
const params = new URLSearchParams({
client_id: env.GOOGLE_CLIENT_ID,
redirect_uri: `${origin}/api/auth/callback/google`,
response_type: 'code',
scope: 'openid email profile',
access_type: 'offline',
prompt: 'consent',
})
set.status = 302; set.headers['location'] = `https://accounts.google.com/o/oauth2/v2/auth?${params}`
})
.get('/api/auth/callback/google', async ({ request, set }) => {
const url = new URL(request.url)
const code = url.searchParams.get('code')
const origin = url.origin
if (!code) {
set.status = 302; set.headers['location'] = '/login?error=google_failed'
return
}
// Exchange code for tokens
const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
code,
client_id: env.GOOGLE_CLIENT_ID,
client_secret: env.GOOGLE_CLIENT_SECRET,
redirect_uri: `${origin}/api/auth/callback/google`,
grant_type: 'authorization_code',
}),
})
if (!tokenRes.ok) {
set.status = 302; set.headers['location'] = '/login?error=google_failed'
return
}
const tokens = (await tokenRes.json()) as { access_token: string }
// Get user info
const userInfoRes = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: { Authorization: `Bearer ${tokens.access_token}` },
})
if (!userInfoRes.ok) {
set.status = 302; set.headers['location'] = '/login?error=google_failed'
return
}
const googleUser = (await userInfoRes.json()) as { email: string; name: string }
// Upsert user (no password for Google users)
const isSuperAdmin = env.SUPER_ADMIN_EMAILS.includes(googleUser.email)
const user = await prisma.user.upsert({
where: { email: googleUser.email },
update: { name: googleUser.name, ...(isSuperAdmin ? { role: 'SUPER_ADMIN' } : {}) },
create: { email: googleUser.email, name: googleUser.name, password: '', role: isSuperAdmin ? 'SUPER_ADMIN' : 'USER' },
})
// Create session
const token = crypto.randomUUID()
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000)
await prisma.session.create({ data: { token, userId: user.id, expiresAt } })
await createSystemLog(user.id, 'LOGIN', 'Logged in via Google')
set.headers['set-cookie'] = `session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400`
set.status = 302; set.headers['location'] = user.role === 'SUPER_ADMIN' ? '/dashboard' : '/profile'
})
// ─── Monitoring API ────────────────────────────────
.get('/api/dashboard/stats', async () => {
@@ -172,7 +99,7 @@ export function createApp() {
})
return bugs.map(b => ({
id: b.id,
app: b.app,
app: b.appId,
message: b.description,
version: b.affectedVersion,
time: b.createdAt.toISOString(),
@@ -180,18 +107,56 @@ export function createApp() {
}))
})
.get('/api/apps', async () => {
const desaPlusErrors = await prisma.bug.count({ where: { app: { in: ['desa-plus', 'desa_plus'] }, status: 'OPEN' } })
return [
{ id: 'desa-plus', name: 'Desa+', status: 'active', users: 12450, errors: desaPlusErrors, version: '2.4.1' },
]
.get('/api/apps', async ({ query }) => {
const search = (query.search as string) || ''
const where: any = {}
if (search) {
where.name = { contains: search, mode: 'insensitive' }
}
const apps = await prisma.app.findMany({
where,
include: {
_count: { select: { bugs: true } },
bugs: { where: { status: 'OPEN' }, select: { id: true } },
},
orderBy: { name: 'asc' },
})
return apps.map((app) => ({
id: app.id,
name: app.name,
status: app.maintenance ? 'warning' : app.bugs.length > 0 ? 'error' : 'active',
errors: app.bugs.length,
version: app.version ?? '-',
maintenance: app.maintenance,
}))
})
.get('/api/apps/:appId', ({ params: { appId } }) => {
const apps = {
'desa-plus': { id: 'desa-plus', name: 'Desa+', status: 'active', users: 12450, errors: 12, version: '2.4.1' },
.get('/api/apps/:appId', async ({ params: { appId }, set }) => {
const app = await prisma.app.findUnique({
where: { id: appId },
include: {
_count: { select: { bugs: true } },
bugs: { where: { status: 'OPEN' }, select: { id: true } },
},
})
if (!app) {
set.status = 404
return { error: 'App not found' }
}
return {
id: app.id,
name: app.name,
status: app.maintenance ? 'warning' : app.bugs.length > 0 ? 'error' : 'active',
errors: app.bugs.length,
version: app.version ?? '-',
minVersion: app.minVersion,
maintenance: app.maintenance,
totalBugs: app._count.bugs,
}
return apps[appId as keyof typeof apps] || { id: appId, name: appId, status: 'active', users: 0, errors: 0, version: '1.0.0' }
})
.get('/api/logs', async ({ query }) => {
@@ -246,7 +211,7 @@ export function createApp() {
}
const body = (await request.json()) as { type: string, message: string }
const actingUserId = userId || (await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } }))?.id || ''
const actingUserId = userId || (await prisma.user.findFirst({ where: { role: 'DEVELOPER' } }))?.id || ''
await createSystemLog(actingUserId, body.type as any, body.message)
return { ok: true }
@@ -419,7 +384,7 @@ export function createApp() {
]
}
if (app && app !== 'all') {
where.app = app
where.appId = app
}
if (status && status !== 'all') {
where.status = status
@@ -463,12 +428,12 @@ export function createApp() {
}
const body = (await request.json()) as any
const defaultAdmin = await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } })
const defaultAdmin = await prisma.user.findFirst({ where: { role: 'DEVELOPER' } })
const actingUserId = userId || defaultAdmin?.id || ''
const bug = await prisma.bug.create({
data: {
app: body.app,
appId: body.app,
affectedVersion: body.affectedVersion,
device: body.device,
os: body.os,
@@ -508,7 +473,7 @@ export function createApp() {
}
const body = (await request.json()) as { feedBack: string }
const defaultAdmin = await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } })
const defaultAdmin = await prisma.user.findFirst({ where: { role: 'DEVELOPER' } })
const actingUserId = userId || defaultAdmin?.id || undefined
const bug = await prisma.bug.update({
@@ -538,7 +503,7 @@ export function createApp() {
}
const body = (await request.json()) as { status: string; description?: string }
const defaultAdmin = await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } })
const defaultAdmin = await prisma.user.findFirst({ where: { role: 'DEVELOPER' } })
const actingUserId = userId || defaultAdmin?.id || undefined
const bug = await prisma.bug.update({
@@ -562,6 +527,30 @@ export function createApp() {
return bug
})
// ─── System Status API ─────────────────────────────
.get('/api/system/status', async () => {
try {
// Check database connectivity
await prisma.$queryRaw`SELECT 1`
const activeSessions = await prisma.session.count({
where: { expiresAt: { gte: new Date() } },
})
return {
status: 'operational',
database: 'connected',
activeSessions,
uptime: process.uptime(),
}
} catch {
return {
status: 'degraded',
database: 'disconnected',
activeSessions: 0,
uptime: process.uptime(),
}
}
})
// ─── Example API ───────────────────────────────────
.get('/api/hello', () => ({
message: 'Hello, world!',