rspace-online/modules/rvnb/schemas.ts

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',
};