fix: add missing domain ports files that were gitignored
Some checks failed
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Deployment Summary (push) Blocked by required conditions
CI/CD Pipeline / Backend - Build, Test & Push (push) Failing after 2m11s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Has been cancelled

Root cause: The .gitignore had 'out/' which was ignoring ALL directories
named 'out', including 'src/domain/ports/out/' which contains critical
port interfaces for hexagonal architecture.

Changes:
- Modified .gitignore to only ignore Next.js output directories
- Added all 17 missing files from src/domain/ports/out/
  - audit-log.repository.ts
  - booking.repository.ts
  - cache.port.ts
  - carrier-connector.port.ts
  - carrier.repository.ts
  - csv-booking.repository.ts
  - csv-rate-loader.port.ts
  - email.port.ts
  - index.ts
  - notification.repository.ts
  - organization.repository.ts
  - pdf.port.ts
  - port.repository.ts
  - rate-quote.repository.ts
  - storage.port.ts
  - user.repository.ts
  - webhook.repository.ts

This resolves all "Cannot find module '@domain/ports/out/*'" TypeScript errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
David 2025-11-17 01:50:05 +01:00
parent 62cad30fc2
commit ee38ee6961
18 changed files with 1048 additions and 1 deletions

4
.gitignore vendored
View File

@ -12,7 +12,9 @@ coverage/
dist/ dist/
build/ build/
.next/ .next/
out/ # Only ignore Next.js output directory, not all 'out' folders
/.next/out/
/apps/frontend/out/
# Environment variables # Environment variables
.env .env

View File

@ -0,0 +1,59 @@
/**
* Audit Log Repository Port
*
* Defines the interface for Audit Log persistence operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
import { AuditLog } from '../../entities/audit-log.entity';
export const AUDIT_LOG_REPOSITORY = 'AuditLogRepository';
export interface AuditLogFilters {
userId?: string;
organizationId?: string;
action?: string[];
resourceType?: string;
resourceId?: string;
dateFrom?: Date;
dateTo?: Date;
limit?: number;
offset?: number;
}
export interface AuditLogRepository {
/**
* Save an audit log entry
*/
save(auditLog: AuditLog): Promise<void>;
/**
* Find audit log by ID
*/
findById(id: string): Promise<AuditLog | null>;
/**
* Find audit logs by filters
*/
findByFilters(filters: AuditLogFilters): Promise<AuditLog[]>;
/**
* Count audit logs matching filters
*/
count(filters: AuditLogFilters): Promise<number>;
/**
* Find audit logs for a specific resource
*/
findByResource(resourceType: string, resourceId: string): Promise<AuditLog[]>;
/**
* Find recent audit logs for an organization
*/
findRecentByOrganization(organizationId: string, limit: number): Promise<AuditLog[]>;
/**
* Find audit logs by user
*/
findByUser(userId: string, limit: number): Promise<AuditLog[]>;
}

View File

@ -0,0 +1,49 @@
/**
* Booking Repository Port
*
* Defines the interface for Booking persistence operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
import { Booking } from '../../entities/booking.entity';
import { BookingNumber } from '../../value-objects/booking-number.vo';
import { BookingStatus } from '../../value-objects/booking-status.vo';
export const BOOKING_REPOSITORY = 'BookingRepository';
export interface BookingRepository {
/**
* Save a booking entity
*/
save(booking: Booking): Promise<Booking>;
/**
* Find booking by ID
*/
findById(id: string): Promise<Booking | null>;
/**
* Find booking by booking number
*/
findByBookingNumber(bookingNumber: BookingNumber): Promise<Booking | null>;
/**
* Find all bookings for a specific user
*/
findByUser(userId: string): Promise<Booking[]>;
/**
* Find all bookings for an organization
*/
findByOrganization(organizationId: string): Promise<Booking[]>;
/**
* Find all bookings with a specific status
*/
findByStatus(status: BookingStatus): Promise<Booking[]>;
/**
* Delete booking by ID
*/
delete(id: string): Promise<void>;
}

