mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 04:51:34 +00:00
Adds sample author data, text content, analysis results and annotation entities to JSON storage. Replit-Commit-Author: Agent Replit-Commit-Session-Id: cbacfb18-842a-4116-a907-18c0105ad8ec Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/39b5c689-6e8a-4d5a-9792-69cc81a56534/32264298-034a-445f-8b51-86b6180c466c.jpg
310 lines
10 KiB
TypeScript
310 lines
10 KiB
TypeScript
import { pgTable, text, serial, integer, boolean, timestamp, pgEnum, primaryKey, json, unique } from "drizzle-orm/pg-core";
|
|
import { createInsertSchema } from "drizzle-zod";
|
|
import { z } from "zod";
|
|
|
|
// Enums
|
|
export const roleEnum = pgEnum('role', ['user', 'contributor', 'reviewer', 'editor', 'admin']);
|
|
export const workTypeEnum = pgEnum('work_type', ['poem', 'story', 'novel', 'play', 'essay', 'other']);
|
|
|
|
// Users table
|
|
export const users = pgTable("users", {
|
|
id: serial("id").primaryKey(),
|
|
username: text("username").notNull().unique(),
|
|
password: text("password").notNull(),
|
|
email: text("email").notNull().unique(),
|
|
displayName: text("display_name"),
|
|
bio: text("bio"),
|
|
avatar: text("avatar"),
|
|
role: roleEnum("role").default('user').notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Authors table
|
|
export const authors = pgTable("authors", {
|
|
id: serial("id").primaryKey(),
|
|
name: text("name").notNull(),
|
|
slug: text("slug").notNull().unique(),
|
|
birthYear: integer("birth_year"),
|
|
deathYear: integer("death_year"),
|
|
country: text("country"),
|
|
biography: text("biography"),
|
|
portrait: text("portrait"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
createdBy: integer("created_by").references(() => users.id),
|
|
});
|
|
|
|
// Works table (original literary works)
|
|
export const works = pgTable("works", {
|
|
id: serial("id").primaryKey(),
|
|
title: text("title").notNull(),
|
|
slug: text("slug").notNull().unique(),
|
|
authorId: integer("author_id").references(() => authors.id).notNull(),
|
|
year: integer("year"),
|
|
type: workTypeEnum("type").default('other').notNull(),
|
|
language: text("language").notNull(),
|
|
content: text("content").notNull(),
|
|
description: text("description"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
createdBy: integer("created_by").references(() => users.id),
|
|
});
|
|
|
|
// Translations table
|
|
export const translations = pgTable("translations", {
|
|
id: serial("id").primaryKey(),
|
|
workId: integer("work_id").references(() => works.id).notNull(),
|
|
title: text("title").notNull(),
|
|
language: text("language").notNull(),
|
|
content: text("content").notNull(),
|
|
translatorId: integer("translator_id").references(() => users.id).notNull(),
|
|
year: integer("year"),
|
|
notes: text("notes"),
|
|
description: text("description"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Tags table
|
|
export const tags = pgTable("tags", {
|
|
id: serial("id").primaryKey(),
|
|
name: text("name").notNull().unique(),
|
|
type: text("type").notNull(), // genre, period, theme, etc.
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Work-Tag relations (many-to-many)
|
|
export const workTags = pgTable("work_tags", {
|
|
workId: integer("work_id").references(() => works.id).notNull(),
|
|
tagId: integer("tag_id").references(() => tags.id).notNull(),
|
|
}, (t) => ({
|
|
pk: primaryKey(t.workId, t.tagId),
|
|
}));
|
|
|
|
// Bookmarks
|
|
export const bookmarks = pgTable("bookmarks", {
|
|
id: serial("id").primaryKey(),
|
|
userId: integer("user_id").references(() => users.id).notNull(),
|
|
workId: integer("work_id").references(() => works.id).notNull(),
|
|
note: text("note"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
}, (t) => ({
|
|
unq: unique().on(t.userId, t.workId),
|
|
}));
|
|
|
|
// Comments
|
|
export const comments = pgTable("comments", {
|
|
id: serial("id").primaryKey(),
|
|
userId: integer("user_id").references(() => users.id).notNull(),
|
|
workId: integer("work_id").references(() => works.id), // Optional for non-work comments
|
|
translationId: integer("translation_id").references(() => translations.id),
|
|
lineNumber: integer("line_number"),
|
|
content: text("content").notNull(),
|
|
parentId: integer("parent_id").references(() => comments.id),
|
|
// Adding support for blog posts and other content types
|
|
entityType: text("entity_type"), // 'work', 'translation', 'blogPost', etc.
|
|
entityId: integer("entity_id"), // ID of the entity
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Likes (polymorphic)
|
|
export const likes = pgTable("likes", {
|
|
id: serial("id").primaryKey(),
|
|
userId: integer("user_id").references(() => users.id).notNull(),
|
|
entityType: text("entity_type").notNull(), // 'work', 'translation', 'comment'
|
|
entityId: integer("entity_id").notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
}, (t) => ({
|
|
unq: unique().on(t.userId, t.entityType, t.entityId),
|
|
}));
|
|
|
|
// Collections (curated sets of works)
|
|
export const collections = pgTable("collections", {
|
|
id: serial("id").primaryKey(),
|
|
name: text("name").notNull(),
|
|
slug: text("slug").notNull().unique(),
|
|
description: text("description"),
|
|
userId: integer("user_id").references(() => users.id).notNull(),
|
|
isPublic: boolean("is_public").default(true).notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Collection items
|
|
export const collectionItems = pgTable("collection_items", {
|
|
id: serial("id").primaryKey(),
|
|
collectionId: integer("collection_id").references(() => collections.id).notNull(),
|
|
workId: integer("work_id").references(() => works.id).notNull(),
|
|
order: integer("order").default(0).notNull(),
|
|
addedAt: timestamp("added_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Timeline events for authors
|
|
export const timelineEvents = pgTable("timeline_events", {
|
|
id: serial("id").primaryKey(),
|
|
authorId: integer("author_id").references(() => authors.id).notNull(),
|
|
year: integer("year").notNull(),
|
|
title: text("title").notNull(),
|
|
description: text("description"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Blog posts
|
|
export const blogPosts = pgTable("blog_posts", {
|
|
id: serial("id").primaryKey(),
|
|
title: text("title").notNull(),
|
|
slug: text("slug").notNull().unique(),
|
|
content: text("content").notNull(),
|
|
authorId: integer("author_id").references(() => users.id).notNull(),
|
|
excerpt: text("excerpt"),
|
|
publishedAt: timestamp("published_at"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Blog post tags
|
|
export const blogPostTags = pgTable("blog_post_tags", {
|
|
postId: integer("post_id").references(() => blogPosts.id).notNull(),
|
|
tagId: integer("tag_id").references(() => tags.id).notNull(),
|
|
}, (t) => ({
|
|
pk: primaryKey(t.postId, t.tagId),
|
|
}));
|
|
|
|
// Reading progress
|
|
export const readingProgress = pgTable("reading_progress", {
|
|
id: serial("id").primaryKey(),
|
|
userId: integer("user_id").references(() => users.id).notNull(),
|
|
workId: integer("work_id").references(() => works.id).notNull(),
|
|
translationId: integer("translation_id").references(() => translations.id),
|
|
progress: integer("progress").default(0).notNull(), // percentage
|
|
lastReadAt: timestamp("last_read_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Insert schemas
|
|
export const insertUserSchema = createInsertSchema(users).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
role: true
|
|
});
|
|
|
|
export const insertAuthorSchema = createInsertSchema(authors).omit({
|
|
id: true,
|
|
createdAt: true
|
|
});
|
|
|
|
export const insertWorkSchema = createInsertSchema(works).omit({
|
|
id: true,
|
|
createdAt: true
|
|
});
|
|
|
|
export const insertTranslationSchema = createInsertSchema(translations).omit({
|
|
id: true,
|
|
createdAt: true
|
|
});
|
|
|
|
export const insertTagSchema = createInsertSchema(tags).omit({
|
|
id: true,
|
|
createdAt: true
|
|
});
|
|
|
|
export const insertBookmarkSchema = createInsertSchema(bookmarks).omit({
|
|
id: true,
|
|
createdAt: true
|
|
});
|
|
|
|
export const insertCommentSchema = createInsertSchema(comments).omit({
|
|
id: true,
|
|
createdAt: true
|
|
});
|
|
|
|
export const insertLikeSchema = createInsertSchema(likes).omit({
|
|
id: true,
|
|
createdAt: true
|
|
});
|
|
|
|
export const insertCollectionSchema = createInsertSchema(collections).omit({
|
|
id: true,
|
|
createdAt: true
|
|
});
|
|
|
|
export const insertCollectionItemSchema = createInsertSchema(collectionItems).omit({
|
|
id: true,
|
|
addedAt: true
|
|
});
|
|
|
|
export const insertTimelineEventSchema = createInsertSchema(timelineEvents).omit({
|
|
id: true,
|
|
createdAt: true
|
|
});
|
|
|
|
export const insertBlogPostSchema = createInsertSchema(blogPosts).omit({
|
|
id: true,
|
|
createdAt: true
|
|
});
|
|
|
|
export const insertReadingProgressSchema = createInsertSchema(readingProgress).omit({
|
|
id: true,
|
|
lastReadAt: true
|
|
});
|
|
|
|
// Types
|
|
export type InsertUser = z.infer<typeof insertUserSchema>;
|
|
export type User = typeof users.$inferSelect;
|
|
|
|
export type InsertAuthor = z.infer<typeof insertAuthorSchema>;
|
|
export type Author = typeof authors.$inferSelect;
|
|
|
|
export type InsertWork = z.infer<typeof insertWorkSchema>;
|
|
export type Work = typeof works.$inferSelect;
|
|
|
|
export type InsertTranslation = z.infer<typeof insertTranslationSchema>;
|
|
export type Translation = typeof translations.$inferSelect;
|
|
|
|
export type InsertTag = z.infer<typeof insertTagSchema>;
|
|
export type Tag = typeof tags.$inferSelect;
|
|
|
|
export type InsertBookmark = z.infer<typeof insertBookmarkSchema>;
|
|
export type Bookmark = typeof bookmarks.$inferSelect;
|
|
|
|
export type InsertComment = z.infer<typeof insertCommentSchema>;
|
|
export type Comment = typeof comments.$inferSelect;
|
|
|
|
export type InsertLike = z.infer<typeof insertLikeSchema>;
|
|
export type Like = typeof likes.$inferSelect;
|
|
|
|
export type InsertCollection = z.infer<typeof insertCollectionSchema>;
|
|
export type Collection = typeof collections.$inferSelect;
|
|
|
|
export type InsertCollectionItem = z.infer<typeof insertCollectionItemSchema>;
|
|
export type CollectionItem = typeof collectionItems.$inferSelect;
|
|
|
|
export type InsertTimelineEvent = z.infer<typeof insertTimelineEventSchema>;
|
|
export type TimelineEvent = typeof timelineEvents.$inferSelect;
|
|
|
|
export type InsertBlogPost = z.infer<typeof insertBlogPostSchema>;
|
|
export type BlogPost = typeof blogPosts.$inferSelect;
|
|
|
|
export type InsertReadingProgress = z.infer<typeof insertReadingProgressSchema>;
|
|
export type ReadingProgress = typeof readingProgress.$inferSelect;
|
|
|
|
// Analysis results interface for linguistic/sentiment analysis and other analytics
|
|
export interface AnalysisResult {
|
|
id: number;
|
|
workId: number;
|
|
type: string; // 'sentiment', 'linguistics', 'themes', 'poetic', etc.
|
|
data: any;
|
|
createdAt: Date;
|
|
}
|
|
|
|
export type InsertAnalysisResult = Omit<AnalysisResult, 'id' | 'createdAt'>;
|
|
|
|
// Annotations interface for line-by-line notes, context, and analysis
|
|
export interface Annotation {
|
|
id: number;
|
|
workId: number;
|
|
translationId: number | null;
|
|
lineNumber: number | null;
|
|
content: string;
|
|
type: string; // 'context', 'analysis', 'translation', etc.
|
|
userId: number;
|
|
isOfficial: boolean;
|
|
createdAt: Date;
|
|
}
|
|
|
|
export type InsertAnnotation = Omit<Annotation, 'id' | 'createdAt'>;
|