feat: Add new scripts and update project structure for database management and user authentication

This commit introduces several new scripts for managing database operations, including user creation, permission grants, and data migrations. It also adds new documentation files to guide users through the setup and configuration processes. Additionally, the project structure is updated to enhance organization and maintainability, ensuring a smoother development experience for contributors. These changes support the ongoing transition to a web-based architecture and improve overall project functionality.
This commit is contained in:
Tanya
2026-01-06 13:53:24 -05:00
parent 713584dc04
commit de2144be2a
175 changed files with 35854 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
generator client {
provider = "prisma-client-js"
output = "../node_modules/.prisma/client-auth"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL_AUTH")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
passwordHash String @map("password_hash")
isAdmin Boolean @default(false) @map("is_admin")
hasWriteAccess Boolean @default(false) @map("has_write_access")
emailVerified Boolean @default(false) @map("email_verified")
emailConfirmationToken String? @unique @map("email_confirmation_token")
emailConfirmationTokenExpiry DateTime? @map("email_confirmation_token_expiry")
passwordResetToken String? @unique @map("password_reset_token")
passwordResetTokenExpiry DateTime? @map("password_reset_token_expiry")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
pendingIdentifications PendingIdentification[]
pendingPhotos PendingPhoto[]
inappropriatePhotoReports InappropriatePhotoReport[]
pendingLinkages PendingLinkage[]
photoFavorites PhotoFavorite[]
@@map("users")
}
model PendingIdentification {
id Int @id @default(autoincrement())
faceId Int @map("face_id")
userId Int @map("user_id")
firstName String @map("first_name")
lastName String @map("last_name")
middleName String? @map("middle_name")
maidenName String? @map("maiden_name")
dateOfBirth DateTime? @map("date_of_birth") @db.Date
status String @default("pending") // pending, approved, rejected
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
// Note: faceId references faces in the punimtag database, but we can't use a foreign key
// across databases. We'll validate the face exists in application code.
@@index([faceId])
@@index([userId])
@@index([status])
@@map("pending_identifications")
}
model PendingPhoto {
id Int @id @default(autoincrement())
userId Int @map("user_id")
filename String
originalFilename String @map("original_filename")
filePath String @map("file_path")
fileSize Int @map("file_size")
mimeType String @map("mime_type")
status String @default("pending") // pending, approved, rejected
submittedAt DateTime @default(now()) @map("submitted_at")
reviewedAt DateTime? @map("reviewed_at")
reviewedBy Int? @map("reviewed_by") // Admin user ID who reviewed
rejectionReason String? @map("rejection_reason")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([status])
@@index([submittedAt])
@@map("pending_photos")
}
model InappropriatePhotoReport {
id Int @id @default(autoincrement())
photoId Int @map("photo_id")
userId Int @map("user_id")
status String @default("pending") // pending, reviewed, dismissed
reportedAt DateTime @default(now()) @map("reported_at")
reviewedAt DateTime? @map("reviewed_at")
reviewedBy Int? @map("reviewed_by") // Admin user ID who reviewed
reviewNotes String? @map("review_notes")
reportComment String? @map("report_comment")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
// Note: photoId references photos in the punimtag database, but we can't use a foreign key
// across databases. We'll validate the photo exists in application code.
@@unique([photoId, userId], name: "uq_photo_user_report") // Prevent duplicate reports from same user
@@index([photoId])
@@index([userId])
@@index([status])
@@index([reportedAt])
@@map("inappropriate_photo_reports")
}
model PendingLinkage {
id Int @id @default(autoincrement())
photoId Int @map("photo_id")
tagId Int? @map("tag_id")
tagName String? @map("tag_name")
userId Int @map("user_id")
status String @default("pending")
notes String? @map("notes")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([photoId])
@@index([tagId])
@@index([userId])
@@index([status])
@@map("pending_linkages")
}
model PhotoFavorite {
id Int @id @default(autoincrement())
photoId Int @map("photo_id")
userId Int @map("user_id")
favoritedAt DateTime @default(now()) @map("favorited_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
// Note: photoId references photos in the punimtag database, but we can't use a foreign key
// across databases. We'll validate the photo exists in application code.
@@unique([photoId, userId], name: "uq_photo_user_favorite")
@@index([photoId])
@@index([userId])
@@index([favoritedAt])
@@map("photo_favorites")
}
+214
View File
@@ -0,0 +1,214 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model AlembicVersion {
@@map("alembic_version")
version_num String @id
}
model Face {
@@map("faces")
id Int @id @default(autoincrement())
photo_id Int
person_id Int?
encoding Bytes
location String
confidence Decimal
quality_score Decimal
is_primary_encoding Boolean
detector_backend String
model_name String
face_confidence Decimal
exif_orientation Int?
pose_mode String
yaw_angle Decimal?
pitch_angle Decimal?
roll_angle Decimal?
landmarks String?
identified_by_user_id Int?
excluded Boolean @default(false)
Person Person? @relation(fields: [person_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
Photo Photo @relation(fields: [photo_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
User User? @relation(fields: [identified_by_user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
PersonEncoding PersonEncoding[]
@@index([excluded], map: "idx_faces_excluded")
@@index([identified_by_user_id], map: "idx_faces_identified_by")
@@index([pose_mode], map: "idx_faces_pose_mode")
@@index([photo_id], map: "ix_faces_photo_id")
@@index([quality_score], map: "idx_faces_quality")
@@index([photo_id], map: "idx_faces_photo_id")
@@index([id], map: "ix_faces_id")
@@index([person_id], map: "idx_faces_person_id")
@@index([person_id], map: "ix_faces_person_id")
@@index([quality_score], map: "ix_faces_quality_score")
@@index([pose_mode], map: "ix_faces_pose_mode")
}
model Person {
@@map("people")
id Int @id @default(autoincrement())
first_name String
last_name String
middle_name String?
maiden_name String?
date_of_birth DateTime?
created_date DateTime
Face Face[]
PersonEncoding PersonEncoding[]
PhotoPersonLinkage PhotoPersonLinkage[]
@@unique([first_name, last_name, middle_name, maiden_name, date_of_birth], map: "sqlite_autoindex_people_1")
@@index([id], map: "ix_people_id")
}
model PersonEncoding {
@@map("person_encodings")
id Int @id @default(autoincrement())
person_id Int
face_id Int
encoding Bytes
quality_score Decimal
detector_backend String
model_name String
created_date DateTime
Face Face @relation(fields: [face_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
Person Person @relation(fields: [person_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
@@index([person_id], map: "ix_person_encodings_person_id")
@@index([quality_score], map: "idx_person_encodings_quality")
@@index([id], map: "ix_person_encodings_id")
@@index([face_id], map: "ix_person_encodings_face_id")
@@index([person_id], map: "idx_person_encodings_person_id")
@@index([quality_score], map: "ix_person_encodings_quality_score")
}
model PhotoFavorite {
@@map("photo_favorites")
id Int @id @default(autoincrement())
username String
photo_id Int
created_date DateTime
Photo Photo @relation(fields: [photo_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
@@unique([username, photo_id], map: "sqlite_autoindex_photo_favorites_1")
@@index([username], map: "ix_photo_favorites_username")
@@index([username], map: "idx_favorites_username")
@@index([photo_id], map: "idx_favorites_photo")
@@index([photo_id], map: "ix_photo_favorites_photo_id")
}
model PhotoPersonLinkage {
@@map("photo_person_linkage")
id Int @id @default(autoincrement())
photo_id Int
person_id Int
identified_by_user_id Int?
created_date DateTime
User User? @relation(fields: [identified_by_user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
Person Person @relation(fields: [person_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
Photo Photo @relation(fields: [photo_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
@@unique([photo_id, person_id], map: "sqlite_autoindex_photo_person_linkage_1")
@@index([identified_by_user_id], map: "idx_photo_person_user")
@@index([person_id], map: "idx_photo_person_person")
@@index([photo_id], map: "idx_photo_person_photo")
@@index([photo_id], map: "ix_photo_person_linkage_photo_id")
@@index([person_id], map: "ix_photo_person_linkage_person_id")
@@index([identified_by_user_id], map: "ix_photo_person_linkage_identified_by_user_id")
}
model Photo {
@@map("photos")
id Int @id @default(autoincrement())
path String @unique(map: "ix_photos_path")
filename String
date_added DateTime
date_taken DateTime?
processed Boolean
media_type String?
Face Face[]
PhotoFavorite PhotoFavorite[]
PhotoPersonLinkage PhotoPersonLinkage[]
PhotoTagLinkage PhotoTagLinkage[]
@@index([media_type], map: "idx_photos_media_type")
@@index([date_added], map: "idx_photos_date_added")
@@index([date_taken], map: "idx_photos_date_taken")
@@index([processed], map: "ix_photos_processed")
@@index([processed], map: "idx_photos_processed")
@@index([date_taken], map: "ix_photos_date_taken")
@@index([id], map: "ix_photos_id")
}
model PhotoTagLinkage {
@@map("phototaglinkage")
linkage_id Int @id @default(autoincrement())
photo_id Int
tag_id Int
linkage_type Int @default(0)
created_date DateTime
Tag Tag @relation(fields: [tag_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
Photo Photo @relation(fields: [photo_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
@@unique([photo_id, tag_id], map: "sqlite_autoindex_phototaglinkage_1")
@@index([tag_id], map: "ix_phototaglinkage_tag_id")
@@index([photo_id], map: "ix_phototaglinkage_photo_id")
@@index([tag_id], map: "idx_photo_tags_tag")
@@index([photo_id], map: "idx_photo_tags_photo")
}
model role_permissions {
id Int @id @default(autoincrement())
role String
feature_key String
allowed Boolean @default(false)
@@unique([role, feature_key], map: "sqlite_autoindex_role_permissions_1")
@@index([feature_key], map: "ix_role_permissions_feature_key")
@@index([role], map: "ix_role_permissions_role")
@@index([role, feature_key], map: "idx_role_permissions_role_feature")
}
model Tag {
@@map("tags")
id Int @id @default(autoincrement())
tag_name String @unique(map: "ix_tags_tag_name")
created_date DateTime
PhotoTagLinkage PhotoTagLinkage[]
@@index([id], map: "ix_tags_id")
}
model User {
@@map("users")
id Int @id @default(autoincrement())
username String @unique(map: "ix_users_username")
password_hash String
email String @unique(map: "ix_users_email")
full_name String
is_active Boolean
is_admin Boolean
role String @default("viewer")
password_change_required Boolean
created_date DateTime
last_login DateTime?
Face Face[]
PhotoPersonLinkage PhotoPersonLinkage[]
@@index([email], map: "idx_users_email")
@@index([username], map: "idx_users_username")
@@index([id], map: "ix_users_id")
@@index([password_change_required], map: "ix_users_password_change_required")
@@index([role], map: "idx_users_role")
@@index([role], map: "ix_users_role")
@@index([password_change_required], map: "idx_users_password_change_required")
@@index([is_admin], map: "idx_users_is_admin")
@@index([is_admin], map: "ix_users_is_admin")
}