View File

@ -0,0 +1,60 @@
/**
* Cache Port
*
* Defines the interface for caching operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
export const CACHE_PORT = 'CachePort';
export interface CachePort {
/**
* Get a value from cache
* Returns null if key doesn't exist
*/
get<T>(key: string): Promise<T | null>;
/**
* Set a value in cache
* @param key - Cache key
* @param value - Value to store
* @param ttlSeconds - Time to live in seconds (optional)
*/
set<T>(key: string, value: T, ttlSeconds?: number): Promise<void>;
/**
* Delete a key from cache
*/
delete(key: string): Promise<void>;
/**
* Delete multiple keys from cache
*/
deleteMany(keys: string[]): Promise<void>;
/**
* Check if a key exists in cache
*/
exists(key: string): Promise<boolean>;
/**
* Get time to live for a key (in seconds)
* Returns -2 if key doesn't exist, -1 if key has no expiration
*/
ttl(key: string): Promise<number>;
/**
* Clear all cache entries
*/
clear(): Promise<void>;
/**
* Get cache statistics
*/
getStats(): Promise<{
hits: number;
misses: number;
hitRate: number;
keyCount: number;
}>;
}

View File

@ -0,0 +1,64 @@
/**
* Carrier Connector Port
*
* Defines the interface for carrier API integrations.
* This is a secondary port (output port) in hexagonal architecture.
*/
import { RateQuote } from '../../entities/rate-quote.entity';
export const CARRIER_CONNECTOR_PORT = 'CarrierConnectorPort';
export interface CarrierRateSearchInput {
origin: string;
destination: string;
containerType: string;
departureDate: Date;
quantity?: number;
mode?: string; // 'FCL' or 'LCL'
weight?: number; // Weight in kg
volume?: number; // Volume in CBM
isHazmat?: boolean; // Hazardous materials flag
imoClass?: string; // IMO class for hazmat
hazardous?: boolean;
reefer?: boolean;
commodityType?: string;
}
export interface CarrierAvailabilityInput {
origin: string;
destination: string;
containerType: string;
startDate: Date;
endDate: Date;
departureDate?: Date; // Specific departure date
quantity?: number; // Number of containers
}
export interface CarrierConnectorPort {
/**
* Get the carrier name
*/
getCarrierName(): string;
/**
* Get the carrier code
*/
getCarrierCode(): string;
/**
* Search for shipping rates
*/
searchRates(input: CarrierRateSearchInput): Promise<RateQuote[]>;
/**
* Check container availability
* Returns the number of available containers
*/
checkAvailability(input: CarrierAvailabilityInput): Promise<number>;
/**
* Health check to verify carrier API is accessible
*/
healthCheck(): Promise<boolean>;
}

View File

@ -0,0 +1,62 @@
/**
* Carrier Repository Port
*
* Defines the interface for Carrier persistence operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
import { Carrier } from '../../entities/carrier.entity';
export const CARRIER_REPOSITORY = 'CarrierRepository';
export interface CarrierRepository {
/**
* Save a carrier entity
*/
save(carrier: Carrier): Promise<Carrier>;
/**
* Save multiple carrier entities
*/
saveMany(carriers: Carrier[]): Promise<Carrier[]>;
/**
* Find carrier by ID
*/
findById(id: string): Promise<Carrier | null>;
/**
* Find carrier by carrier code
*/
findByCode(code: string): Promise<Carrier | null>;
/**
* Find carrier by SCAC (Standard Carrier Alpha Code)
*/
findByScac(scac: string): Promise<Carrier | null>;
/**
* Find all active carriers
*/
findAllActive(): Promise<Carrier[]>;
/**
* Find all carriers that support API integration
*/
findWithApiSupport(): Promise<Carrier[]>;
/**
* Find all carriers (including inactive)
*/
findAll(): Promise<Carrier[]>;
/**
* Update a carrier entity
*/
update(carrier: Carrier): Promise<Carrier>;
/**
* Delete carrier by ID
*/
deleteById(id: string): Promise<void>;
}

