first MVP
This commit is contained in:
43
prisma/migrations/20251114190833_init/migration.sql
Normal file
43
prisma/migrations/20251114190833_init/migration.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Person" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"company" TEXT,
|
||||
"role" TEXT,
|
||||
"email" TEXT,
|
||||
"location" TEXT,
|
||||
"sectors" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"interests" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Person_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Connection" (
|
||||
"id" TEXT NOT NULL,
|
||||
"personAId" TEXT NOT NULL,
|
||||
"personBId" TEXT NOT NULL,
|
||||
"introducedByChain" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"eventLabels" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Connection_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Person_name_idx" ON "Person"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Connection_personAId_idx" ON "Connection"("personAId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Connection_personBId_idx" ON "Connection"("personBId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Connection" ADD CONSTRAINT "Connection_personAId_fkey" FOREIGN KEY ("personAId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Connection" ADD CONSTRAINT "Connection_personBId_fkey" FOREIGN KEY ("personBId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,14 @@
|
||||
-- Enforce undirected connection uniqueness and prevent self-edges
|
||||
|
||||
-- Prevent self-edge (A == B)
|
||||
ALTER TABLE "Connection"
|
||||
ADD CONSTRAINT "Connection_no_self_edge"
|
||||
CHECK ("personAId" <> "personBId");
|
||||
|
||||
-- Unique undirected pair using functional index on LEAST/GREATEST
|
||||
-- Ensures only one edge exists for a given unordered pair {A,B}
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "Connection_undirected_pair_unique"
|
||||
ON "Connection" (
|
||||
(LEAST("personAId","personBId")),
|
||||
(GREATEST("personAId","personBId"))
|
||||
);
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
51
prisma/schema.prisma
Normal file
51
prisma/schema.prisma
Normal file
@@ -0,0 +1,51 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Person {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
company String?
|
||||
role String?
|
||||
email String?
|
||||
location String?
|
||||
sectors String[] @default([])
|
||||
interests String[] @default([])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations (undirected connections modeled as two directed FKs)
|
||||
connectionsA Connection[] @relation("ConnectionsA")
|
||||
connectionsB Connection[] @relation("ConnectionsB")
|
||||
|
||||
@@index([name])
|
||||
}
|
||||
|
||||
model Connection {
|
||||
id String @id @default(uuid())
|
||||
personAId String
|
||||
personBId String
|
||||
personA Person @relation("ConnectionsA", fields: [personAId], references: [id], onDelete: Cascade)
|
||||
personB Person @relation("ConnectionsB", fields: [personBId], references: [id], onDelete: Cascade)
|
||||
introducedByChain String[] @default([])
|
||||
eventLabels String[] @default([])
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([personAId])
|
||||
@@index([personBId])
|
||||
// Uniqueness of undirected pair (A,B) and self-edge prevention enforced via SQL migration with
|
||||
// a functional unique index on (LEAST(personAId, personBId), GREATEST(personAId, personBId))
|
||||
// and a CHECK (personAId <> personBId).
|
||||
}
|
||||
138
prisma/seed.ts
Normal file
138
prisma/seed.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
// prisma/seed.ts
|
||||
import "dotenv/config";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type SeedPerson = {
|
||||
name: string;
|
||||
company?: string;
|
||||
role?: string;
|
||||
email?: string;
|
||||
location?: string;
|
||||
sectors?: string[];
|
||||
interests?: string[];
|
||||
};
|
||||
|
||||
const people: SeedPerson[] = [
|
||||
{ name: "Alex Carter", company: "GreenFields", role: "Founder", email: "alex@greenfields.io", location: "Brussels", sectors: ["agriculture", "sustainability"], interests: ["funding", "network"] },
|
||||
{ name: "John Miller", company: "BuildRight", role: "PM", email: "john@buildright.eu", location: "Amsterdam", sectors: ["construction"], interests: ["knowledge", "network"] },
|
||||
{ name: "Mark Li", company: "BioPharmX", role: "Analyst", email: "mark@biopharmx.com", location: "Zurich", sectors: ["pharma"], interests: ["team", "network"] },
|
||||
{ name: "Sara Gomez", company: "AeroNext", role: "VP BizDev", email: "sara@aeronext.ai", location: "Madrid", sectors: ["aerospace", "ai"], interests: ["funding"] },
|
||||
{ name: "Priya Nair", company: "AgriSense", role: "CTO", email: "priya@agrisense.io", location: "Bangalore", sectors: ["agriculture", "iot"], interests: ["network", "knowledge"] },
|
||||
{ name: "Luca Moretti", company: "Constructa", role: "Engineer", email: "luca@constructa.it", location: "Milan", sectors: ["construction"], interests: ["team"] },
|
||||
{ name: "Emily Chen", company: "FinScope", role: "Investor", email: "emily@finscope.vc", location: "London", sectors: ["finance"], interests: ["where to invest"] },
|
||||
{ name: "David Kim", company: "HealthBridge", role: "Founder", email: "david@healthbridge.io", location: "Seoul", sectors: ["healthcare", "pharma"], interests: ["funding", "network"] },
|
||||
{ name: "Nina Petrov", company: "EcoLogix", role: "Consultant", email: "nina@ecologix.de", location: "Berlin", sectors: ["sustainability"], interests: ["knowledge"] },
|
||||
{ name: "Omar Hassan", company: "BuildHub", role: "Architect", email: "omar@buildhub.me", location: "Dubai", sectors: ["construction"], interests: ["network"] },
|
||||
{ name: "Isabella Rossi", company: "AgriCo", role: "Ops Lead", email: "isabella@agrico.it", location: "Rome", sectors: ["agriculture"], interests: ["team"] },
|
||||
{ name: "Tom Williams", company: "MedNova", role: "Scientist", email: "tom@mednova.uk", location: "Oxford", sectors: ["pharma", "biotech"], interests: ["knowledge", "team"] },
|
||||
{ name: "Chen Wei", company: "SkyLink", role: "Systems Eng", email: "chen@skylink.cn", location: "Shanghai", sectors: ["aerospace"], interests: ["network"] },
|
||||
{ name: "Ana Silva", company: "GreenWave", role: "Analyst", email: "ana@greenwave.pt", location: "Lisbon", sectors: ["sustainability", "energy"], interests: ["where to invest"] },
|
||||
{ name: "Michael Brown", company: "FinBridge", role: "Partner", email: "michael@finbridge.vc", location: "New York", sectors: ["finance"], interests: ["where to invest", "network"] },
|
||||
{ name: "Yuki Tanaka", company: "BioCore", role: "Researcher", email: "yuki@biocore.jp", location: "Tokyo", sectors: ["biotech"], interests: ["knowledge", "team"] },
|
||||
{ name: "Fatima Zahra", company: "AgriRoot", role: "Founder", email: "fatima@agriroot.ma", location: "Casablanca", sectors: ["agriculture"], interests: ["funding", "network"] },
|
||||
{ name: "Peter Novak", company: "BuildSmart", role: "Engineer", email: "peter@buildsmart.cz", location: "Prague", sectors: ["construction", "iot"], interests: ["knowledge"] },
|
||||
{ name: "Sofia Anders", company: "NordPharm", role: "PM", email: "sofia@nordpharm.se", location: "Stockholm", sectors: ["pharma"], interests: ["network"] },
|
||||
{ name: "Rafael Diaz", company: "AeroLab", role: "Founder", email: "rafael@aerolab.mx", location: "Mexico City", sectors: ["aerospace"], interests: ["funding", "team"] },
|
||||
];
|
||||
|
||||
function pairKey(a: string, b: string) {
|
||||
return a < b ? `${a}|${b}` : `${b}|${a}`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("Seeding database…");
|
||||
|
||||
// Reset (dev only)
|
||||
await prisma.connection.deleteMany({});
|
||||
await prisma.person.deleteMany({});
|
||||
|
||||
// Create people
|
||||
const created = await Promise.all(
|
||||
people.map((p) =>
|
||||
prisma.person.create({
|
||||
data: {
|
||||
name: p.name,
|
||||
company: p.company ?? null,
|
||||
role: p.role ?? null,
|
||||
email: p.email ?? null,
|
||||
location: p.location ?? null,
|
||||
sectors: p.sectors ?? [],
|
||||
interests: p.interests ?? [],
|
||||
},
|
||||
select: { id: true, name: true },
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const ids = created.map((c) => c.id);
|
||||
const idByName = new Map(created.map((c) => [c.name, c.id]));
|
||||
|
||||
// Create a set of sample undirected connections (about 28-32)
|
||||
const targetEdges = Math.min(32, Math.floor((ids.length * (ids.length - 1)) / 6));
|
||||
const used = new Set<string>();
|
||||
const rnd = (n: number) => Math.floor(Math.random() * n);
|
||||
|
||||
const edges: {
|
||||
a: string;
|
||||
b: string;
|
||||
introducedByChain: string[];
|
||||
eventLabels: string[];
|
||||
notes?: string | null;
|
||||
}[] = [];
|
||||
|
||||
let guard = 0;
|
||||
while (edges.length < targetEdges && guard < 5000) {
|
||||
guard++;
|
||||
const i = rnd(ids.length);
|
||||
let j = rnd(ids.length);
|
||||
if (j === i) continue;
|
||||
const a = ids[i];
|
||||
const b = ids[j];
|
||||
const key = pairKey(a, b);
|
||||
if (used.has(key)) continue;
|
||||
used.add(key);
|
||||
|
||||
// 50% add a single introducer different from a and b
|
||||
let introducedByChain: string[] = [];
|
||||
if (Math.random() < 0.5 && ids.length > 2) {
|
||||
let k = rnd(ids.length);
|
||||
let guard2 = 0;
|
||||
while ((k === i || k === j) && guard2 < 100) {
|
||||
k = rnd(ids.length);
|
||||
guard2++;
|
||||
}
|
||||
if (k !== i && k !== j) {
|
||||
introducedByChain = [ids[k]];
|
||||
}
|
||||
}
|
||||
|
||||
const eventLabels = Math.random() < 0.4 ? ["event:demo"] : [];
|
||||
edges.push({ a, b, introducedByChain, eventLabels, notes: null });
|
||||
}
|
||||
|
||||
for (const e of edges) {
|
||||
await prisma.connection.create({
|
||||
data: {
|
||||
personAId: e.a,
|
||||
personBId: e.b,
|
||||
introducedByChain: e.introducedByChain,
|
||||
eventLabels: e.eventLabels,
|
||||
notes: e.notes ?? null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Inserted ${created.length} people and ${edges.length} connections.`);
|
||||
}
|
||||
|
||||
main()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user