301 lines
6.9 KiB
TypeScript
301 lines
6.9 KiB
TypeScript
/**
|
|
* rVnb Automerge document schemas.
|
|
*
|
|
* Community RV & camper rentals — trust-based vehicle sharing.
|
|
* Granularity: one Automerge document per space (vehicles + rentals + endorsements).
|
|
* DocId format: {space}:vnb:vehicles
|
|
*/
|
|
|
|
import type { DocSchema } from '../../shared/local-first/document';
|
|
|
|
// ── Economy model ──
|
|
|
|
export type EconomyModel = 'gift' | 'suggested' | 'fixed' | 'sliding_scale' | 'exchange';
|
|
|
|
// ── Vehicle types ──
|
|
|
|
export type VehicleType =
|
|
| 'motorhome'
|
|
| 'camper_van'
|
|
| 'travel_trailer'
|
|
| 'truck_camper'
|
|
| 'skoolie'
|
|
| 'other';
|
|
|
|
// ── Fuel types ──
|
|
|
|
export type FuelType = 'gas' | 'diesel' | 'electric' | 'hybrid' | 'propane' | 'other';
|
|
|
|
// ── Mileage policy ──
|
|
|
|
export type MileagePolicy = 'unlimited' | 'per_mile' | 'included_miles';
|
|
|
|
// ── Rental request status ──
|
|
|
|
export type RentalStatus =
|
|
| 'pending'
|
|
| 'accepted'
|
|
| 'declined'
|
|
| 'cancelled'
|
|
| 'completed'
|
|
| 'endorsed';
|
|
|
|
// ── Endorsement visibility ──
|
|
|
|
export type EndorsementVisibility = 'public' | 'private' | 'community';
|
|
|
|
// ── Core types ──
|
|
|
|
export interface TripWindow {
|
|
id: string;
|
|
vehicleId: string;
|
|
startDate: number; // epoch ms (start of day)
|
|
endDate: number; // epoch ms (end of day)
|
|
status: 'available' | 'blocked' | 'tentative';
|
|
pickupLocationName: string | null;
|
|
pickupLat: number | null;
|
|
pickupLng: number | null;
|
|
dropoffLocationName: string | null;
|
|
dropoffLat: number | null;
|
|
dropoffLng: number | null;
|
|
notes: string | null;
|
|
createdAt: number;
|
|
}
|
|
|
|
export interface Vehicle {
|
|
id: string;
|
|
ownerDid: string; // DID of the vehicle owner
|
|
ownerName: string;
|
|
title: string;
|
|
description: string;
|
|
type: VehicleType;
|
|
economy: EconomyModel;
|
|
|
|
// Vehicle specs
|
|
year: number | null;
|
|
make: string | null;
|
|
model: string | null;
|
|
lengthFeet: number | null;
|
|
sleeps: number;
|
|
fuelType: FuelType | null;
|
|
|
|
// Amenities (boolean flags)
|
|
hasGenerator: boolean;
|
|
hasSolar: boolean;
|
|
hasAC: boolean;
|
|
hasHeating: boolean;
|
|
hasShower: boolean;
|
|
hasToilet: boolean;
|
|
hasKitchen: boolean;
|
|
petFriendly: boolean;
|
|
towRequired: boolean;
|
|
|
|
// Mileage policy
|
|
mileagePolicy: MileagePolicy;
|
|
includedMiles: number | null;
|
|
perMileRate: number | null;
|
|
|
|
// Pricing (relevant for non-gift economies)
|
|
suggestedAmount: number | null; // suggested/fixed price per night
|
|
currency: string | null;
|
|
slidingMin: number | null;
|
|
slidingMax: number | null;
|
|
exchangeDescription: string | null;
|
|
|
|
// Pickup / Dropoff
|
|
pickupLocationName: string;
|
|
pickupLocationLat: number | null;
|
|
pickupLocationLng: number | null;
|
|
dropoffSameAsPickup: boolean;
|
|
dropoffLocationName: string | null;
|
|
dropoffLocationLat: number | null;
|
|
dropoffLocationLng: number | null;
|
|
|
|
// Photos
|
|
photos: string[];
|
|
coverPhoto: string | null;
|
|
|
|
// Trust & auto-accept
|
|
trustThreshold: number | null;
|
|
instantAccept: boolean;
|
|
|
|
// Metadata
|
|
isActive: boolean;
|
|
createdAt: number;
|
|
updatedAt: number;
|
|
}
|
|
|
|
export interface RentalMessage {
|
|
id: string;
|
|
senderDid: string;
|
|
senderName: string;
|
|
body: string;
|
|
sentAt: number;
|
|
}
|
|
|
|
export interface RentalRequest {
|
|
id: string;
|
|
vehicleId: string;
|
|
renterDid: string;
|
|
renterName: string;
|
|
ownerDid: string;
|
|
|
|
// Dates
|
|
pickupDate: number; // epoch ms
|
|
dropoffDate: number; // epoch ms
|
|
estimatedMiles: number | null;
|
|
|
|
// Requested locations
|
|
requestedPickupLocation: string | null;
|
|
requestedPickupLat: number | null;
|
|
requestedPickupLng: number | null;
|
|
requestedDropoffLocation: string | null;
|
|
requestedDropoffLat: number | null;
|
|
requestedDropoffLng: number | null;
|
|
|
|
// Status flow: pending -> accepted/declined -> completed -> endorsed
|
|
status: RentalStatus;
|
|
|
|
// Messages embedded in the CRDT
|
|
messages: RentalMessage[];
|
|
|
|
// Contribution (for non-gift economies)
|
|
offeredAmount: number | null;
|
|
offeredCurrency: string | null;
|
|
offeredExchange: string | null;
|
|
|
|
// Timestamps
|
|
requestedAt: number;
|
|
respondedAt: number | null;
|
|
completedAt: number | null;
|
|
cancelledAt: number | null;
|
|
}
|
|
|
|
export interface Endorsement {
|
|
id: string;
|
|
rentalId: string;
|
|
vehicleId: string;
|
|
|
|
// Who wrote it and about whom
|
|
authorDid: string;
|
|
authorName: string;
|
|
subjectDid: string;
|
|
subjectName: string;
|
|
direction: 'renter_to_owner' | 'owner_to_renter';
|
|
|
|
// Content
|
|
body: string;
|
|
rating: number | null; // 1-5, optional
|
|
tags: string[];
|
|
visibility: EndorsementVisibility;
|
|
|
|
// Trust integration
|
|
trustWeight: number; // 0-1
|
|
|
|
createdAt: number;
|
|
}
|
|
|
|
export interface SpaceConfig {
|
|
defaultEconomy: EconomyModel;
|
|
defaultTrustThreshold: number;
|
|
endorsementTagCatalog: string[];
|
|
requireEndorsement: boolean;
|
|
maxRentalDays: number;
|
|
}
|
|
|
|
// ── Top-level document ──
|
|
|
|
export interface VnbDoc {
|
|
meta: {
|
|
module: string;
|
|
collection: string;
|
|
version: number;
|
|
spaceSlug: string;
|
|
createdAt: number;
|
|
};
|
|
config: SpaceConfig;
|
|
vehicles: Record<string, Vehicle>;
|
|
availability: Record<string, TripWindow>;
|
|
rentals: Record<string, RentalRequest>;
|
|
endorsements: Record<string, Endorsement>;
|
|
}
|
|
|
|
// ── Schema registration ──
|
|
|
|
export const DEFAULT_ENDORSEMENT_TAGS = [
|
|
'reliable', 'clean', 'suspiciously_clean', 'great_mileage',
|
|
'cozy', 'didnt_break_down', 'smells_like_adventure', 'felt_like_home',
|
|
'better_than_a_hotel', 'surprisingly_spacious', 'good_communication',
|
|
'smooth_handoff',
|
|
];
|
|
|
|
const DEFAULT_CONFIG: SpaceConfig = {
|
|
defaultEconomy: 'suggested',
|
|
defaultTrustThreshold: 30,
|
|
endorsementTagCatalog: DEFAULT_ENDORSEMENT_TAGS,
|
|
requireEndorsement: false,
|
|
maxRentalDays: 30,
|
|
};
|
|
|
|
export const vnbSchema: DocSchema<VnbDoc> = {
|
|
module: 'vnb',
|
|
collection: 'vehicles',
|
|
version: 1,
|
|
init: (): VnbDoc => ({
|
|
meta: {
|
|
module: 'vnb',
|
|
collection: 'vehicles',
|
|
version: 1,
|
|
spaceSlug: '',
|
|
createdAt: Date.now(),
|
|
},
|
|
config: { ...DEFAULT_CONFIG },
|
|
vehicles: {},
|
|
availability: {},
|
|
rentals: {},
|
|
endorsements: {},
|
|
}),
|
|
};
|
|
|
|
// ── Helpers ──
|
|
|
|
export function vnbDocId(space: string) {
|
|
return `${space}:vnb:vehicles` as const;
|
|
}
|
|
|
|
/** Economy model display labels */
|
|
export const ECONOMY_LABELS: Record<EconomyModel, string> = {
|
|
gift: 'Gift Economy',
|
|
suggested: 'Suggested Contribution',
|
|
fixed: 'Fixed Price',
|
|
sliding_scale: 'Sliding Scale',
|
|
exchange: 'Skill/Service Exchange',
|
|
};
|
|
|
|
/** Vehicle type display labels */
|
|
export const VEHICLE_TYPE_LABELS: Record<VehicleType, string> = {
|
|
motorhome: 'Motorhome',
|
|
camper_van: 'Camper Van',
|
|
travel_trailer: 'Travel Trailer',
|
|
truck_camper: 'Truck Camper',
|
|
skoolie: 'Skoolie',
|
|
other: 'Other',
|
|
};
|
|
|
|
/** Vehicle type icons */
|
|
export const VEHICLE_TYPE_ICONS: Record<VehicleType, string> = {
|
|
motorhome: '\u{1F690}', // minibus
|
|
camper_van: '\u{1F68C}', // bus
|
|
travel_trailer: '\u{1F3D5}', // camping
|
|
truck_camper: '\u{1F6FB}', // pickup truck
|
|
skoolie: '\u{1F68E}', // trolleybus
|
|
other: '\u{1F3E0}', // house
|
|
};
|
|
|
|
/** Mileage policy display labels */
|
|
export const MILEAGE_LABELS: Record<MileagePolicy, string> = {
|
|
unlimited: 'Unlimited Miles',
|
|
per_mile: 'Per Mile',
|
|
included_miles: 'Included Miles',
|
|
};
|