Initial commit: itinova-podcasts v1

Stack: Node.js/Express + React/Vite + tRPC + MySQL (Drizzle ORM)
Features: Gestion de podcasts, établissements, mots-clés, upload audio S3
Migrations: 0000-0002 (users, etablissements, mots_cles, podcasts, podcast_mots_cles)
This commit is contained in:
manus-admin
2026-04-12 18:34:56 -04:00
commit aab11c8308
138 changed files with 27782 additions and 0 deletions

98
drizzle/schema.ts Normal file
View File

@@ -0,0 +1,98 @@
import {
int,
mysqlEnum,
mysqlTable,
text,
timestamp,
varchar,
primaryKey,
boolean,
} from "drizzle-orm/mysql-core";
export const users = mysqlTable("users", {
id: int("id").autoincrement().primaryKey(),
openId: varchar("openId", { length: 64 }).notNull().unique(),
name: text("name"),
email: varchar("email", { length: 320 }),
loginMethod: varchar("loginMethod", { length: 64 }),
role: mysqlEnum("role", ["user", "admin"]).default("user").notNull(),
/** Identifiant de connexion locale (ex: adminServPodcast) */
username: varchar("username", { length: 64 }).unique(),
/** Hash bcrypt du mot de passe local */
passwordHash: varchar("passwordHash", { length: 255 }),
/** Empêche la modification ou suppression de ce compte */
immutable: boolean("immutable").default(false).notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
lastSignedIn: timestamp("lastSignedIn").defaultNow().notNull(),
});
export type User = typeof users.$inferSelect;
export type InsertUser = typeof users.$inferInsert;
// ─── Établissements ────────────────────────────────────────────────────────────
export const etablissements = mysqlTable("etablissements", {
id: int("id").autoincrement().primaryKey(),
nom: varchar("nom", { length: 255 }).notNull(),
description: text("description"),
logoUrl: text("logoUrl"),
actif: boolean("actif").default(true).notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
export type Etablissement = typeof etablissements.$inferSelect;
export type InsertEtablissement = typeof etablissements.$inferInsert;
// ─── Mots-clés ─────────────────────────────────────────────────────────────────
export const motsCles = mysqlTable("mots_cles", {
id: int("id").autoincrement().primaryKey(),
label: varchar("label", { length: 100 }).notNull().unique(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
});
export type MotCle = typeof motsCles.$inferSelect;
export type InsertMotCle = typeof motsCles.$inferInsert;
// ─── Podcasts ──────────────────────────────────────────────────────────────────
export const podcasts = mysqlTable("podcasts", {
id: int("id").autoincrement().primaryKey(),
titre: varchar("titre", { length: 255 }).notNull(),
resume: text("resume").notNull(),
etablissementId: int("etablissementId")
.notNull()
.references(() => etablissements.id),
audioUrl: text("audioUrl"),
audioKey: text("audioKey"),
dureeSecondes: int("dureeSecondes"),
statut: mysqlEnum("statut", ["brouillon", "publie"]).default("brouillon").notNull(),
auteurId: int("auteurId").references(() => users.id),
imageUrl: text("imageUrl"),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
export type Podcast = typeof podcasts.$inferSelect;
export type InsertPodcast = typeof podcasts.$inferInsert;
// ─── Relation Podcast ↔ Mots-clés ─────────────────────────────────────────────
export const podcastMotsCles = mysqlTable(
"podcast_mots_cles",
{
podcastId: int("podcastId")
.notNull()
.references(() => podcasts.id, { onDelete: "cascade" }),
motCleId: int("motCleId")
.notNull()
.references(() => motsCles.id, { onDelete: "cascade" }),
},
(table) => ({
pk: primaryKey({ columns: [table.podcastId, table.motCleId] }),
})
);
export type PodcastMotCle = typeof podcastMotsCles.$inferSelect;