Initial commit of edr-passenger-api alpha version

This commit is contained in:
Stephanos A
2026-05-13 16:58:49 +03:00
parent 199a3eba11
commit 39ba561d8f
113 changed files with 3602 additions and 1035 deletions

View File

@@ -0,0 +1,690 @@
-- CreateEnum
CREATE TYPE "UserRole" AS ENUM ('PASSENGER', 'ADMIN', 'STAFF');
-- CreateEnum
CREATE TYPE "TripStatus" AS ENUM ('SCHEDULED', 'BOARDING', 'EN_ROUTE', 'ARRIVED', 'CANCELLED', 'DELAYED');
-- CreateEnum
CREATE TYPE "SeatKind" AS ENUM ('STANDARD', 'PREMIUM', 'ACCESSIBLE');
-- CreateEnum
CREATE TYPE "SeatStatus" AS ENUM ('AVAILABLE', 'HELD', 'BOOKED', 'BLOCKED');
-- CreateEnum
CREATE TYPE "ServiceClass" AS ENUM ('ECONOMY', 'BUSINESS', 'FIRST');
-- CreateEnum
CREATE TYPE "BookingStatus" AS ENUM ('DRAFT', 'PENDING_PAYMENT', 'CONFIRMED', 'CANCELLED', 'COMPLETED', 'NO_SHOW');
-- CreateEnum
CREATE TYPE "PaymentMethodType" AS ENUM ('TELEBIRR', 'CBE_BIRR', 'EBIRR', 'CARD', 'WALLET');
-- CreateEnum
CREATE TYPE "PaymentIntentStatus" AS ENUM ('REQUIRES_ACTION', 'PROCESSING', 'SUCCEEDED', 'FAILED', 'CANCELLED');
-- CreateEnum
CREATE TYPE "WalletLedgerType" AS ENUM ('CREDIT', 'DEBIT');
-- CreateEnum
CREATE TYPE "NotificationCategory" AS ENUM ('BOOKING', 'PAYMENT', 'DISRUPTION', 'PROMOTION', 'SYSTEM');
-- CreateEnum
CREATE TYPE "StopStatus" AS ENUM ('COMPLETED', 'APPROACHING', 'CURRENT', 'UPCOMING');
-- CreateEnum
CREATE TYPE "SupportConversationStatus" AS ENUM ('OPEN', 'RESOLVED', 'CLOSED');
-- CreateEnum
CREATE TYPE "SupportSender" AS ENUM ('USER', 'BOT', 'AGENT');
-- CreateEnum
CREATE TYPE "LoyaltyTier" AS ENUM ('BRONZE', 'SILVER', 'GOLD', 'PLATINUM');
-- CreateEnum
CREATE TYPE "LoyaltyLedgerReason" AS ENUM ('TRIP_COMPLETED', 'REWARD_REDEEMED', 'PROMO_BONUS', 'MANUAL_ADJUSTMENT', 'EXPIRY');
-- CreateEnum
CREATE TYPE "FoodOrderStatus" AS ENUM ('PENDING', 'PREPARING', 'READY', 'DELIVERED', 'CANCELLED');
-- CreateEnum
CREATE TYPE "DevicePlatform" AS ENUM ('IOS', 'ANDROID', 'WEB');
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"phone" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"role" "UserRole" NOT NULL DEFAULT 'PASSENGER',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Passenger" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Passenger_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TravelerProfile" (
"id" TEXT NOT NULL,
"passengerId" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"relationship" TEXT NOT NULL,
"dateOfBirth" TIMESTAMP(3),
"nationalId" TEXT,
"notes" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "TravelerProfile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Station" (
"id" TEXT NOT NULL,
"code" TEXT NOT NULL,
"name" TEXT NOT NULL,
"city" TEXT NOT NULL,
"timezone" TEXT NOT NULL DEFAULT 'Africa/Addis_Ababa',
"lat" DECIMAL(9,6) NOT NULL,
"lng" DECIMAL(9,6) NOT NULL,
CONSTRAINT "Station_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TrainService" (
"id" TEXT NOT NULL,
"number" TEXT NOT NULL,
"name" TEXT NOT NULL,
"operatorId" TEXT NOT NULL DEFAULT 'op_edr',
CONSTRAINT "TrainService_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Trip" (
"id" TEXT NOT NULL,
"serviceId" TEXT NOT NULL,
"originStationId" TEXT NOT NULL,
"destinationStationId" TEXT NOT NULL,
"departureAt" TIMESTAMP(3) NOT NULL,
"arrivalAt" TIMESTAMP(3) NOT NULL,
"durationMinutes" INTEGER NOT NULL,
"status" "TripStatus" NOT NULL DEFAULT 'SCHEDULED',
"stopsCount" INTEGER NOT NULL DEFAULT 0,
"onTimePercent" INTEGER NOT NULL DEFAULT 100,
"carbonRating" TEXT NOT NULL DEFAULT 'A',
CONSTRAINT "Trip_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TripStopTime" (
"id" TEXT NOT NULL,
"tripId" TEXT NOT NULL,
"stationId" TEXT NOT NULL,
"sequence" INTEGER NOT NULL,
"plannedArrivalAt" TIMESTAMP(3),
"plannedDepartureAt" TIMESTAMP(3),
"actualArrivalAt" TIMESTAMP(3),
"status" "StopStatus" NOT NULL DEFAULT 'UPCOMING',
CONSTRAINT "TripStopTime_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TripLiveStatus" (
"id" TEXT NOT NULL,
"tripId" TEXT NOT NULL,
"state" TEXT NOT NULL,
"currentLocationLabel" TEXT,
"progressPercent" INTEGER NOT NULL DEFAULT 0,
"delayMinutes" INTEGER NOT NULL DEFAULT 0,
"currentSpeedKph" INTEGER,
"platformLabel" TEXT,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TripLiveStatus_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Coach" (
"id" TEXT NOT NULL,
"tripId" TEXT NOT NULL,
"label" TEXT NOT NULL,
"serviceClass" "ServiceClass" NOT NULL,
CONSTRAINT "Coach_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Seat" (
"id" TEXT NOT NULL,
"coachId" TEXT NOT NULL,
"row" INTEGER NOT NULL,
"col" TEXT NOT NULL,
"label" TEXT NOT NULL,
"kind" "SeatKind" NOT NULL DEFAULT 'STANDARD',
"status" "SeatStatus" NOT NULL DEFAULT 'AVAILABLE',
"heldUntil" TIMESTAMP(3),
CONSTRAINT "Seat_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SeatHold" (
"id" TEXT NOT NULL,
"tripId" TEXT NOT NULL,
"seatIds" TEXT[],
"fareQuoteId" TEXT,
"passengerId" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "SeatHold_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FareRule" (
"id" TEXT NOT NULL,
"tripId" TEXT,
"route" TEXT,
"serviceClass" "ServiceClass" NOT NULL,
"baseFareMinor" INTEGER NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'ETB',
"refundable" BOOLEAN NOT NULL DEFAULT true,
"validFrom" TIMESTAMP(3) NOT NULL,
"validUntil" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "FareRule_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Booking" (
"id" TEXT NOT NULL,
"bookingRef" TEXT NOT NULL,
"passengerId" TEXT NOT NULL,
"tripId" TEXT NOT NULL,
"status" "BookingStatus" NOT NULL DEFAULT 'DRAFT',
"currency" TEXT NOT NULL DEFAULT 'ETB',
"totalMinor" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Booking_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "BookingSeat" (
"id" TEXT NOT NULL,
"bookingId" TEXT NOT NULL,
"seatId" TEXT NOT NULL,
"passengerName" TEXT NOT NULL,
"idDocumentType" TEXT,
"idDocumentNumber" TEXT,
CONSTRAINT "BookingSeat_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PaymentMethod" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"type" "PaymentMethodType" NOT NULL,
"displayName" TEXT NOT NULL,
"maskedHint" TEXT,
"isDefault" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PaymentMethod_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PaymentIntent" (
"id" TEXT NOT NULL,
"bookingId" TEXT NOT NULL,
"amountMinor" INTEGER NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'ETB',
"method" "PaymentMethodType" NOT NULL,
"status" "PaymentIntentStatus" NOT NULL DEFAULT 'REQUIRES_ACTION',
"providerRef" TEXT,
"clientAction" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "PaymentIntent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Ticket" (
"id" TEXT NOT NULL,
"bookingId" TEXT NOT NULL,
"bookingRef" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'CONFIRMED',
"qrPayload" TEXT NOT NULL,
"issuedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"validatedAt" TIMESTAMP(3),
"validatorId" TEXT,
CONSTRAINT "Ticket_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LoyaltyAccount" (
"id" TEXT NOT NULL,
"passengerId" TEXT NOT NULL,
"pointsBalance" INTEGER NOT NULL DEFAULT 0,
"tier" "LoyaltyTier" NOT NULL DEFAULT 'BRONZE',
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "LoyaltyAccount_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LoyaltyLedgerEntry" (
"id" TEXT NOT NULL,
"accountId" TEXT NOT NULL,
"delta" INTEGER NOT NULL,
"reason" "LoyaltyLedgerReason" NOT NULL,
"bookingId" TEXT,
"balanceAfter" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "LoyaltyLedgerEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LoyaltyReward" (
"id" TEXT NOT NULL,
"accountId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"costPoints" INTEGER NOT NULL,
"available" BOOLEAN NOT NULL DEFAULT true,
"description" TEXT,
CONSTRAINT "LoyaltyReward_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WalletAccount" (
"id" TEXT NOT NULL,
"passengerId" TEXT NOT NULL,
"balanceMinor" INTEGER NOT NULL DEFAULT 0,
"currency" TEXT NOT NULL DEFAULT 'ETB',
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "WalletAccount_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WalletLedgerEntry" (
"id" TEXT NOT NULL,
"walletId" TEXT NOT NULL,
"type" "WalletLedgerType" NOT NULL,
"amountMinor" INTEGER NOT NULL,
"balanceAfterMinor" INTEGER NOT NULL,
"description" TEXT NOT NULL,
"relatedBookingId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "WalletLedgerEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Notification" (
"id" TEXT NOT NULL,
"passengerId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"body" TEXT NOT NULL,
"category" "NotificationCategory" NOT NULL,
"read" BOOLEAN NOT NULL DEFAULT false,
"deepLink" TEXT,
"metadata" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Notification_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Promotion" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"subtitle" TEXT,
"code" TEXT NOT NULL,
"percentOff" INTEGER,
"amountOffMinor" INTEGER,
"validUntil" TIMESTAMP(3) NOT NULL,
"ctaLabel" TEXT,
"deepLink" TEXT,
"active" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Promotion_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "StationCrowdSignal" (
"id" TEXT NOT NULL,
"stationId" TEXT NOT NULL,
"level" TEXT NOT NULL,
"label" TEXT NOT NULL,
"statusLabel" TEXT NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "StationCrowdSignal_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WeatherAlert" (
"id" TEXT NOT NULL,
"region" TEXT NOT NULL,
"severity" TEXT NOT NULL,
"title" TEXT NOT NULL,
"message" TEXT NOT NULL,
"validUntil" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "WeatherAlert_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MenuCategory" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "MenuCategory_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MenuItem" (
"id" TEXT NOT NULL,
"tripId" TEXT NOT NULL,
"categoryId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"priceMinor" INTEGER NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'ETB',
"available" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "MenuItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FoodOrder" (
"id" TEXT NOT NULL,
"bookingId" TEXT NOT NULL,
"status" "FoodOrderStatus" NOT NULL DEFAULT 'PENDING',
"totalMinor" INTEGER NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'ETB',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "FoodOrder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FoodOrderItem" (
"id" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"menuItemId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"quantity" INTEGER NOT NULL,
"lineTotalMinor" INTEGER NOT NULL,
CONSTRAINT "FoodOrderItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FaqCategory" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"iconKey" TEXT,
CONSTRAINT "FaqCategory_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FaqArticle" (
"id" TEXT NOT NULL,
"categoryId" TEXT NOT NULL,
"question" TEXT NOT NULL,
"answerMarkdown" TEXT NOT NULL,
"rank" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "FaqArticle_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SupportConversation" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"status" "SupportConversationStatus" NOT NULL DEFAULT 'OPEN',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "SupportConversation_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SupportMessage" (
"id" TEXT NOT NULL,
"conversationId" TEXT NOT NULL,
"sender" "SupportSender" NOT NULL,
"text" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "SupportMessage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "UserPreferences" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"pushEnabled" BOOLEAN NOT NULL DEFAULT true,
"emailEnabled" BOOLEAN NOT NULL DEFAULT true,
"smsEnabled" BOOLEAN NOT NULL DEFAULT false,
"promosEnabled" BOOLEAN NOT NULL DEFAULT true,
"biometricEnabled" BOOLEAN NOT NULL DEFAULT false,
"twoFactorEnabled" BOOLEAN NOT NULL DEFAULT false,
"defaultPaymentMethodId" TEXT,
"autoDownloadTickets" BOOLEAN NOT NULL DEFAULT true,
"dataSharing" BOOLEAN NOT NULL DEFAULT false,
"locale" TEXT NOT NULL DEFAULT 'en',
"darkMode" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "UserPreferences_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Device" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"platform" "DevicePlatform" NOT NULL,
"name" TEXT NOT NULL,
"pushToken" TEXT,
"trusted" BOOLEAN NOT NULL DEFAULT false,
"lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Device_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SavedRoute" (
"id" TEXT NOT NULL,
"passengerId" TEXT NOT NULL,
"fromStationId" TEXT NOT NULL,
"toStationId" TEXT NOT NULL,
"fromName" TEXT NOT NULL,
"toName" TEXT NOT NULL,
"tripCount" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "SavedRoute_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "User_phone_key" ON "User"("phone");
-- CreateIndex
CREATE UNIQUE INDEX "Session_token_key" ON "Session"("token");
-- CreateIndex
CREATE UNIQUE INDEX "Passenger_userId_key" ON "Passenger"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "Station_code_key" ON "Station"("code");
-- CreateIndex
CREATE UNIQUE INDEX "TrainService_number_key" ON "TrainService"("number");
-- CreateIndex
CREATE UNIQUE INDEX "TripStopTime_tripId_sequence_key" ON "TripStopTime"("tripId", "sequence");
-- CreateIndex
CREATE UNIQUE INDEX "TripLiveStatus_tripId_key" ON "TripLiveStatus"("tripId");
-- CreateIndex
CREATE UNIQUE INDEX "Coach_tripId_label_key" ON "Coach"("tripId", "label");
-- CreateIndex
CREATE UNIQUE INDEX "Seat_coachId_row_col_key" ON "Seat"("coachId", "row", "col");
-- CreateIndex
CREATE UNIQUE INDEX "Booking_bookingRef_key" ON "Booking"("bookingRef");
-- CreateIndex
CREATE UNIQUE INDEX "PaymentIntent_bookingId_key" ON "PaymentIntent"("bookingId");
-- CreateIndex
CREATE UNIQUE INDEX "Ticket_bookingId_key" ON "Ticket"("bookingId");
-- CreateIndex
CREATE UNIQUE INDEX "LoyaltyAccount_passengerId_key" ON "LoyaltyAccount"("passengerId");
-- CreateIndex
CREATE UNIQUE INDEX "WalletAccount_passengerId_key" ON "WalletAccount"("passengerId");
-- CreateIndex
CREATE UNIQUE INDEX "Promotion_code_key" ON "Promotion"("code");
-- CreateIndex
CREATE UNIQUE INDEX "UserPreferences_userId_key" ON "UserPreferences"("userId");
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Passenger" ADD CONSTRAINT "Passenger_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TravelerProfile" ADD CONSTRAINT "TravelerProfile_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Trip" ADD CONSTRAINT "Trip_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "TrainService"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Trip" ADD CONSTRAINT "Trip_originStationId_fkey" FOREIGN KEY ("originStationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Trip" ADD CONSTRAINT "Trip_destinationStationId_fkey" FOREIGN KEY ("destinationStationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TripStopTime" ADD CONSTRAINT "TripStopTime_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TripStopTime" ADD CONSTRAINT "TripStopTime_stationId_fkey" FOREIGN KEY ("stationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TripLiveStatus" ADD CONSTRAINT "TripLiveStatus_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Coach" ADD CONSTRAINT "Coach_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Seat" ADD CONSTRAINT "Seat_coachId_fkey" FOREIGN KEY ("coachId") REFERENCES "Coach"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BookingSeat" ADD CONSTRAINT "BookingSeat_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BookingSeat" ADD CONSTRAINT "BookingSeat_seatId_fkey" FOREIGN KEY ("seatId") REFERENCES "Seat"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PaymentIntent" ADD CONSTRAINT "PaymentIntent_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LoyaltyAccount" ADD CONSTRAINT "LoyaltyAccount_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LoyaltyLedgerEntry" ADD CONSTRAINT "LoyaltyLedgerEntry_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "LoyaltyAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LoyaltyReward" ADD CONSTRAINT "LoyaltyReward_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "LoyaltyAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WalletAccount" ADD CONSTRAINT "WalletAccount_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WalletLedgerEntry" ADD CONSTRAINT "WalletLedgerEntry_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "WalletAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "StationCrowdSignal" ADD CONSTRAINT "StationCrowdSignal_stationId_fkey" FOREIGN KEY ("stationId") REFERENCES "Station"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MenuItem" ADD CONSTRAINT "MenuItem_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MenuItem" ADD CONSTRAINT "MenuItem_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "MenuCategory"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FoodOrder" ADD CONSTRAINT "FoodOrder_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FoodOrderItem" ADD CONSTRAINT "FoodOrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "FoodOrder"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FaqArticle" ADD CONSTRAINT "FaqArticle_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "FaqCategory"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SupportMessage" ADD CONSTRAINT "SupportMessage_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "SupportConversation"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UserPreferences" ADD CONSTRAINT "UserPreferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Device" ADD CONSTRAINT "Device_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SavedRoute" ADD CONSTRAINT "SavedRoute_passengerId_fkey" FOREIGN KEY ("passengerId") REFERENCES "Passenger"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=reset-admin.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"reset-admin.d.ts","sourceRoot":"","sources":["reset-admin.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,49 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const client_1 = require("@prisma/client");
const bcrypt = __importStar(require("bcrypt"));
const prisma = new client_1.PrismaClient();
async function main() {
const passwordHash = await bcrypt.hash('admin123', 10);
const user = await prisma.user.upsert({
where: { email: 'admin@edr-platform.com' },
update: { passwordHash, role: 'ADMIN' },
create: { fullName: 'EDR Admin', email: 'admin@edr-platform.com', phone: '+251900000000', passwordHash, role: 'ADMIN' },
});
console.log('✅ Admin ready:', user.email);
}
main().catch(console.error).finally(() => prisma.$disconnect());
//# sourceMappingURL=reset-admin.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"reset-admin.js","sourceRoot":"","sources":["reset-admin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA8C;AAC9C,+CAAiC;AAEjC,MAAM,MAAM,GAAG,IAAI,qBAAY,EAAE,CAAC;AAElC,KAAK,UAAU,IAAI;IACjB,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QACpC,KAAK,EAAE,EAAE,KAAK,EAAE,wBAAwB,EAAE;QAC1C,MAAM,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE;QACvC,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,wBAAwB,EAAE,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE;KACxH,CAAC,CAAC;IACH,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC"}

View File

@@ -0,0 +1,16 @@
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
async function main() {
const passwordHash = await bcrypt.hash('admin123', 10);
const user = await prisma.user.upsert({
where: { email: 'admin@edr-platform.com' },
update: { passwordHash, role: 'ADMIN' },
create: { fullName: 'EDR Admin', email: 'admin@edr-platform.com', phone: '+251900000000', passwordHash, role: 'ADMIN' },
});
console.log('✅ Admin ready:', user.email);
}
main().catch(console.error).finally(() => prisma.$disconnect());

View File

@@ -0,0 +1,573 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum UserRole {
PASSENGER
ADMIN
STAFF
}
enum TripStatus {
SCHEDULED
BOARDING
EN_ROUTE
ARRIVED
CANCELLED
DELAYED
}
enum SeatKind {
STANDARD
PREMIUM
ACCESSIBLE
}
enum SeatStatus {
AVAILABLE
HELD
BOOKED
BLOCKED
}
enum ServiceClass {
ECONOMY
BUSINESS
FIRST
}
enum BookingStatus {
DRAFT
PENDING_PAYMENT
CONFIRMED
CANCELLED
COMPLETED
NO_SHOW
}
enum PaymentMethodType {
TELEBIRR
CBE_BIRR
EBIRR
CARD
WALLET
}
enum PaymentIntentStatus {
REQUIRES_ACTION
PROCESSING
SUCCEEDED
FAILED
CANCELLED
}
enum WalletLedgerType {
CREDIT
DEBIT
}
enum NotificationCategory {
BOOKING
PAYMENT
DISRUPTION
PROMOTION
SYSTEM
}
enum StopStatus {
COMPLETED
APPROACHING
CURRENT
UPCOMING
}
enum SupportConversationStatus {
OPEN
RESOLVED
CLOSED
}
enum SupportSender {
USER
BOT
AGENT
}
enum LoyaltyTier {
BRONZE
SILVER
GOLD
PLATINUM
}
enum LoyaltyLedgerReason {
TRIP_COMPLETED
REWARD_REDEEMED
PROMO_BONUS
MANUAL_ADJUSTMENT
EXPIRY
}
enum FoodOrderStatus {
PENDING
PREPARING
READY
DELIVERED
CANCELLED
}
enum DevicePlatform {
IOS
ANDROID
WEB
}
model User {
id String @id @default(uuid())
email String @unique
phone String @unique
fullName String
passwordHash String
role UserRole @default(PASSENGER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
passenger Passenger?
sessions Session[]
devices Device[]
preferences UserPreferences?
}
model Session {
id String @id @default(uuid())
userId String
token String @unique
expiresAt DateTime
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Passenger {
id String @id @default(uuid())
userId String @unique
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
bookings Booking[]
loyalty LoyaltyAccount?
wallet WalletAccount?
notifications Notification[]
travelerProfiles TravelerProfile[]
savedRoutes SavedRoute[]
}
model TravelerProfile {
id String @id @default(uuid())
passengerId String
fullName String
relationship String
dateOfBirth DateTime?
nationalId String?
notes String?
createdAt DateTime @default(now())
passenger Passenger @relation(fields: [passengerId], references: [id])
}
model Station {
id String @id @default(uuid())
code String @unique
name String
city String
timezone String @default("Africa/Addis_Ababa")
lat Decimal @db.Decimal(9, 6)
lng Decimal @db.Decimal(9, 6)
originTrips Trip[] @relation("OriginTrips")
destinationTrips Trip[] @relation("DestinationTrips")
stopTimes TripStopTime[]
crowdSignals StationCrowdSignal[]
}
model TrainService {
id String @id @default(uuid())
number String @unique
name String
operatorId String @default("op_edr")
trips Trip[]
}
model Trip {
id String @id @default(uuid())
serviceId String
originStationId String
destinationStationId String
departureAt DateTime
arrivalAt DateTime
durationMinutes Int
status TripStatus @default(SCHEDULED)
stopsCount Int @default(0)
onTimePercent Int @default(100)
carbonRating String @default("A")
service TrainService @relation(fields: [serviceId], references: [id])
originStation Station @relation("OriginTrips", fields: [originStationId], references: [id])
destinationStation Station @relation("DestinationTrips", fields: [destinationStationId], references: [id])
coaches Coach[]
bookings Booking[]
stopTimes TripStopTime[]
liveStatus TripLiveStatus?
menuItems MenuItem[]
}
model TripStopTime {
id String @id @default(uuid())
tripId String
stationId String
sequence Int
plannedArrivalAt DateTime?
plannedDepartureAt DateTime?
actualArrivalAt DateTime?
status StopStatus @default(UPCOMING)
trip Trip @relation(fields: [tripId], references: [id])
station Station @relation(fields: [stationId], references: [id])
@@unique([tripId, sequence])
}
model TripLiveStatus {
id String @id @default(uuid())
tripId String @unique
state String
currentLocationLabel String?
progressPercent Int @default(0)
delayMinutes Int @default(0)
currentSpeedKph Int?
platformLabel String?
updatedAt DateTime @updatedAt
trip Trip @relation(fields: [tripId], references: [id])
}
model Coach {
id String @id @default(uuid())
tripId String
label String
serviceClass ServiceClass
trip Trip @relation(fields: [tripId], references: [id])
seats Seat[]
@@unique([tripId, label])
}
model Seat {
id String @id @default(uuid())
coachId String
row Int
col String
label String
kind SeatKind @default(STANDARD)
status SeatStatus @default(AVAILABLE)
heldUntil DateTime?
coach Coach @relation(fields: [coachId], references: [id])
bookingSeats BookingSeat[]
@@unique([coachId, row, col])
}
model SeatHold {
id String @id @default(uuid())
tripId String
seatIds String[]
fareQuoteId String?
passengerId String
expiresAt DateTime
createdAt DateTime @default(now())
}
model FareRule {
id String @id @default(uuid())
tripId String?
route String?
serviceClass ServiceClass
baseFareMinor Int
currency String @default("ETB")
refundable Boolean @default(true)
validFrom DateTime
validUntil DateTime?
createdAt DateTime @default(now())
}
model Booking {
id String @id @default(uuid())
bookingRef String @unique
passengerId String
tripId String
status BookingStatus @default(DRAFT)
currency String @default("ETB")
totalMinor Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
passenger Passenger @relation(fields: [passengerId], references: [id])
trip Trip @relation(fields: [tripId], references: [id])
seats BookingSeat[]
paymentIntent PaymentIntent?
ticket Ticket?
foodOrders FoodOrder[]
}
model BookingSeat {
id String @id @default(uuid())
bookingId String
seatId String
passengerName String
idDocumentType String?
idDocumentNumber String?
booking Booking @relation(fields: [bookingId], references: [id])
seat Seat @relation(fields: [seatId], references: [id])
}
model PaymentMethod {
id String @id @default(uuid())
userId String
type PaymentMethodType
displayName String
maskedHint String?
isDefault Boolean @default(false)
createdAt DateTime @default(now())
}
model PaymentIntent {
id String @id @default(uuid())
bookingId String @unique
amountMinor Int
currency String @default("ETB")
method PaymentMethodType
status PaymentIntentStatus @default(REQUIRES_ACTION)
providerRef String?
clientAction Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
booking Booking @relation(fields: [bookingId], references: [id])
}
model Ticket {
id String @id @default(uuid())
bookingId String @unique
bookingRef String
status String @default("CONFIRMED")
qrPayload String
issuedAt DateTime @default(now())
validatedAt DateTime?
validatorId String?
booking Booking @relation(fields: [bookingId], references: [id])
}
model LoyaltyAccount {
id String @id @default(uuid())
passengerId String @unique
pointsBalance Int @default(0)
tier LoyaltyTier @default(BRONZE)
updatedAt DateTime @updatedAt
passenger Passenger @relation(fields: [passengerId], references: [id])
ledger LoyaltyLedgerEntry[]
rewards LoyaltyReward[]
}
model LoyaltyLedgerEntry {
id String @id @default(uuid())
accountId String
delta Int
reason LoyaltyLedgerReason
bookingId String?
balanceAfter Int
createdAt DateTime @default(now())
account LoyaltyAccount @relation(fields: [accountId], references: [id])
}
model LoyaltyReward {
id String @id @default(uuid())
accountId String
title String
costPoints Int
available Boolean @default(true)
description String?
account LoyaltyAccount @relation(fields: [accountId], references: [id])
}
model WalletAccount {
id String @id @default(uuid())
passengerId String @unique
balanceMinor Int @default(0)
currency String @default("ETB")
updatedAt DateTime @updatedAt
passenger Passenger @relation(fields: [passengerId], references: [id])
ledger WalletLedgerEntry[]
}
model WalletLedgerEntry {
id String @id @default(uuid())
walletId String
type WalletLedgerType
amountMinor Int
balanceAfterMinor Int
description String
relatedBookingId String?
createdAt DateTime @default(now())
wallet WalletAccount @relation(fields: [walletId], references: [id])
}
model Notification {
id String @id @default(uuid())
passengerId String
title String
body String
category NotificationCategory
read Boolean @default(false)
deepLink String?
metadata Json?
createdAt DateTime @default(now())
passenger Passenger @relation(fields: [passengerId], references: [id])
}
model Promotion {
id String @id @default(uuid())
title String
subtitle String?
code String @unique
percentOff Int?
amountOffMinor Int?
validUntil DateTime
ctaLabel String?
deepLink String?
active Boolean @default(true)
createdAt DateTime @default(now())
}
model StationCrowdSignal {
id String @id @default(uuid())
stationId String
level String
label String
statusLabel String
updatedAt DateTime @updatedAt
station Station @relation(fields: [stationId], references: [id])
}
model WeatherAlert {
id String @id @default(uuid())
region String
severity String
title String
message String
validUntil DateTime
createdAt DateTime @default(now())
}
model MenuCategory {
id String @id @default(uuid())
name String
items MenuItem[]
}
model MenuItem {
id String @id @default(uuid())
tripId String
categoryId String
name String
priceMinor Int
currency String @default("ETB")
available Boolean @default(true)
trip Trip @relation(fields: [tripId], references: [id])
category MenuCategory @relation(fields: [categoryId], references: [id])
}
model FoodOrder {
id String @id @default(uuid())
bookingId String
status FoodOrderStatus @default(PENDING)
totalMinor Int
currency String @default("ETB")
createdAt DateTime @default(now())
booking Booking @relation(fields: [bookingId], references: [id])
items FoodOrderItem[]
}
model FoodOrderItem {
id String @id @default(uuid())
orderId String
menuItemId String
name String
quantity Int
lineTotalMinor Int
order FoodOrder @relation(fields: [orderId], references: [id])
}
model FaqCategory {
id String @id @default(uuid())
title String
iconKey String?
articles FaqArticle[]
}
model FaqArticle {
id String @id @default(uuid())
categoryId String
question String
answerMarkdown String
rank Int @default(0)
category FaqCategory @relation(fields: [categoryId], references: [id])
}
model SupportConversation {
id String @id @default(uuid())
userId String
status SupportConversationStatus @default(OPEN)
createdAt DateTime @default(now())
messages SupportMessage[]
}
model SupportMessage {
id String @id @default(uuid())
conversationId String
sender SupportSender
text String
createdAt DateTime @default(now())
conversation SupportConversation @relation(fields: [conversationId], references: [id])
}
model UserPreferences {
id String @id @default(uuid())
userId String @unique
pushEnabled Boolean @default(true)
emailEnabled Boolean @default(true)
smsEnabled Boolean @default(false)
promosEnabled Boolean @default(true)
biometricEnabled Boolean @default(false)
twoFactorEnabled Boolean @default(false)
defaultPaymentMethodId String?
autoDownloadTickets Boolean @default(true)
dataSharing Boolean @default(false)
locale String @default("en")
darkMode Boolean @default(false)
user User @relation(fields: [userId], references: [id])
}
model Device {
id String @id @default(uuid())
userId String
platform DevicePlatform
name String
pushToken String?
trusted Boolean @default(false)
lastSeenAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
}
model SavedRoute {
id String @id @default(uuid())
passengerId String
fromStationId String
toStationId String
fromName String
toName String
tripCount Int @default(0)
createdAt DateTime @default(now())
passenger Passenger @relation(fields: [passengerId], references: [id])
}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=seed.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["seed.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,73 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const client_1 = require("@prisma/client");
const bcrypt = __importStar(require("bcrypt"));
const prisma = new client_1.PrismaClient();
async function main() {
const addis = await prisma.station.upsert({ where: { code: 'ADD' }, update: {}, create: { code: 'ADD', name: 'Addis Ababa', city: 'Addis Ababa', lat: 9.0054, lng: 38.7636 } });
const direDawa = await prisma.station.upsert({ where: { code: 'DDW' }, update: {}, create: { code: 'DDW', name: 'Dire Dawa', city: 'Dire Dawa', lat: 9.5931, lng: 41.8661 } });
const djibouti = await prisma.station.upsert({ where: { code: 'DJI' }, update: {}, create: { code: 'DJI', name: 'Djibouti', city: 'Djibouti', timezone: 'Africa/Djibouti', lat: 11.5720, lng: 43.1456 } });
const service = await prisma.trainService.upsert({ where: { number: '301' }, update: {}, create: { number: '301', name: 'Express 301' } });
const trip = await prisma.trip.create({
data: { serviceId: service.id, originStationId: addis.id, destinationStationId: djibouti.id, departureAt: new Date('2026-05-11T08:30:00Z'), arrivalAt: new Date('2026-05-11T20:00:00Z'), durationMinutes: 690, stopsCount: 1 },
});
for (const [label, cls] of [['A', 'ECONOMY'], ['B', 'BUSINESS']]) {
const coach = await prisma.coach.create({ data: { tripId: trip.id, label, serviceClass: cls } });
const seats = [];
for (let row = 1; row <= 10; row++) {
for (const col of ['A', 'B', 'C', 'D'])
seats.push({ coachId: coach.id, row, col, label: `${row}${col}` });
}
await prisma.seat.createMany({ data: seats });
}
await prisma.fareRule.create({ data: { tripId: trip.id, serviceClass: 'ECONOMY', baseFareMinor: 45000, validFrom: new Date('2026-01-01') } });
const hash = await bcrypt.hash('password123', 10);
const user = await prisma.user.upsert({ where: { email: 'kelemu@email.com' }, update: {}, create: { fullName: 'Kelemu Ketsela', email: 'kelemu@email.com', phone: '+251912345678', passwordHash: hash } });
let passenger = await prisma.passenger.findUnique({ where: { userId: user.id } });
if (!passenger) {
passenger = await prisma.passenger.create({ data: { userId: user.id } });
await prisma.loyaltyAccount.create({ data: { passengerId: passenger.id, pointsBalance: 2450, tier: 'SILVER' } });
await prisma.walletAccount.create({ data: { passengerId: passenger.id, balanceMinor: 125000 } });
}
await prisma.userPreferences.upsert({ where: { userId: user.id }, update: {}, create: { userId: user.id } });
await prisma.user.upsert({ where: { email: 'admin@edr-platform.com' }, update: { passwordHash: await bcrypt.hash('admin123', 10), role: 'ADMIN' }, create: { fullName: 'EDR Admin', email: 'admin@edr-platform.com', phone: '+251900000000', passwordHash: await bcrypt.hash('admin123', 10), role: 'ADMIN' } });
await prisma.promotion.upsert({ where: { code: 'WEEKEND15' }, update: {}, create: { title: 'Weekend Sale', code: 'WEEKEND15', percentOff: 15, validUntil: new Date('2026-12-31') } });
const faqCat = await prisma.faqCategory.create({ data: { title: 'Booking & Tickets', iconKey: 'description_outlined' } });
await prisma.faqArticle.create({ data: { categoryId: faqCat.id, question: 'How do I book a train ticket?', answerMarkdown: 'Open Search, pick stations and date, select seats, and proceed to payment.', rank: 1 } });
console.log('✅ Seed complete');
}
main().catch(console.error).finally(() => prisma.$disconnect());
//# sourceMappingURL=seed.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"seed.js","sourceRoot":"","sources":["seed.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,48 @@
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
async function main() {
const addis = await prisma.station.upsert({ where: { code: 'ADD' }, update: {}, create: { code: 'ADD', name: 'Addis Ababa', city: 'Addis Ababa', lat: 9.0054, lng: 38.7636 } });
const direDawa = await prisma.station.upsert({ where: { code: 'DDW' }, update: {}, create: { code: 'DDW', name: 'Dire Dawa', city: 'Dire Dawa', lat: 9.5931, lng: 41.8661 } });
const djibouti = await prisma.station.upsert({ where: { code: 'DJI' }, update: {}, create: { code: 'DJI', name: 'Djibouti', city: 'Djibouti', timezone: 'Africa/Djibouti', lat: 11.5720, lng: 43.1456 } });
const service = await prisma.trainService.upsert({ where: { number: '301' }, update: {}, create: { number: '301', name: 'Express 301' } });
const trip = await prisma.trip.create({
data: { serviceId: service.id, originStationId: addis.id, destinationStationId: djibouti.id, departureAt: new Date('2026-05-11T08:30:00Z'), arrivalAt: new Date('2026-05-11T20:00:00Z'), durationMinutes: 690, stopsCount: 1 },
});
for (const [label, cls] of [['A', 'ECONOMY'], ['B', 'BUSINESS']] as const) {
const coach = await prisma.coach.create({ data: { tripId: trip.id, label, serviceClass: cls } });
const seats = [];
for (let row = 1; row <= 10; row++) {
for (const col of ['A', 'B', 'C', 'D']) seats.push({ coachId: coach.id, row, col, label: `${row}${col}` });
}
await prisma.seat.createMany({ data: seats });
}
await prisma.fareRule.create({ data: { tripId: trip.id, serviceClass: 'ECONOMY', baseFareMinor: 45000, validFrom: new Date('2026-01-01') } });
const hash = await bcrypt.hash('password123', 10);
const user = await prisma.user.upsert({ where: { email: 'kelemu@email.com' }, update: {}, create: { fullName: 'Kelemu Ketsela', email: 'kelemu@email.com', phone: '+251912345678', passwordHash: hash } });
let passenger = await prisma.passenger.findUnique({ where: { userId: user.id } });
if (!passenger) {
passenger = await prisma.passenger.create({ data: { userId: user.id } });
await prisma.loyaltyAccount.create({ data: { passengerId: passenger.id, pointsBalance: 2450, tier: 'SILVER' } });
await prisma.walletAccount.create({ data: { passengerId: passenger.id, balanceMinor: 125000 } });
}
await prisma.userPreferences.upsert({ where: { userId: user.id }, update: {}, create: { userId: user.id } });
await prisma.user.upsert({ where: { email: 'admin@edr-platform.com' }, update: { passwordHash: await bcrypt.hash('admin123', 10), role: 'ADMIN' }, create: { fullName: 'EDR Admin', email: 'admin@edr-platform.com', phone: '+251900000000', passwordHash: await bcrypt.hash('admin123', 10), role: 'ADMIN' } });
await prisma.promotion.upsert({ where: { code: 'WEEKEND15' }, update: {}, create: { title: 'Weekend Sale', code: 'WEEKEND15', percentOff: 15, validUntil: new Date('2026-12-31') } });
const faqCat = await prisma.faqCategory.create({ data: { title: 'Booking & Tickets', iconKey: 'description_outlined' } });
await prisma.faqArticle.create({ data: { categoryId: faqCat.id, question: 'How do I book a train ticket?', answerMarkdown: 'Open Search, pick stations and date, select seats, and proceed to payment.', rank: 1 } });
console.log('✅ Seed complete');
}
main().catch(console.error).finally(() => prisma.$disconnect());