View File

@ -0,0 +1,87 @@
import { CsvBooking } from '../../entities/csv-booking.entity';
/**
* CSV Booking Repository Port (Output Port)
*
* Interface for CSV booking persistence operations.
* Implemented by infrastructure layer.
*
* This port abstracts database operations from the domain layer.
*/
export interface CsvBookingRepositoryPort {
/**
* Create a new booking
* @param booking - Booking to create
* @returns Created booking with generated ID
*/
create(booking: CsvBooking): Promise<CsvBooking>;
/**
* Find booking by ID
* @param id - Booking ID
* @returns Booking if found, null otherwise
*/
findById(id: string): Promise<CsvBooking | null>;
/**
* Find booking by confirmation token
* @param token - Confirmation token from email link
* @returns Booking if found, null otherwise
*/
findByToken(token: string): Promise<CsvBooking | null>;
/**
* Find all bookings for a user
* @param userId - User ID
* @returns Array of bookings
*/
findByUserId(userId: string): Promise<CsvBooking[]>;
/**
* Find all bookings for an organization
* @param organizationId - Organization ID
* @returns Array of bookings
*/
findByOrganizationId(organizationId: string): Promise<CsvBooking[]>;
/**
* Find bookings by status
* @param status - Booking status
* @returns Array of bookings with matching status
*/
findByStatus(status: string): Promise<CsvBooking[]>;
/**
* Find pending bookings that are about to expire
* @param daysUntilExpiration - Number of days until expiration (e.g., 2)
* @returns Array of bookings expiring soon
*/
findExpiringSoon(daysUntilExpiration: number): Promise<CsvBooking[]>;
/**
* Update existing booking
* @param booking - Booking with updated data
* @returns Updated booking
*/
update(booking: CsvBooking): Promise<CsvBooking>;
/**
* Delete booking
* @param id - Booking ID
*/
delete(id: string): Promise<void>;
/**
* Count bookings by status for a user
* @param userId - User ID
* @returns Object with counts per status
*/
countByStatusForUser(userId: string): Promise<Record<string, number>>;
/**
* Count bookings by status for an organization
* @param organizationId - Organization ID
* @returns Object with counts per status
*/
countByStatusForOrganization(organizationId: string): Promise<Record<string, number>>;
}

View File

@ -0,0 +1,44 @@
import { CsvRate } from '../../entities/csv-rate.entity';
/**
* CSV Rate Loader Port (Output Port)
*
* Interface for loading rates from CSV files.
* Implemented by infrastructure layer.
*
* This port abstracts CSV file operations from the domain layer.
*/
export interface CsvRateLoaderPort {
/**
* Load all rates from a CSV file
* @param filePath - Absolute or relative path to CSV file
* @param companyEmail - Email address for the company (stored in config metadata)
* @returns Array of CSV rates
* @throws Error if file cannot be read or parsed
*/
loadRatesFromCsv(filePath: string, companyEmail: string): Promise<CsvRate[]>;
/**
* Load rates for a specific company
* @param companyName - Name of the carrier company
* @returns Array of CSV rates for the company
*/
loadRatesByCompany(companyName: string): Promise<CsvRate[]>;
/**
* Validate CSV file structure without fully parsing
* @param filePath - Path to CSV file
* @returns Validation result with errors if any
*/
validateCsvFile(filePath: string): Promise<{
valid: boolean;
errors: string[];
rowCount?: number;
}>;
/**
* Get list of all available CSV rate files
* @returns Array of file paths
*/
getAvailableCsvFiles(): Promise<string[]>;
}

View File

@ -0,0 +1,92 @@
/**
* Email Port
*
* Defines the interface for email sending operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
export const EMAIL_PORT = 'EmailPort';
export interface EmailAttachment {
filename: string;
content: Buffer | string;
contentType?: string;
}
export interface EmailOptions {
to: string | string[];
cc?: string | string[];
bcc?: string | string[];
replyTo?: string;
subject: string;
html?: string;
text?: string;
attachments?: EmailAttachment[];
}
export interface EmailPort {
/**
* Send an email
*/
send(options: EmailOptions): Promise<void>;
/**
* Send booking confirmation email
*/
sendBookingConfirmation(
email: string,
bookingNumber: string,
bookingDetails: any,
pdfAttachment?: Buffer
): Promise<void>;
/**
* Send email verification email
*/
sendVerificationEmail(email: string, token: string): Promise<void>;
/**
* Send password reset email
*/
sendPasswordResetEmail(email: string, token: string): Promise<void>;
/**
* Send welcome email
*/
sendWelcomeEmail(email: string, firstName: string): Promise<void>;
/**
* Send user invitation email
*/
sendUserInvitation(
email: string,
organizationName: string,
inviterName: string,
tempPassword: string
): Promise<void>;
/**
* Send CSV booking request email to carrier
*/
sendCsvBookingRequest(
carrierEmail: string,
bookingDetails: {
bookingId: string;
origin: string;
destination: string;
volumeCBM: number;
weightKG: number;
palletCount: number;
priceUSD: number;
priceEUR: number;
primaryCurrency: string;
transitDays: number;
containerType: string;
documents: Array<{
type: string;
fileName: string;
}>;
confirmationToken: string;
}
): Promise<void>;
}

View File

@ -0,0 +1,25 @@
/**
* Domain Ports (Output) - Barrel Export
*
* Exports all output port interfaces and tokens for easy importing.
*/
// Repository Ports
export * from './user.repository';
export * from './booking.repository';
export * from './rate-quote.repository';
export * from './organization.repository';
export * from './port.repository';
export * from './carrier.repository';
export * from './notification.repository';
export * from './audit-log.repository';
export * from './webhook.repository';
export * from './csv-booking.repository';
// Infrastructure Ports
export * from './cache.port';
export * from './email.port';
export * from './pdf.port';
export * from './storage.port';
export * from './carrier-connector.port';
export * from './csv-rate-loader.port';

View File

@ -0,0 +1,80 @@
/**
* Notification Repository Port
*
* Defines the interface for Notification persistence operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
import { Notification } from '../../entities/notification.entity';
export const NOTIFICATION_REPOSITORY = 'NotificationRepository';
export interface NotificationFilters {
userId?: string;
organizationId?: string;
type?: string[];
read?: boolean;
priority?: string[];
startDate?: Date;
endDate?: Date;
offset?: number;
limit?: number;
}
export interface NotificationRepository {
/**
* Save a notification entity
*/
save(notification: Notification): Promise<void>;
/**
* Find notification by ID
*/
findById(id: string): Promise<Notification | null>;
/**
* Find notifications by filters
*/
findByFilters(filters: NotificationFilters): Promise<Notification[]>;
/**
* Count notifications matching filters
*/
count(filters: NotificationFilters): Promise<number>;
/**
* Find unread notifications for a user
*/
findUnreadByUser(userId: string, limit?: number): Promise<Notification[]>;
/**
* Count unread notifications for a user
*/
countUnreadByUser(userId: string): Promise<number>;
/**
* Find recent notifications for a user
*/
findRecentByUser(userId: string, limit?: number): Promise<Notification[]>;
/**
* Mark a notification as read
*/
markAsRead(id: string): Promise<void>;
/**
* Mark all notifications as read for a user
*/
markAllAsReadForUser(userId: string): Promise<void>;
/**
* Delete a notification
*/
delete(id: string): Promise<void>;
/**
* Delete old read notifications
* Returns the number of deleted records
*/
deleteOldReadNotifications(olderThanDays: number): Promise<number>;
}

View File

