establish db

This commit is contained in:
2026-06-05 20:30:28 -04:00
parent 820a872f07
commit 58b5804d7a
95 changed files with 42873 additions and 233 deletions

View File

@@ -0,0 +1,62 @@
/**
* Drizzle ORM Database Client for Turso/libSQL.
*
* Provides the configured drizzle instance and convenience helpers.
* Reads DATABASE_URL and DATABASE_TOKEN from environment.
*/
import { sql } from "drizzle-orm";
import { drizzle, type LibSQLDatabase } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
import * as schema from "./schema";
export type { PlantRow, PlantInsert, DiseaseRow, DiseaseInsert } from "./schema";
export { schema };
let _db: LibSQLDatabase<typeof schema> | null = null;
let _client: ReturnType<typeof createClient> | null = null;
/** Get or create the Drizzle database instance (singleton). */
export function getDb(): LibSQLDatabase<typeof schema> {
if (_db) return _db;
const url = process.env.DATABASE_URL;
const token = process.env.DATABASE_TOKEN;
if (!url) {
throw new Error(
"DATABASE_URL is not set. Check your .env.development or .env.production file.",
);
}
if (!token) {
throw new Error(
"DATABASE_TOKEN is not set. Check your .env.development or .env.production file.",
);
}
_client = createClient({ url, authToken: token });
_db = drizzle(_client, { schema });
return _db;
}
/** Check database connectivity. */
export async function checkConnection(): Promise<boolean> {
try {
const db = getDb();
const result = await db.run(sql`SELECT 1 AS ok`);
return result.rowsAffected >= 0;
} catch (err) {
console.error("[DB] Connection failed:", err);
return false;
}
}
/** Close the client connection. */
export function closeDb() {
if (_client) {
_client.close();
_client = null;
_db = null;
}
}

View File

@@ -0,0 +1,104 @@
/**
* Drizzle ORM Schema for the Plant Disease Knowledge Base.
*
* Uses Turso (libSQL) with SQLite dialect.
* Arrays (symptoms, causes, treatment, prevention, lookalike_ids)
* are stored as JSON text columns and typed via Drizzle's $type().
*/
import { sql } from "drizzle-orm";
import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";
// ─── Plants Table ────────────────────────────────────────────────────────────
export const plants = sqliteTable(
"plants",
{
id: text("id").primaryKey(),
commonName: text("common_name").notNull(),
scientificName: text("scientific_name").notNull(),
family: text("family").notNull(),
category: text("category").notNull(),
careSummary: text("care_summary").notNull().default(""),
imageUrl: text("image_url").notNull().default(""),
createdAt: text("created_at")
.notNull()
.default(sql`(datetime('now'))`),
updatedAt: text("updated_at")
.notNull()
.default(sql`(datetime('now'))`),
},
(table) => ({
categoryIdx: index("idx_plants_category").on(table.category),
commonNameIdx: index("idx_plants_common_name").on(table.commonName),
}),
);
// ─── Diseases Table ──────────────────────────────────────────────────────────
export const diseases = sqliteTable(
"diseases",
{
id: text("id").primaryKey(),
plantId: text("plant_id")
.notNull()
.references(() => plants.id),
name: text("name").notNull(),
scientificName: text("scientific_name").notNull().default(""),
causalAgentType: text("causal_agent_type", {
enum: ["fungal", "bacterial", "viral", "environmental"],
}).notNull(),
description: text("description").notNull().default(""),
symptoms: text("symptoms", { mode: "json" }).notNull().default([]).$type<string[]>(),
causes: text("causes", { mode: "json" }).notNull().default([]).$type<string[]>(),
treatment: text("treatment", { mode: "json" }).notNull().default([]).$type<string[]>(),
prevention: text("prevention", { mode: "json" }).notNull().default([]).$type<string[]>(),
lookalikeIds: text("lookalike_ids", { mode: "json" }).notNull().default([]).$type<string[]>(),
severity: text("severity", {
enum: ["low", "moderate", "high", "critical"],
}).notNull(),
sourceUrl: text("source_url").notNull().default(""),
createdAt: text("created_at")
.notNull()
.default(sql`(datetime('now'))`),
updatedAt: text("updated_at")
.notNull()
.default(sql`(datetime('now'))`),
},
(table) => ({
plantIdIdx: index("idx_diseases_plant_id").on(table.plantId),
causalAgentIdx: index("idx_diseases_causal_agent").on(table.causalAgentType),
severityIdx: index("idx_diseases_severity").on(table.severity),
}),
);
// ─── Scrape Sources Table ────────────────────────────────────────────────────
export const scrapeSources = sqliteTable("scrape_sources", {
id: text("id").primaryKey(),
sourceType: text("source_type", {
enum: ["wikipedia", "university_extension", "cabi", "other"],
}).notNull(),
sourceUrl: text("source_url").notNull(),
lastScrapedAt: text("last_scraped_at"),
entriesCount: integer("entries_count").default(0),
status: text("status", { enum: ["pending", "success", "error"] })
.notNull()
.default("pending"),
errorMessage: text("error_message"),
createdAt: text("created_at")
.notNull()
.default(sql`(datetime('now'))`),
});
// ─── Relation Inference ──────────────────────────────────────────────────────
export const plantsRelations = {};
export const diseasesRelations = {};
// ─── Type helpers ────────────────────────────────────────────────────────────
export type PlantRow = typeof plants.$inferSelect;
export type PlantInsert = typeof plants.$inferInsert;
export type DiseaseRow = typeof diseases.$inferSelect;
export type DiseaseInsert = typeof diseases.$inferInsert;