Merge pull request 'feat(plugins): access-type + demo-carbets-seed' (#27) from feat/access-type-and-demo-carbets into main
This commit is contained in:
commit
35080dcde1
10 changed files with 380 additions and 26 deletions
|
|
@ -0,0 +1,13 @@
|
|||
-- Plugin access-type : distinction route+fleuve / fleuve only
|
||||
|
||||
CREATE TYPE "AccessType" AS ENUM ('ROAD_AND_RIVER', 'RIVER_ONLY');
|
||||
|
||||
ALTER TABLE "Carbet"
|
||||
ADD COLUMN "accessType" "AccessType" NOT NULL DEFAULT 'ROAD_AND_RIVER',
|
||||
ADD COLUMN "roadAccessNote" TEXT;
|
||||
|
||||
-- La pirogue n'est obligatoire qu'en RIVER_ONLY. Pour ROAD_AND_RIVER, la valeur
|
||||
-- est optionnelle (estimation pour ceux qui veulent quand même venir en pirogue).
|
||||
ALTER TABLE "Carbet" ALTER COLUMN "pirogueDurationMin" DROP NOT NULL;
|
||||
|
||||
CREATE INDEX "Carbet_accessType_idx" ON "Carbet" ("accessType");
|
||||
|
|
@ -106,7 +106,12 @@ model Carbet {
|
|||
latitude Decimal @db.Decimal(9, 6)
|
||||
longitude Decimal @db.Decimal(9, 6)
|
||||
embarkPoint String
|
||||
pirogueDurationMin Int
|
||||
// Pirogue : obligatoire pour RIVER_ONLY, optionnelle pour ROAD_AND_RIVER
|
||||
// (estimation pour ceux qui veulent quand même venir en pirogue).
|
||||
pirogueDurationMin Int?
|
||||
accessType AccessType @default(ROAD_AND_RIVER)
|
||||
// Détails d'accès route pour ROAD_AND_RIVER (GPS, distance, type de piste).
|
||||
roadAccessNote String?
|
||||
capacity Int
|
||||
status CarbetStatus @default(DRAFT)
|
||||
lastBookedAt DateTime?
|
||||
|
|
@ -124,6 +129,7 @@ model Carbet {
|
|||
@@index([ownerId])
|
||||
@@index([status])
|
||||
@@index([river])
|
||||
@@index([accessType])
|
||||
}
|
||||
|
||||
model Amenity {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { formatAverageRating } from "@/lib/reviews";
|
|||
import { CarbetGallery } from "../_components/carbet-gallery";
|
||||
import { ReviewsSection } from "../_components/reviews-section";
|
||||
import { StarRating } from "../_components/star-rating";
|
||||
import { AccessTypeBadge } from "@/components/AccessTypeBadge";
|
||||
|
||||
type PageProps = {
|
||||
params: Promise<{ slug: string }>;
|
||||
|
|
@ -88,17 +89,23 @@ export default async function PublicCarbetPage({ params }: PageProps) {
|
|||
</Link>
|
||||
|
||||
<header className="mt-3">
|
||||
<p className="text-sm font-medium uppercase tracking-wide text-emerald-700">
|
||||
Fleuve {carbet.river}
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<p className="text-sm font-medium uppercase tracking-wide text-emerald-700">
|
||||
Fleuve {carbet.river}
|
||||
</p>
|
||||
<AccessTypeBadge accessType={carbet.accessType} size="md" />
|
||||
</div>
|
||||
<h1 className="mt-1 text-3xl font-semibold text-zinc-900 sm:text-4xl">
|
||||
{carbet.title}
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-zinc-600">
|
||||
Accueil par {carbet.ownerFirstName} · {carbet.capacity} voyageur
|
||||
{carbet.capacity > 1 ? "s" : ""} · Pirogue{" "}
|
||||
{formatPirogueDuration(carbet.pirogueDurationMin)} depuis{" "}
|
||||
{carbet.embarkPoint}
|
||||
{carbet.capacity > 1 ? "s" : ""}
|
||||
{carbet.accessType === "RIVER_ONLY"
|
||||
? ` · Pirogue ${formatPirogueDuration(carbet.pirogueDurationMin)} depuis ${carbet.embarkPoint}`
|
||||
: carbet.pirogueDurationMin
|
||||
? ` · Route + ${formatPirogueDuration(carbet.pirogueDurationMin)} pirogue depuis ${carbet.embarkPoint}`
|
||||
: ` · Route directe (embarquement ${carbet.embarkPoint})`}
|
||||
</p>
|
||||
{carbet.reviewStats.count > 0 &&
|
||||
carbet.reviewStats.averageRating !== null ? (
|
||||
|
|
@ -157,16 +164,32 @@ export default async function PublicCarbetPage({ params }: PageProps) {
|
|||
Accès au carbet
|
||||
</h2>
|
||||
<dl className="mt-3 space-y-2 text-sm text-zinc-700">
|
||||
<div>
|
||||
<dt className="font-medium text-zinc-500">Type d'accès</dt>
|
||||
<dd>
|
||||
{carbet.accessType === "RIVER_ONLY"
|
||||
? "Expédition fleuve uniquement"
|
||||
: "Route + fleuve"}
|
||||
</dd>
|
||||
</div>
|
||||
{carbet.roadAccessNote ? (
|
||||
<div>
|
||||
<dt className="font-medium text-zinc-500">Accès route</dt>
|
||||
<dd>{carbet.roadAccessNote}</dd>
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<dt className="font-medium text-zinc-500">
|
||||
Point d'embarquement
|
||||
</dt>
|
||||
<dd>{carbet.embarkPoint}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="font-medium text-zinc-500">Trajet pirogue</dt>
|
||||
<dd>{formatPirogueDuration(carbet.pirogueDurationMin)}</dd>
|
||||
</div>
|
||||
{carbet.pirogueDurationMin !== null ? (
|
||||
<div>
|
||||
<dt className="font-medium text-zinc-500">Trajet pirogue</dt>
|
||||
<dd>{formatPirogueDuration(carbet.pirogueDurationMin)}</dd>
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<dt className="font-medium text-zinc-500">Coordonnées GPS</dt>
|
||||
<dd>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import Link from "next/link";
|
|||
import type { CarbetSearchResult } from "@/lib/carbet-search";
|
||||
import { formatPirogueDuration, truncate } from "@/lib/format";
|
||||
import { formatAverageRating } from "@/lib/reviews";
|
||||
import { AccessTypeBadge } from "@/components/AccessTypeBadge";
|
||||
|
||||
import { StarRating } from "./star-rating";
|
||||
|
||||
|
|
@ -28,11 +29,14 @@ export function CarbetCard({ carbet }: { carbet: CarbetSearchResult }) {
|
|||
)}
|
||||
</Link>
|
||||
<div className="flex flex-1 flex-col p-4">
|
||||
<h3 className="text-lg font-semibold text-zinc-900">
|
||||
<Link href={href} className="hover:text-emerald-700">
|
||||
{carbet.title}
|
||||
</Link>
|
||||
</h3>
|
||||
<div className="mb-1 flex items-start justify-between gap-2">
|
||||
<h3 className="text-lg font-semibold text-zinc-900">
|
||||
<Link href={href} className="hover:text-emerald-700">
|
||||
{carbet.title}
|
||||
</Link>
|
||||
</h3>
|
||||
<AccessTypeBadge accessType={carbet.accessType} />
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-zinc-600">
|
||||
Fleuve {carbet.river} · {carbet.capacity} voyageur
|
||||
{carbet.capacity > 1 ? "s" : ""}
|
||||
|
|
@ -50,8 +54,11 @@ export function CarbetCard({ carbet }: { carbet: CarbetSearchResult }) {
|
|||
{truncate(carbet.description, 180)}
|
||||
</p>
|
||||
<p className="mt-3 text-xs text-zinc-500">
|
||||
Pirogue {formatPirogueDuration(carbet.pirogueDurationMin)} depuis{" "}
|
||||
{carbet.embarkPoint}
|
||||
{carbet.accessType === "RIVER_ONLY"
|
||||
? `Pirogue ${formatPirogueDuration(carbet.pirogueDurationMin)} depuis ${carbet.embarkPoint}`
|
||||
: carbet.pirogueDurationMin
|
||||
? `Route + ${formatPirogueDuration(carbet.pirogueDurationMin)} pirogue depuis ${carbet.embarkPoint}`
|
||||
: `Accessible par la route — embarquement possible à ${carbet.embarkPoint}`}
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
|
|
|
|||
43
src/components/AccessTypeBadge.tsx
Normal file
43
src/components/AccessTypeBadge.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
"use client";
|
||||
|
||||
import { useIsPluginEnabled } from "@/lib/plugins/client";
|
||||
import type { AccessType } from "@/generated/prisma/enums";
|
||||
|
||||
/**
|
||||
* Badge route+fleuve vs fleuve only. Gated par le plugin `access-type`.
|
||||
* Si le plugin est désactivé, rien n'est rendu — la fiche tombe sur le
|
||||
* comportement legacy (pirogue toujours mentionnée).
|
||||
*/
|
||||
export function AccessTypeBadge({
|
||||
accessType,
|
||||
size = "sm",
|
||||
}: {
|
||||
accessType: AccessType;
|
||||
size?: "sm" | "md";
|
||||
}) {
|
||||
const enabled = useIsPluginEnabled("access-type");
|
||||
if (!enabled) return null;
|
||||
|
||||
const isExpedition = accessType === "RIVER_ONLY";
|
||||
const label = isExpedition ? "🛶 Expédition fleuve" : "🛣️ Route + fleuve";
|
||||
const styles = isExpedition
|
||||
? "bg-[var(--color-karbe-laterite-300)]/25 text-[var(--color-karbe-laterite-700)] ring-[var(--color-karbe-laterite-500)]/30"
|
||||
: "bg-[var(--color-karbe-canopy-50)] text-[var(--color-karbe-canopy-700)] ring-[var(--color-karbe-canopy-500)]/30";
|
||||
const sizing =
|
||||
size === "md"
|
||||
? "px-3 py-1.5 text-xs"
|
||||
: "px-2 py-0.5 text-[11px]";
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center gap-1 rounded-full font-medium ring-1 ${styles} ${sizing}`}
|
||||
title={
|
||||
isExpedition
|
||||
? "Accessible uniquement par pirogue depuis un dégrad."
|
||||
: "Accessible par la route et par le fleuve."
|
||||
}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import { cache } from "react";
|
|||
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { amenityLabel } from "@/lib/amenities";
|
||||
import { CarbetStatus, MediaType } from "@/generated/prisma/enums";
|
||||
import { AccessType, CarbetStatus, MediaType } from "@/generated/prisma/enums";
|
||||
import type { CarbetReview, CarbetReviewStats } from "@/lib/reviews";
|
||||
import {
|
||||
getCarbetReviewStats,
|
||||
|
|
@ -22,7 +22,9 @@ export type PublicCarbetDetail = {
|
|||
description: string;
|
||||
river: string;
|
||||
embarkPoint: string;
|
||||
pirogueDurationMin: number;
|
||||
pirogueDurationMin: number | null;
|
||||
accessType: AccessType;
|
||||
roadAccessNote: string | null;
|
||||
capacity: number;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
|
|
@ -48,6 +50,8 @@ export const getPublicCarbet = cache(
|
|||
river: true,
|
||||
embarkPoint: true,
|
||||
pirogueDurationMin: true,
|
||||
accessType: true,
|
||||
roadAccessNote: true,
|
||||
capacity: true,
|
||||
latitude: true,
|
||||
longitude: true,
|
||||
|
|
@ -78,6 +82,8 @@ export const getPublicCarbet = cache(
|
|||
river: carbet.river,
|
||||
embarkPoint: carbet.embarkPoint,
|
||||
pirogueDurationMin: carbet.pirogueDurationMin,
|
||||
accessType: carbet.accessType,
|
||||
roadAccessNote: carbet.roadAccessNote,
|
||||
capacity: carbet.capacity,
|
||||
latitude: carbet.latitude.toString(),
|
||||
longitude: carbet.longitude.toString(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { prisma } from "@/lib/prisma";
|
||||
import { Prisma } from "@/generated/prisma/client";
|
||||
import {
|
||||
AccessType,
|
||||
AvailabilityBlockReason,
|
||||
AvailabilityScope,
|
||||
CarbetStatus,
|
||||
|
|
@ -12,6 +13,9 @@ export type CarbetSearchFilters = {
|
|||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
capacity?: number;
|
||||
// Filtre plugin access-type : si "river-only" exclu, on garde uniquement
|
||||
// ROAD_AND_RIVER. Si "all" ou non spécifié, tout passe.
|
||||
accessibility?: "road-only" | "all";
|
||||
};
|
||||
|
||||
export type RawSearchParams = {
|
||||
|
|
@ -60,6 +64,11 @@ export function parseSearchFilters(
|
|||
}
|
||||
}
|
||||
|
||||
const accessibility = pickString(searchParams.accessibility);
|
||||
if (accessibility === "road-only" || accessibility === "all") {
|
||||
filters.accessibility = accessibility;
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +78,9 @@ export type CarbetSearchResult = {
|
|||
title: string;
|
||||
river: string;
|
||||
embarkPoint: string;
|
||||
pirogueDurationMin: number;
|
||||
pirogueDurationMin: number | null;
|
||||
accessType: AccessType;
|
||||
roadAccessNote: string | null;
|
||||
capacity: number;
|
||||
description: string;
|
||||
coverUrl: string | null;
|
||||
|
|
@ -94,6 +105,10 @@ function buildWhere(filters: CarbetSearchFilters): Prisma.CarbetWhereInput {
|
|||
where.capacity = { gte: filters.capacity };
|
||||
}
|
||||
|
||||
if (filters.accessibility === "road-only") {
|
||||
where.accessType = AccessType.ROAD_AND_RIVER;
|
||||
}
|
||||
|
||||
if (filters.startDate && filters.endDate) {
|
||||
where.availabilities = {
|
||||
some: {
|
||||
|
|
@ -124,6 +139,8 @@ export async function searchCarbets(
|
|||
river: true,
|
||||
embarkPoint: true,
|
||||
pirogueDurationMin: true,
|
||||
accessType: true,
|
||||
roadAccessNote: true,
|
||||
capacity: true,
|
||||
description: true,
|
||||
media: {
|
||||
|
|
@ -149,6 +166,8 @@ export async function searchCarbets(
|
|||
river: carbet.river,
|
||||
embarkPoint: carbet.embarkPoint,
|
||||
pirogueDurationMin: carbet.pirogueDurationMin,
|
||||
accessType: carbet.accessType,
|
||||
roadAccessNote: carbet.roadAccessNote,
|
||||
capacity: carbet.capacity,
|
||||
description: carbet.description,
|
||||
coverUrl: carbet.media[0]?.s3Url ?? null,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Format a pirogue trip duration (minutes) into a human readable French label
|
||||
// such as "45 min" or "1 h 20".
|
||||
export function formatPirogueDuration(minutes: number): string {
|
||||
// such as "45 min" or "1 h 20". Null = pas de pirogue requise (carbet routier).
|
||||
export function formatPirogueDuration(minutes: number | null | undefined): string {
|
||||
if (minutes === null || minutes === undefined) return "—";
|
||||
if (minutes < 60) return `${minutes} min`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const rest = minutes % 60;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,21 @@ export interface PluginHookSet {
|
|||
onDisable?: PluginHook;
|
||||
}
|
||||
|
||||
// Pour l'instant, vide : les plugins métier ajouteront leurs hooks ici
|
||||
// au fur et à mesure (sans toucher au runtime du système Plugin).
|
||||
export const pluginHooks: Record<string, PluginHookSet | undefined> = {};
|
||||
import { archiveDemoCarbets, seedDemoCarbets } from "./seeds/demo-carbets";
|
||||
|
||||
export const pluginHooks: Record<string, PluginHookSet | undefined> = {
|
||||
"demo-carbets-seed": {
|
||||
onEnable: async () => {
|
||||
const { created, existing } = await seedDemoCarbets();
|
||||
console.log(
|
||||
`[plugin demo-carbets-seed] seed terminé : ${created} créés, ${existing} déjà présents`,
|
||||
);
|
||||
},
|
||||
onDisable: async () => {
|
||||
const archived = await archiveDemoCarbets();
|
||||
console.log(
|
||||
`[plugin demo-carbets-seed] disable : ${archived} carbets démo archivés`,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
221
src/lib/plugins/seeds/demo-carbets.ts
Normal file
221
src/lib/plugins/seeds/demo-carbets.ts
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* Seed du plugin `demo-carbets-seed`.
|
||||
*
|
||||
* Crée 3 propriétaires fictifs et 6 carbets démo répartis sur 4 fleuves
|
||||
* (Maroni, Approuague, Comté, Oyapock) et 2 types d'accès. Les carbets démo
|
||||
* sont tagués par leur slug préfixé `demo-` pour pouvoir être soft-deleted
|
||||
* (`status=ARCHIVED`) au disable du plugin sans toucher aux carbets utilisateurs.
|
||||
*/
|
||||
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { hashPassword } from "@/lib/password";
|
||||
import {
|
||||
AccessType,
|
||||
CarbetStatus,
|
||||
UserRole,
|
||||
} from "@/generated/prisma/enums";
|
||||
|
||||
const DEMO_OWNERS = [
|
||||
{
|
||||
email: "demo-yann@karbe.demo",
|
||||
firstName: "Yann",
|
||||
lastName: "Cabassou",
|
||||
phone: "+594-694-001122",
|
||||
},
|
||||
{
|
||||
email: "demo-emilie@karbe.demo",
|
||||
firstName: "Émilie",
|
||||
lastName: "Sénégal",
|
||||
phone: "+594-694-003344",
|
||||
},
|
||||
{
|
||||
email: "demo-ce-hopital@karbe.demo",
|
||||
firstName: "CE",
|
||||
lastName: "Hôpital Cayenne",
|
||||
phone: "+594-594-005566",
|
||||
},
|
||||
] as const;
|
||||
|
||||
type DemoCarbet = {
|
||||
slug: string;
|
||||
title: string;
|
||||
ownerIdx: 0 | 1 | 2;
|
||||
river: string;
|
||||
embarkPoint: string;
|
||||
accessType: AccessType;
|
||||
pirogueDurationMin: number | null;
|
||||
roadAccessNote: string | null;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
capacity: number;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const DEMO_CARBETS: DemoCarbet[] = [
|
||||
{
|
||||
slug: "demo-karbe-awara-maroni",
|
||||
title: "Karbé Awara",
|
||||
ownerIdx: 0,
|
||||
river: "Maroni",
|
||||
embarkPoint: "Dégrad Apatou",
|
||||
accessType: AccessType.RIVER_ONLY,
|
||||
pirogueDurationMin: 90,
|
||||
roadAccessNote: null,
|
||||
latitude: 5.2008,
|
||||
longitude: -53.9519,
|
||||
capacity: 6,
|
||||
description:
|
||||
"Au cœur du Maroni, à 1h30 de pirogue d'Apatou. Trois hamacs, une terrasse qui domine le fleuve, le silence total. Bois local, panneau solaire, eau pluviale. Le passeur fait l'aller-retour à votre demande.",
|
||||
},
|
||||
{
|
||||
slug: "demo-karbe-wapa-comte",
|
||||
title: "Karbé Wapa",
|
||||
ownerIdx: 1,
|
||||
river: "Comté",
|
||||
embarkPoint: "Roura, ponton municipal",
|
||||
accessType: AccessType.ROAD_AND_RIVER,
|
||||
pirogueDurationMin: 20,
|
||||
roadAccessNote:
|
||||
"30 km de piste depuis Roura, dernier kilomètre en 4×4 conseillé en saison des pluies. Parking sécurisé au ponton.",
|
||||
latitude: 4.7281,
|
||||
longitude: -52.3261,
|
||||
capacity: 4,
|
||||
description:
|
||||
"Carbet familial accessible en voiture, à 30 min du centre de Roura. Idéal week-end : on arrive vendredi soir, on dort au bord du Comté, on baigne dimanche matin. Pirogue dispo pour explorer en amont.",
|
||||
},
|
||||
{
|
||||
slug: "demo-karbe-maripa-approuague",
|
||||
title: "Karbé Maripa",
|
||||
ownerIdx: 0,
|
||||
river: "Approuague",
|
||||
embarkPoint: "Saint-Georges, dégrad principal",
|
||||
accessType: AccessType.RIVER_ONLY,
|
||||
pirogueDurationMin: 180,
|
||||
roadAccessNote: null,
|
||||
latitude: 3.9001,
|
||||
longitude: -51.8101,
|
||||
capacity: 8,
|
||||
description:
|
||||
"Trois heures de remontée de l'Approuague, plus rien ne vient brouiller la nuit. Carbet ancien rénové, deux pièces séparées, cuisine au feu de bois. Singes hurleurs garantis au lever du soleil.",
|
||||
},
|
||||
{
|
||||
slug: "demo-karbe-paripou-oyapock",
|
||||
title: "Karbé Paripou",
|
||||
ownerIdx: 1,
|
||||
river: "Oyapock",
|
||||
embarkPoint: "Saint-Georges, embarcadère mairie",
|
||||
accessType: AccessType.RIVER_ONLY,
|
||||
pirogueDurationMin: 240,
|
||||
roadAccessNote: null,
|
||||
latitude: 3.7501,
|
||||
longitude: -51.5801,
|
||||
capacity: 4,
|
||||
description:
|
||||
"Côté Oyapock, vis-à-vis du Brésil, quatre heures de pirogue depuis Saint-Georges. Pour ceux qui veulent vraiment dormir loin. Saison sèche uniquement : étiage rend l'Oyapock difficile en mai-juin.",
|
||||
},
|
||||
{
|
||||
slug: "demo-karbe-mahury-ce-hopital",
|
||||
title: "Karbé du CE — bord du Mahury",
|
||||
ownerIdx: 2,
|
||||
river: "Mahury",
|
||||
embarkPoint: "Rémire-Montjoly, base nautique",
|
||||
accessType: AccessType.ROAD_AND_RIVER,
|
||||
pirogueDurationMin: 15,
|
||||
roadAccessNote:
|
||||
"Accès depuis Rémire-Montjoly, route asphaltée jusqu'à la base nautique. Parking signalé.",
|
||||
latitude: 4.8801,
|
||||
longitude: -52.2691,
|
||||
capacity: 12,
|
||||
description:
|
||||
"Le carbet du Comité Social de l'Hôpital de Cayenne, réservé aux agents en semaine et ouvert au public le week-end (sauf jours fériés). Spacieux, équipé pour familles : groupe électrogène, frigo, plancha.",
|
||||
},
|
||||
{
|
||||
slug: "demo-karbe-kourou-couleuvre",
|
||||
title: "Karbé Couleuvre",
|
||||
ownerIdx: 1,
|
||||
river: "Kourou",
|
||||
embarkPoint: "Pont du Kourou",
|
||||
accessType: AccessType.ROAD_AND_RIVER,
|
||||
pirogueDurationMin: null,
|
||||
roadAccessNote:
|
||||
"100 % accessible par la voiture. Garez-vous au pont du Kourou, comptez 10 min à pied par la berge.",
|
||||
latitude: 5.1568,
|
||||
longitude: -52.6504,
|
||||
capacity: 3,
|
||||
description:
|
||||
"Petit carbet pour couple, sur la berge du Kourou. Accès route uniquement, idéal nuit improvisée après le boulot. Pas de pirogue ici, juste un hamac, un livre, le clapotis.",
|
||||
},
|
||||
];
|
||||
|
||||
const DEMO_PASSWORD = "demo-karbe-2026";
|
||||
|
||||
async function ensureOwner(idx: 0 | 1 | 2): Promise<string> {
|
||||
const owner = DEMO_OWNERS[idx];
|
||||
const existing = await prisma.user.findUnique({ where: { email: owner.email } });
|
||||
if (existing) return existing.id;
|
||||
|
||||
const passwordHash = await hashPassword(DEMO_PASSWORD);
|
||||
const created = await prisma.user.create({
|
||||
data: {
|
||||
email: owner.email,
|
||||
passwordHash,
|
||||
firstName: owner.firstName,
|
||||
lastName: owner.lastName,
|
||||
phone: owner.phone,
|
||||
role: UserRole.OWNER,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
return created.id;
|
||||
}
|
||||
|
||||
export async function seedDemoCarbets(): Promise<{ created: number; existing: number }> {
|
||||
const ownerIds: string[] = [];
|
||||
for (const idx of [0, 1, 2] as const) {
|
||||
ownerIds.push(await ensureOwner(idx));
|
||||
}
|
||||
|
||||
let created = 0;
|
||||
let existing = 0;
|
||||
for (const carbet of DEMO_CARBETS) {
|
||||
const found = await prisma.carbet.findUnique({ where: { slug: carbet.slug } });
|
||||
if (found) {
|
||||
// Si désactivé/archivé puis re-activé, on remet en PUBLISHED.
|
||||
if (found.status !== CarbetStatus.PUBLISHED) {
|
||||
await prisma.carbet.update({
|
||||
where: { id: found.id },
|
||||
data: { status: CarbetStatus.PUBLISHED },
|
||||
});
|
||||
}
|
||||
existing += 1;
|
||||
continue;
|
||||
}
|
||||
await prisma.carbet.create({
|
||||
data: {
|
||||
slug: carbet.slug,
|
||||
title: carbet.title,
|
||||
description: carbet.description,
|
||||
river: carbet.river,
|
||||
embarkPoint: carbet.embarkPoint,
|
||||
accessType: carbet.accessType,
|
||||
pirogueDurationMin: carbet.pirogueDurationMin,
|
||||
roadAccessNote: carbet.roadAccessNote,
|
||||
latitude: carbet.latitude,
|
||||
longitude: carbet.longitude,
|
||||
capacity: carbet.capacity,
|
||||
status: CarbetStatus.PUBLISHED,
|
||||
ownerId: ownerIds[carbet.ownerIdx],
|
||||
},
|
||||
});
|
||||
created += 1;
|
||||
}
|
||||
return { created, existing };
|
||||
}
|
||||
|
||||
export async function archiveDemoCarbets(): Promise<number> {
|
||||
const result = await prisma.carbet.updateMany({
|
||||
where: { slug: { startsWith: "demo-karbe-" } },
|
||||
data: { status: CarbetStatus.ARCHIVED },
|
||||
});
|
||||
return result.count;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue