← back to Wine Finder Next
lib/membershipDatabase.ts
526 lines
// In-Memory Database for Tokenized Wine Membership System
// In production, this would be replaced with a real database (PostgreSQL, MongoDB, etc.)
import {
Bottle,
MembershipUnit,
GovernanceVote,
Vote,
SampleUnit,
SampleRight,
MarketplaceListing,
FulfillmentRequest,
FulfillmentPartner,
DigitalRecord,
UserProfile,
Transaction,
SampleTransfer,
PlatformConfig
} from '@/src/types/membership';
// In-memory data stores
const bottles: Map<string, Bottle> = new Map();
const membershipUnits: Map<string, MembershipUnit> = new Map();
const governanceVotes: Map<string, GovernanceVote> = new Map();
const votes: Map<string, Vote> = new Map();
const sampleUnits: Map<string, SampleUnit> = new Map();
const sampleRights: Map<string, SampleRight> = new Map();
const marketplaceListings: Map<string, MarketplaceListing> = new Map();
const fulfillmentRequests: Map<string, FulfillmentRequest> = new Map();
const fulfillmentPartners: Map<string, FulfillmentPartner> = new Map();
const digitalRecords: Map<string, DigitalRecord> = new Map();
const userProfiles: Map<string, UserProfile> = new Map();
// Platform configuration
const platformConfig: PlatformConfig = {
votingQuorumPercentage: 50,
votingApprovalThreshold: 66,
votingPeriodDays: 7,
sampleSizeML: 50,
platformFeePercentage: 2.5,
minimumListingPrice: 10,
maximumListingDays: 30
};
// Helper function to generate unique IDs
function generateId(prefix: string): string {
return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// Helper function to create digital record
function createDigitalRecord(recordType: string, entityId: string, data: any): DigitalRecord {
const previousRecords = Array.from(digitalRecords.values())
.filter(r => r.entityId === entityId)
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
const record: DigitalRecord = {
id: generateId('record'),
recordType: recordType as any,
entityId,
data,
hash: generateHash(data),
previousHash: previousRecords[0]?.hash,
timestamp: new Date(),
metadata: {
version: '1.0',
creator: 'system'
}
};
digitalRecords.set(record.id, record);
return record;
}
// Simple hash function for demonstration
function generateHash(data: any): string {
const str = JSON.stringify(data);
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString(16);
}
// Initialize with sample data
function initializeSampleData() {
// Create a sample bottle
// Current market price: $15,000
// Total cost = Market price × 4.5 (350% markup) = $67,500
const bottle: Bottle = {
id: generateId('bottle'),
name: 'Château Margaux',
vintage: 1996,
producer: 'Château Margaux',
region: 'Margaux, Bordeaux',
size: '750ml',
totalMembershipUnits: 10000,
availableMembershipUnits: 7250,
estimatedValue: 67500, // $15k market price + 350% = $67,500 total needed
imageUrl: '/images/margaux-1996.jpg',
description: 'One of the greatest vintages of the 20th century. Perfect provenance from temperature-controlled storage.',
provenance: 'Purchased directly from château in 1997. Stored at 55°F, 70% humidity since acquisition.',
status: 'active',
createdAt: new Date('2024-01-15'),
updatedAt: new Date()
};
bottles.set(bottle.id, bottle);
createDigitalRecord('bottle', bottle.id, bottle);
// Create another bottle
// Current market price: $25,000
// Total cost = Market price × 4.5 (350% markup) = $112,500
const bottle2: Bottle = {
id: generateId('bottle'),
name: 'Domaine de la Romanée-Conti La Tâche',
vintage: 2005,
producer: 'Domaine de la Romanée-Conti',
region: 'Vosne-Romanée, Burgundy',
size: '750ml',
totalMembershipUnits: 10000,
availableMembershipUnits: 10000,
estimatedValue: 112500, // $25k market price + 350% = $112,500 total needed
imageUrl: '/images/drc-latache-2005.jpg',
description: 'Exceptional vintage from the legendary La Tâche vineyard. Impeccable provenance.',
provenance: 'Acquired from original allocation holder. Never moved from professional storage.',
status: 'active',
createdAt: new Date('2024-02-01'),
updatedAt: new Date()
};
bottles.set(bottle2.id, bottle2);
createDigitalRecord('bottle', bottle2.id, bottle2);
// Create sample membership units for first bottle (2,750 sold)
// Price per share = $67,500 / 10,000 = $6.75
for (let i = 0; i < 2750; i++) {
const unit: MembershipUnit = {
id: generateId('unit'),
bottleId: bottle.id,
tokenId: `TOKEN_${bottle.id}_${i + 1}`,
ownerId: `user_${Math.floor(Math.random() * 100) + 1}`,
ownerEmail: `member${i + 1}@example.com`,
ownerName: `Member ${i + 1}`,
purchasePrice: 6.75 + Math.random() * 0.50,
purchaseDate: new Date(Date.now() - Math.random() * 90 * 24 * 60 * 60 * 1000),
status: 'active',
votingPower: 1,
transactionHistory: []
};
membershipUnits.set(unit.id, unit);
createDigitalRecord('membership', unit.id, unit);
}
// Create a sample governance vote
const vote: GovernanceVote = {
id: generateId('vote'),
bottleId: bottle.id,
proposal: 'Unlock bottle for sampling event',
proposalType: 'unlock',
description: 'Proposal to unlock the 1996 Château Margaux and create 100 sample units (50ml each) for distribution to members. This will allow members to taste this exceptional wine while preserving value through collectible samples.',
startDate: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
endDate: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000),
status: 'active',
votesFor: 18,
votesAgainst: 3,
votesAbstain: 1,
quorumRequired: 50,
approvalThreshold: 66,
votes: [],
createdBy: 'admin',
};
governanceVotes.set(vote.id, vote);
// Create sample fulfillment partner
const partner: FulfillmentPartner = {
id: generateId('partner'),
name: 'Premium Wine Fulfillment Services',
licenseNumber: 'CA-DTC-2024-001',
licenseState: 'CA',
serviceStates: ['CA', 'NY', 'FL', 'TX', 'IL', 'WA', 'OR'],
apiEndpoint: 'https://api.premiumwinefulfillment.com',
contactEmail: 'orders@premiumwinefulfillment.com',
contactPhone: '+1-555-WINE-FUL',
capabilities: ['sampling', 'shipping', 'events', 'storage'],
active: true
};
fulfillmentPartners.set(partner.id, partner);
console.log('Sample data initialized:', {
bottles: bottles.size,
membershipUnits: membershipUnits.size,
votes: governanceVotes.size,
partners: fulfillmentPartners.size
});
}
// Initialize sample data on module load
initializeSampleData();
// CRUD Operations for Bottles
export const bottleDb = {
getAll: () => Array.from(bottles.values()),
getById: (id: string) => bottles.get(id),
create: (bottle: Omit<Bottle, 'id' | 'createdAt' | 'updatedAt'>) => {
const newBottle: Bottle = {
...bottle,
id: generateId('bottle'),
createdAt: new Date(),
updatedAt: new Date()
};
bottles.set(newBottle.id, newBottle);
createDigitalRecord('bottle', newBottle.id, newBottle);
return newBottle;
},
update: (id: string, updates: Partial<Bottle>) => {
const bottle = bottles.get(id);
if (!bottle) return null;
const updated = { ...bottle, ...updates, updatedAt: new Date() };
bottles.set(id, updated);
createDigitalRecord('bottle', id, updated);
return updated;
},
delete: (id: string) => bottles.delete(id)
};
// CRUD Operations for Membership Units
export const membershipUnitDb = {
getAll: () => Array.from(membershipUnits.values()),
getById: (id: string) => membershipUnits.get(id),
getByBottleId: (bottleId: string) =>
Array.from(membershipUnits.values()).filter(u => u.bottleId === bottleId),
getByOwnerId: (ownerId: string) =>
Array.from(membershipUnits.values()).filter(u => u.ownerId === ownerId),
create: (unit: Omit<MembershipUnit, 'id'>) => {
const newUnit: MembershipUnit = {
...unit,
id: generateId('unit')
};
membershipUnits.set(newUnit.id, newUnit);
createDigitalRecord('membership', newUnit.id, newUnit);
return newUnit;
},
update: (id: string, updates: Partial<MembershipUnit>) => {
const unit = membershipUnits.get(id);
if (!unit) return null;
const updated = { ...unit, ...updates };
membershipUnits.set(id, updated);
createDigitalRecord('membership', id, updated);
return updated;
},
transfer: (unitId: string, toUserId: string, toEmail: string, toName: string, price?: number) => {
const unit = membershipUnits.get(unitId);
if (!unit) return null;
const transaction: Transaction = {
id: generateId('tx'),
type: 'transfer',
fromUserId: unit.ownerId,
toUserId,
membershipUnitId: unitId,
price,
timestamp: new Date()
};
unit.transactionHistory.push(transaction);
unit.ownerId = toUserId;
unit.ownerEmail = toEmail;
unit.ownerName = toName;
membershipUnits.set(unitId, unit);
createDigitalRecord('transaction', transaction.id, transaction);
return unit;
}
};
// CRUD Operations for Governance Votes
export const governanceVoteDb = {
getAll: () => Array.from(governanceVotes.values()),
getById: (id: string) => governanceVotes.get(id),
getByBottleId: (bottleId: string) =>
Array.from(governanceVotes.values()).filter(v => v.bottleId === bottleId),
getActive: () =>
Array.from(governanceVotes.values()).filter(v => v.status === 'active' && new Date() < v.endDate),
create: (vote: Omit<GovernanceVote, 'id' | 'votesFor' | 'votesAgainst' | 'votesAbstain' | 'votes'>) => {
const newVote: GovernanceVote = {
...vote,
id: generateId('vote'),
votesFor: 0,
votesAgainst: 0,
votesAbstain: 0,
votes: []
};
governanceVotes.set(newVote.id, newVote);
return newVote;
},
castVote: (voteId: string, userId: string, membershipUnitId: string, choice: 'for' | 'against' | 'abstain') => {
const governanceVote = governanceVotes.get(voteId);
if (!governanceVote) return null;
const unit = membershipUnits.get(membershipUnitId);
if (!unit || unit.ownerId !== userId) return null;
// Check if already voted
const existingVote = governanceVote.votes.find(v => v.membershipUnitId === membershipUnitId);
if (existingVote) return null;
const newVote: Vote = {
id: generateId('vote'),
voteId,
userId,
membershipUnitId,
choice,
votingPower: unit.votingPower,
timestamp: new Date()
};
governanceVote.votes.push(newVote);
if (choice === 'for') governanceVote.votesFor += unit.votingPower;
else if (choice === 'against') governanceVote.votesAgainst += unit.votingPower;
else governanceVote.votesAbstain += unit.votingPower;
governanceVotes.set(voteId, governanceVote);
votes.set(newVote.id, newVote);
return governanceVote;
},
executeVote: (voteId: string) => {
const vote = governanceVotes.get(voteId);
if (!vote) return null;
const totalVotes = vote.votesFor + vote.votesAgainst + vote.votesAbstain;
const bottle = bottles.get(vote.bottleId);
if (!bottle) return null;
const quorumMet = (totalVotes / bottle.totalMembershipUnits) * 100 >= vote.quorumRequired;
const approvalMet = (vote.votesFor / totalVotes) * 100 >= vote.approvalThreshold;
if (quorumMet && approvalMet) {
vote.status = 'passed';
vote.executedAt = new Date();
// If it's an unlock vote, create sample units
if (vote.proposalType === 'unlock') {
const numSamples = 100; // Could be configurable
for (let i = 0; i < numSamples; i++) {
const sample: SampleUnit = {
id: generateId('sample'),
bottleId: vote.bottleId,
sampleNumber: i + 1,
size: '50ml',
status: 'available',
digitalRecordId: generateId('record'),
createdFromVoteId: voteId,
createdAt: new Date()
};
sampleUnits.set(sample.id, sample);
createDigitalRecord('sample', sample.id, sample);
}
// Update bottle status
bottleDb.update(vote.bottleId, { status: 'unlocked' });
}
} else {
vote.status = 'failed';
}
governanceVotes.set(voteId, vote);
return vote;
}
};
// Sample Units Operations
export const sampleUnitDb = {
getAll: () => Array.from(sampleUnits.values()),
getById: (id: string) => sampleUnits.get(id),
getByBottleId: (bottleId: string) =>
Array.from(sampleUnits.values()).filter(s => s.bottleId === bottleId),
getAvailable: (bottleId?: string) => {
const samples = Array.from(sampleUnits.values()).filter(s => s.status === 'available');
return bottleId ? samples.filter(s => s.bottleId === bottleId) : samples;
},
update: (id: string, updates: Partial<SampleUnit>) => {
const sample = sampleUnits.get(id);
if (!sample) return null;
const updated = { ...sample, ...updates };
sampleUnits.set(id, updated);
return updated;
}
};
// Sample Rights Operations
export const sampleRightDb = {
getAll: () => Array.from(sampleRights.values()),
getById: (id: string) => sampleRights.get(id),
getByOwnerId: (ownerId: string) =>
Array.from(sampleRights.values()).filter(r => r.ownerId === ownerId),
create: (right: Omit<SampleRight, 'id' | 'createdAt'>) => {
const newRight: SampleRight = {
...right,
id: generateId('right'),
createdAt: new Date()
};
sampleRights.set(newRight.id, newRight);
return newRight;
},
transfer: (rightId: string, toUserId: string, toEmail: string, price: number) => {
const right = sampleRights.get(rightId);
if (!right) return null;
const transfer: SampleTransfer = {
id: generateId('transfer'),
sampleRightId: rightId,
fromUserId: right.ownerId,
toUserId,
price,
timestamp: new Date(),
status: 'completed'
};
right.transferHistory.push(transfer);
right.ownerId = toUserId;
right.ownerEmail = toEmail;
right.status = 'active';
sampleRights.set(rightId, right);
createDigitalRecord('transaction', transfer.id, transfer);
return right;
}
};
// Marketplace Operations
export const marketplaceDb = {
getAll: () => Array.from(marketplaceListings.values()),
getActive: () => Array.from(marketplaceListings.values()).filter(l => l.status === 'active'),
getById: (id: string) => marketplaceListings.get(id),
getBySellerId: (sellerId: string) =>
Array.from(marketplaceListings.values()).filter(l => l.sellerId === sellerId),
create: (listing: Omit<MarketplaceListing, 'id' | 'listedAt'>) => {
const newListing: MarketplaceListing = {
...listing,
id: generateId('listing'),
listedAt: new Date()
};
marketplaceListings.set(newListing.id, newListing);
return newListing;
},
completeSale: (listingId: string, buyerId: string, buyerEmail: string, buyerName: string) => {
const listing = marketplaceListings.get(listingId);
if (!listing || listing.status !== 'active') return null;
listing.status = 'sold';
listing.soldAt = new Date();
listing.soldTo = buyerId;
// Transfer ownership
if (listing.listingType === 'membership') {
membershipUnitDb.transfer(listing.itemId, buyerId, buyerEmail, buyerName, listing.price);
} else {
sampleRightDb.transfer(listing.itemId, buyerId, buyerEmail, listing.price);
}
marketplaceListings.set(listingId, listing);
return listing;
},
cancel: (listingId: string) => {
const listing = marketplaceListings.get(listingId);
if (!listing) return null;
listing.status = 'cancelled';
marketplaceListings.set(listingId, listing);
return listing;
}
};
// Fulfillment Operations
export const fulfillmentDb = {
getAll: () => Array.from(fulfillmentRequests.values()),
getById: (id: string) => fulfillmentRequests.get(id),
getByUserId: (userId: string) =>
Array.from(fulfillmentRequests.values()).filter(r => r.userId === userId),
create: (request: Omit<FulfillmentRequest, 'id' | 'createdAt'>) => {
const newRequest: FulfillmentRequest = {
...request,
id: generateId('fulfill'),
createdAt: new Date()
};
fulfillmentRequests.set(newRequest.id, newRequest);
return newRequest;
},
updateStatus: (id: string, status: FulfillmentRequest['status'], updates?: Partial<FulfillmentRequest>) => {
const request = fulfillmentRequests.get(id);
if (!request) return null;
const updated = { ...request, status, ...updates };
fulfillmentRequests.set(id, updated);
return updated;
}
};
// Fulfillment Partner Operations
export const partnerDb = {
getAll: () => Array.from(fulfillmentPartners.values()),
getActive: () => Array.from(fulfillmentPartners.values()).filter(p => p.active),
getById: (id: string) => fulfillmentPartners.get(id),
getByState: (state: string) =>
Array.from(fulfillmentPartners.values()).filter(p =>
p.active && p.serviceStates.includes(state)
)
};
// Digital Records Operations
export const digitalRecordDb = {
getAll: () => Array.from(digitalRecords.values()),
getById: (id: string) => digitalRecords.get(id),
getByEntityId: (entityId: string) =>
Array.from(digitalRecords.values())
.filter(r => r.entityId === entityId)
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
};
// Configuration
export const getConfig = () => platformConfig;
export const updateConfig = (updates: Partial<PlatformConfig>) => {
Object.assign(platformConfig, updates);
return platformConfig;
};