@ -0,0 +1,62 @@
/**
* Organization Repository Port
*
* Defines the interface for Organization persistence operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
import { Organization } from '../../entities/organization.entity';
export const ORGANIZATION_REPOSITORY = 'OrganizationRepository';
export interface OrganizationRepository {
/**
* Save an organization entity
*/
save(organization: Organization): Promise<Organization>;
/**
* Find organization by ID
*/
findById(id: string): Promise<Organization | null>;
/**
* Find organization by name
*/
findByName(name: string): Promise<Organization | null>;
/**
* Find organization by SCAC (Standard Carrier Alpha Code)
*/
findBySCAC(scac: string): Promise<Organization | null>;
/**
* Find all organizations
*/
findAll(): Promise<Organization[]>;
/**
* Find all active organizations
*/
findAllActive(): Promise<Organization[]>;
/**
* Find organizations by type (e.g., 'FREIGHT_FORWARDER', 'CARRIER')
*/
findByType(type: string): Promise<Organization[]>;
/**
* Update an organization entity
*/
update(organization: Organization): Promise<Organization>;
/**
* Delete organization by ID
*/
deleteById(id: string): Promise<void>;
/**
* Count total organizations
*/
count(): Promise<number>;
}

View File

@ -0,0 +1,66 @@
/**
* PDF Port
*
* Defines the interface for PDF generation operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
export const PDF_PORT = 'PdfPort';
export interface BookingPdfData {
bookingNumber: string;
bookingDate: Date;
origin: {
code: string;
name: string;
};
destination: {
code: string;
name: string;
};
carrier: {
name: string;
logo?: string;
};
etd: Date;
eta: Date;
transitDays: number;
shipper: {
name: string;
address: string;
contact: string;
email: string;
phone: string;
};
consignee: {
name: string;
address: string;
contact: string;
email: string;
phone: string;
};
containers: Array<{
type: string;
quantity: number;
containerNumber?: string;
sealNumber?: string;
}>;
cargoDescription: string;
specialInstructions?: string;
price: {
amount: number;
currency: string;
};
}
export interface PdfPort {
/**
* Generate booking confirmation PDF
*/
generateBookingConfirmation(data: BookingPdfData): Promise<Buffer>;
/**
* Generate rate quote comparison PDF
*/
generateRateQuoteComparison(quotes: any[]): Promise<Buffer>;
}

View File

@ -0,0 +1,58 @@
/**
* Port Repository Port
*
* Defines the interface for Port (maritime port) persistence operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
import { Port } from '../../entities/port.entity';
export const PORT_REPOSITORY = 'PortRepository';
export interface PortRepository {
/**
* Save a port entity
*/
save(port: Port): Promise<Port>;
/**
* Save multiple port entities
*/
saveMany(ports: Port[]): Promise<Port[]>;
/**
* Find port by UN LOCODE
*/
findByCode(code: string): Promise<Port | null>;
/**
* Find multiple ports by codes
*/
findByCodes(codes: string[]): Promise<Port[]>;
/**
* Search ports by query string (name, city, or code)
* with optional country filter and limit
*/
search(query: string, limit?: number, countryFilter?: string): Promise<Port[]>;
/**
* Find all active ports
*/
findAllActive(): Promise<Port[]>;
/**
* Find all ports in a specific country
*/
findByCountry(countryCode: string): Promise<Port[]>;
/**
* Count total ports
*/
count(): Promise<number>;
/**
* Delete port by code
*/
deleteByCode(code: string): Promise<void>;
}

View File

@ -0,0 +1,53 @@
/**
* RateQuote Repository Port
*
* Defines the interface for RateQuote persistence operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
import { RateQuote } from '../../entities/rate-quote.entity';
export const RATE_QUOTE_REPOSITORY = 'RateQuoteRepository';
export interface RateQuoteRepository {
/**
* Save a rate quote entity
*/
save(rateQuote: RateQuote): Promise<RateQuote>;
/**
* Save multiple rate quote entities
*/
saveMany(rateQuotes: RateQuote[]): Promise<RateQuote[]>;
/**
* Find rate quote by ID
*/
findById(id: string): Promise<RateQuote | null>;
/**
* Find rate quotes by search criteria
*/
findBySearchCriteria(criteria: {
origin: string;
destination: string;
containerType: string;
departureDate: Date;
}): Promise<RateQuote[]>;
/**
* Find all rate quotes for a specific carrier
*/
findByCarrier(carrierId: string): Promise<RateQuote[]>;
/**
* Delete expired rate quotes
* Returns the number of deleted records
*/
deleteExpired(): Promise<number>;
/**
* Delete rate quote by ID
*/
deleteById(id: string): Promise<void>;
}

