nico/25-feb-26 #70
@@ -26,7 +26,24 @@ export async function seedBerita() {
|
||||
|
||||
console.log("🔄 Seeding Berita...");
|
||||
|
||||
// Build a map of valid kategori IDs
|
||||
const validKategoriIds = new Set<string>();
|
||||
const kategoriList = await prisma.kategoriBerita.findMany({
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
kategoriList.forEach((k) => validKategoriIds.add(k.id));
|
||||
|
||||
console.log(`📋 Found ${validKategoriIds.size} valid kategori IDs in database`);
|
||||
|
||||
for (const b of beritaJson) {
|
||||
// Validate kategoriBeritaId exists
|
||||
if (!b.kategoriBeritaId || !validKategoriIds.has(b.kategoriBeritaId)) {
|
||||
console.warn(
|
||||
`⚠️ Skipping berita "${b.judul}": Invalid kategoriBeritaId "${b.kategoriBeritaId}"`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let imageId: string | null = null;
|
||||
|
||||
if (b.imageName) {
|
||||
@@ -44,26 +61,32 @@ export async function seedBerita() {
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.berita.upsert({
|
||||
where: { id: b.id },
|
||||
update: {
|
||||
judul: b.judul,
|
||||
deskripsi: b.deskripsi,
|
||||
content: b.content,
|
||||
kategoriBeritaId: b.kategoriBeritaId,
|
||||
imageId,
|
||||
},
|
||||
create: {
|
||||
id: b.id,
|
||||
judul: b.judul,
|
||||
deskripsi: b.deskripsi,
|
||||
content: b.content,
|
||||
kategoriBeritaId: b.kategoriBeritaId,
|
||||
imageId,
|
||||
},
|
||||
});
|
||||
try {
|
||||
await prisma.berita.upsert({
|
||||
where: { id: b.id },
|
||||
update: {
|
||||
judul: b.judul,
|
||||
deskripsi: b.deskripsi,
|
||||
content: b.content,
|
||||
kategoriBeritaId: b.kategoriBeritaId,
|
||||
imageId,
|
||||
},
|
||||
create: {
|
||||
id: b.id,
|
||||
judul: b.judul,
|
||||
deskripsi: b.deskripsi,
|
||||
content: b.content,
|
||||
kategoriBeritaId: b.kategoriBeritaId,
|
||||
imageId,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`✅ Berita seeded: ${b.judul}`);
|
||||
console.log(`✅ Berita seeded: ${b.judul}`);
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
`❌ Failed to seed berita "${b.judul}": ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("🎉 Berita seed selesai");
|
||||
|
||||
@@ -95,7 +95,7 @@ function Page() {
|
||||
fz={{ base: 'md', md: 'lg' }}
|
||||
lh={{ base: 1.4, md: 1.4 }}
|
||||
>
|
||||
{perbekel.nama || "I.B. Surya Prabhawa Manuaba, S.H., M.H."}
|
||||
I.B. Surya Prabhawa Manuaba, S.H., M.H.
|
||||
</Text>
|
||||
</Paper>
|
||||
</Stack>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { authStore } from "@/store/authStore";
|
||||
import { useDarkMode } from "@/state/darkModeStore";
|
||||
import { themeTokens, getActiveStateStyles } from "@/utils/themeTokens";
|
||||
import { DarkModeToggle } from "@/components/admin/DarkModeToggle";
|
||||
import { useDarkMode } from "@/state/darkModeStore";
|
||||
import { authStore } from "@/store/authStore";
|
||||
import { themeTokens } from "@/utils/themeTokens";
|
||||
import {
|
||||
ActionIcon,
|
||||
AppShell,
|
||||
|
||||
@@ -17,7 +17,6 @@ export default async function kategoriBeritaDelete(context: Context) {
|
||||
where: {
|
||||
kategoriBeritaId: id,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -23,10 +23,9 @@ export default async function findUnique(
|
||||
|
||||
// ✅ Filter by isActive and deletedAt
|
||||
const data = await prisma.potensiDesa.findFirst({
|
||||
where: {
|
||||
where: {
|
||||
id,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
include: {
|
||||
image: true,
|
||||
|
||||
@@ -17,7 +17,6 @@ export default async function kategoriPotensiDelete(context: Context) {
|
||||
where: {
|
||||
kategoriId: id,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { requireAuth } from "@/lib/api-auth";
|
||||
|
||||
export default async function sejarahDesaFindFirst(request: Request) {
|
||||
export default async function sejarahDesaFindFirst() {
|
||||
// ✅ Authentication check
|
||||
const headers = new Headers(request.url);
|
||||
const authResult = await requireAuth({ headers });
|
||||
const authResult = await requireAuth();
|
||||
if (!authResult.authenticated) {
|
||||
return authResult.response;
|
||||
}
|
||||
@@ -12,9 +11,8 @@ export default async function sejarahDesaFindFirst(request: Request) {
|
||||
try {
|
||||
// Get the first active record
|
||||
const data = await prisma.sejarahDesa.findFirst({
|
||||
where: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null
|
||||
},
|
||||
orderBy: { createdAt: 'asc' } // Get the oldest one first
|
||||
});
|
||||
|
||||
@@ -7,8 +7,8 @@ const SejarahDesa = new Elysia({
|
||||
prefix: "/sejarah",
|
||||
tags: ["Desa/Profile"],
|
||||
})
|
||||
.get("/first", async (context) => {
|
||||
const response = await sejarahDesaFindFirst(new Request(context.request));
|
||||
.get("/first", async () => {
|
||||
const response = await sejarahDesaFindFirst();
|
||||
return response;
|
||||
})
|
||||
.get("/:id", async (context) => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Context } from "elysia";
|
||||
|
||||
export default async function sejarahDesaUpdate(context: Context) {
|
||||
// ✅ Authentication check
|
||||
const authResult = await requireAuth(context);
|
||||
const authResult = await requireAuth();
|
||||
if (!authResult.authenticated) {
|
||||
return authResult.response;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useDarkMode } from '@/state/darkModeStore';
|
||||
import { themeTokens } from '@/utils/themeTokens';
|
||||
import { Paper, Box, BoxProps, Divider, DividerProps } from '@mantine/core';
|
||||
import { Box, BoxProps, Divider, DividerProps, Paper } from '@mantine/core';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,6 @@ import React from 'react';
|
||||
|
||||
// ============================================================================
|
||||
// Unified Card Component
|
||||
* ============================================================================
|
||||
|
||||
interface UnifiedCardProps extends BoxProps {
|
||||
withBorder?: boolean;
|
||||
@@ -63,12 +62,18 @@ export function UnifiedCard({
|
||||
}
|
||||
};
|
||||
|
||||
const getShadow = () => {
|
||||
if (shadow === 'none') return 'none';
|
||||
return tokens.shadows[shadow];
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
withBorder={withBorder}
|
||||
bg={tokens.colors.bg.card}
|
||||
p={getPadding()}
|
||||
radius={tokens.radius.lg} // 12-16px sesuai spec
|
||||
shadow={getShadow()}
|
||||
style={{
|
||||
borderColor: tokens.colors.border.default,
|
||||
transition: hoverable
|
||||
|
||||
@@ -5,6 +5,8 @@ import { themeTokens, getResponsiveFz } from '@/utils/themeTokens';
|
||||
import { Text, Title, Box, BoxProps } from '@mantine/core';
|
||||
import React from 'react';
|
||||
|
||||
type TextTruncate = 'end' | 'start' | boolean;
|
||||
|
||||
/**
|
||||
* Unified Typography Components
|
||||
*
|
||||
@@ -73,7 +75,7 @@ export function UnifiedTitle({
|
||||
const getColor = () => {
|
||||
if (color === 'primary') return tokens.colors.text.primary;
|
||||
if (color === 'secondary') return tokens.colors.text.secondary;
|
||||
if (color === 'brand') return tokens.colors.brand;
|
||||
if (color === 'brand') return tokens.colors.text.brand;
|
||||
return color;
|
||||
};
|
||||
|
||||
@@ -109,8 +111,14 @@ interface UnifiedTextProps {
|
||||
align?: 'left' | 'center' | 'right';
|
||||
color?: 'primary' | 'secondary' | 'tertiary' | 'muted' | 'brand' | 'link' | string;
|
||||
lineClamp?: number;
|
||||
truncate?: 'start' | 'end' | 'middle' | boolean;
|
||||
truncate?: TextTruncate;
|
||||
span?: boolean;
|
||||
mt?: string;
|
||||
mb?: string;
|
||||
ml?: string;
|
||||
mr?: string;
|
||||
mx?: string;
|
||||
my?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
@@ -123,6 +131,12 @@ export function UnifiedText({
|
||||
lineClamp,
|
||||
truncate,
|
||||
span = false,
|
||||
mt,
|
||||
mb,
|
||||
ml,
|
||||
mr,
|
||||
mx,
|
||||
my,
|
||||
style,
|
||||
}: UnifiedTextProps) {
|
||||
const { isDark } = useDarkMode();
|
||||
@@ -163,7 +177,7 @@ export function UnifiedText({
|
||||
case 'muted':
|
||||
return tokens.colors.text.muted;
|
||||
case 'brand':
|
||||
return tokens.colors.brand;
|
||||
return tokens.colors.text.brand;
|
||||
case 'link':
|
||||
return tokens.colors.text.link;
|
||||
default:
|
||||
@@ -177,7 +191,7 @@ export function UnifiedText({
|
||||
|
||||
if (span) {
|
||||
return (
|
||||
<Text.Span
|
||||
<Text
|
||||
ta={align}
|
||||
fz={typo.fz}
|
||||
fw={fw}
|
||||
@@ -185,10 +199,16 @@ export function UnifiedText({
|
||||
c={textColor}
|
||||
lineClamp={lineClamp}
|
||||
truncate={truncate}
|
||||
mt={mt}
|
||||
mb={mb}
|
||||
ml={ml}
|
||||
mr={mr}
|
||||
mx={mx}
|
||||
my={my}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</Text.Span>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -201,6 +221,12 @@ export function UnifiedText({
|
||||
c={textColor}
|
||||
lineClamp={lineClamp}
|
||||
truncate={truncate}
|
||||
mt={mt}
|
||||
mb={mb}
|
||||
ml={ml}
|
||||
mr={mr}
|
||||
mx={mx}
|
||||
my={my}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Authentication helper untuk API endpoints
|
||||
*
|
||||
*
|
||||
* Usage:
|
||||
* import { requireAuth } from "@/lib/api-auth";
|
||||
*
|
||||
* export default async function myEndpoint(context: Context) {
|
||||
* const authResult = await requireAuth(context);
|
||||
*
|
||||
* export default async function myEndpoint() {
|
||||
* const authResult = await requireAuth();
|
||||
* if (!authResult.authenticated) {
|
||||
* return authResult.response;
|
||||
* }
|
||||
@@ -13,24 +13,24 @@
|
||||
* }
|
||||
*/
|
||||
|
||||
import { getSession } from "@/lib/session";
|
||||
import { getSession, SessionData } from "@/lib/session";
|
||||
|
||||
export type AuthResult =
|
||||
| { authenticated: true; user: any }
|
||||
export type AuthResult =
|
||||
| { authenticated: true; user: NonNullable<SessionData["user"]> }
|
||||
| { authenticated: false; response: Response };
|
||||
|
||||
export async function requireAuth(context: any): Promise<AuthResult> {
|
||||
export async function requireAuth(): Promise<AuthResult> {
|
||||
try {
|
||||
// Cek session dari cookies
|
||||
const session = await getSession();
|
||||
|
||||
|
||||
if (!session || !session.user) {
|
||||
return {
|
||||
authenticated: false,
|
||||
response: new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Unauthorized - Silakan login terlebih dahulu"
|
||||
}), {
|
||||
}), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
@@ -44,7 +44,7 @@ export async function requireAuth(context: any): Promise<AuthResult> {
|
||||
response: new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Akun Anda tidak aktif. Hubungi administrator."
|
||||
}), {
|
||||
}), {
|
||||
status: 403,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
@@ -55,14 +55,13 @@ export async function requireAuth(context: any): Promise<AuthResult> {
|
||||
authenticated: true,
|
||||
user: session.user
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Auth error:", error);
|
||||
} catch {
|
||||
return {
|
||||
authenticated: false,
|
||||
response: new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Authentication error"
|
||||
}), {
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
@@ -74,11 +73,11 @@ export async function requireAuth(context: any): Promise<AuthResult> {
|
||||
* Optional auth - tidak error jika tidak authenticated
|
||||
* Berguna untuk endpoint yang bisa diakses public atau private
|
||||
*/
|
||||
export async function optionalAuth(context: any): Promise<any> {
|
||||
export async function optionalAuth(): Promise<NonNullable<SessionData["user"]> | null> {
|
||||
try {
|
||||
const session = await getSession();
|
||||
return session?.user || null;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +223,10 @@ export const themeTokens = (isDark: boolean = false): ThemeTokens => {
|
||||
hoverSoft: 'rgba(25, 113, 194, 0.03)',
|
||||
hoverMedium: 'rgba(25, 113, 194, 0.05)',
|
||||
activeAccent: 'rgba(25, 113, 194, 0.1)',
|
||||
success: '#22c55e',
|
||||
warning: '#facc15',
|
||||
error: '#ef4444',
|
||||
info: '#38bdf8',
|
||||
};
|
||||
|
||||
const current = isDark ? darkColors : lightColors;
|
||||
@@ -381,3 +385,7 @@ export const getActiveStateStyles = (isActive: boolean, isDark: boolean = false)
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user