View File

@ -0,0 +1,69 @@
/**
* Storage Port
*
* Defines the interface for object storage operations (S3, MinIO, etc.).
* This is a secondary port (output port) in hexagonal architecture.
*/
export const STORAGE_PORT = 'StoragePort';
export interface UploadOptions {
bucket: string;
key: string;
body: Buffer | string;
contentType?: string;
metadata?: Record<string, string>;
acl?: string;
}
export interface DownloadOptions {
bucket: string;
key: string;
}
export interface DeleteOptions {
bucket: string;
key: string;
}
export interface StorageObject {
key: string;
url: string;
size: number;
contentType?: string;
lastModified?: Date;
}
export interface StoragePort {
/**
* Upload a file to storage
*/
upload(options: UploadOptions): Promise<StorageObject>;
/**
* Download a file from storage
*/
download(options: DownloadOptions): Promise<Buffer>;
/**
* Delete a file from storage
*/
delete(options: DeleteOptions): Promise<void>;
/**
* Get a signed URL for temporary access
* @param options - Download options
* @param expiresIn - URL expiration in seconds (default: 3600)
*/
getSignedUrl(options: DownloadOptions, expiresIn?: number): Promise<string>;
/**
* Check if a file exists in storage
*/
exists(options: DownloadOptions): Promise<boolean>;
/**
* List objects in a bucket
*/
list(bucket: string, prefix?: string): Promise<StorageObject[]>;
}

View File

@ -0,0 +1,62 @@
/**
* User Repository Port
*
* Defines the interface for User persistence operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
import { User } from '../../entities/user.entity';
export const USER_REPOSITORY = 'UserRepository';
export interface UserRepository {
/**
* Save a user entity
*/
save(user: User): Promise<User>;
/**
* Find user by ID
*/
findById(id: string): Promise<User | null>;
/**
* Find user by email address
*/
findByEmail(email: string): Promise<User | null>;
/**
* Find all users belonging to an organization
*/
findByOrganization(organizationId: string): Promise<User[]>;
/**
* Find all users with a specific role
*/
findByRole(role: string): Promise<User[]>;
/**
* Find all active users
*/
findAllActive(): Promise<User[]>;
/**
* Update a user entity
*/
update(user: User): Promise<User>;
/**
* Delete user by ID
*/
deleteById(id: string): Promise<void>;
/**
* Count users in an organization
*/
countByOrganization(organizationId: string): Promise<number>;
/**
* Check if email exists
*/
emailExists(email: string): Promise<boolean>;
}

View File

@ -0,0 +1,53 @@
/**
* Webhook Repository Port
*
* Defines the interface for Webhook persistence operations.
* This is a secondary port (output port) in hexagonal architecture.
*/
import { Webhook, WebhookEvent } from '../../entities/webhook.entity';
export const WEBHOOK_REPOSITORY = 'WebhookRepository';
export interface WebhookFilters {
organizationId?: string;
status?: string[];
event?: WebhookEvent;
}
export interface WebhookRepository {
/**
* Save a webhook entity
*/
save(webhook: Webhook): Promise<void>;
/**
* Find webhook by ID
*/
findById(id: string): Promise<Webhook | null>;
/**
* Find all webhooks for an organization
*/
findByOrganization(organizationId: string): Promise<Webhook[]>;
/**
* Find active webhooks by event and organization
*/
findActiveByEvent(event: WebhookEvent, organizationId: string): Promise<Webhook[]>;
/**
* Find webhooks by filters
*/
findByFilters(filters: WebhookFilters): Promise<Webhook[]>;
/**
* Delete a webhook
*/
delete(id: string): Promise<void>;
/**
* Count webhooks for an organization
*/
countByOrganization(organizationId: string): Promise<number>;
}