MongoDB Schemas - pacificnm/wiki-ai GitHub Wiki

๐Ÿ“ฆ MongoDB Schemas (camelCase)

This page defines the MongoDB document structures for each core collection in the AI Wiki Markup App. All field names follow camelCase convention.


๐Ÿ“„ Document Collection

Fields

Field Name Description Type
_id Unique document ID (MongoDB ObjectId) ObjectId
userId Reference to the user who owns the doc ObjectId
title Document title String
description Optional description or subtitle String
tags User-defined tags [String]
autoTags AI-generated tags [String]
summary AI-generated summary String
currentVersionId ID of the current version ObjectId
versionHistory List of version IDs [ObjectId]
categoryIds Linked categories [ObjectId]
commentIds Linked comments [ObjectId]
attachmentPaths File paths or URLs [String]
createdAt Creation date Date
updatedAt Last update date Date

Mongoose Schema

import mongoose from 'mongoose';

const documentSchema = new mongoose.Schema({
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  title: { type: String, required: true },
  description: { type: String },
  tags: [String],
  autoTags: [String],
  summary: { type: String },
  currentVersionId: { type: mongoose.Schema.Types.ObjectId, ref: 'Version' },
  versionHistory: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Version' }],
  categoryIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Category' }],
  commentIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }],
  attachmentPaths: [String],
  isPublished: { type: Boolean, default: false },
  publishedAt: { type: Date },
  viewCount: { type: Number, default: 0 },
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now }
});

// Add indexes for efficient queries
documentSchema.index({ userId: 1 });
documentSchema.index({ title: 'text', description: 'text', tags: 'text' });
documentSchema.index({ createdAt: -1 });
documentSchema.index({ updatedAt: -1 });
documentSchema.index({ isPublished: 1, publishedAt: -1 });
documentSchema.index({ tags: 1 });
documentSchema.index({ categoryIds: 1 });

// Compound indexes for common queries
documentSchema.index({ userId: 1, isPublished: 1, updatedAt: -1 });
documentSchema.index({ userId: 1, createdAt: -1 });

// Update the updatedAt field on save
documentSchema.pre('save', function(next) {
  this.updatedAt = new Date();
  next();
});

export default mongoose.model('Document', documentSchema);

๐Ÿงพ Version Collection

Fields

Field Name Description Type
_id Unique version ID ObjectId
documentId Linked document ID ObjectId
markdown GitHub-flavored markdown content String
createdBy User who created the version ObjectId
reason Optional reason for this version String
createdAt Timestamp for version creation Date

Mongoose Schema

import mongoose from 'mongoose';

const versionSchema = new mongoose.Schema({
  documentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Document', required: true },
  markdown: { type: String, required: true },
  createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  reason: { type: String },
  wordCount: { type: Number },
  charCount: { type: Number },
  isMinor: { type: Boolean, default: false },
  createdAt: { type: Date, default: Date.now }
});

// Add indexes for version queries
versionSchema.index({ documentId: 1, createdAt: -1 });
versionSchema.index({ createdBy: 1 });
versionSchema.index({ createdAt: -1 });

// Pre-save middleware to calculate word and character counts
versionSchema.pre('save', function(next) {
  if (this.markdown) {
    this.charCount = this.markdown.length;
    this.wordCount = this.markdown.split(/\s+/).filter(word => word.length > 0).length;
  }
  next();
});

export default mongoose.model('Version', versionSchema);

๐Ÿ—‚ Category Collection

Fields

Field Name Description Type
_id Unique category ID ObjectId
name Display name of the category String
slug URL-safe identifier (e.g., "ai-tools") String
description Optional description of the category String
parentId Reference to parent category (if nested) ObjectId
path Full slug path for nesting (e.g., ["ai","chatgpt"]) [String]
depth Level in the hierarchy (0 = root) Number

Mongoose Schema

import mongoose from 'mongoose';

const categorySchema = new mongoose.Schema({
  name: { type: String, required: true },
  slug: { type: String, required: true, unique: true },
  description: { type: String },
  parentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Category' },
  path: [{ type: String }],
  depth: { type: Number, default: 0 }
});

// Add indexes for category queries
categorySchema.index({ slug: 1 }, { unique: true });
categorySchema.index({ parentId: 1 });
categorySchema.index({ depth: 1 });
categorySchema.index({ path: 1 });

// Virtual for full path string
categorySchema.virtual('fullPath').get(function() {
  return this.path.join('/');
});

export default mongoose.model('Category', categorySchema);

๐Ÿ’ฌ Comment Collection

Fields

Field Name Description Type
_id Unique comment ID ObjectId
documentId Reference to the associated document ObjectId
versionId (Optional) Linked version if comment is version-specific ObjectId
userId Reference to the user who created the comment ObjectId
text Comment content String
location (Optional) Reference marker in markdown (e.g., line or section) String
createdAt Timestamp for when the comment was created Date

Mongoose Schema

import mongoose from 'mongoose';

const commentSchema = new mongoose.Schema({
  documentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Document', required: true },
  versionId: { type: mongoose.Schema.Types.ObjectId, ref: 'Version' },
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  text: { type: String, required: true },
  location: { type: String },
  createdAt: { type: Date, default: Date.now }
});

export default mongoose.model('Comment', commentSchema);

๐Ÿ“Ž Attachment Collection

Fields

Field Name Description Type
_id Unique attachment ID ObjectId
documentId Reference to the document this attachment belongs to ObjectId
versionId (Optional) Reference to a specific version ObjectId
uploadedBy User who uploaded the file ObjectId
fileName Original file name String
filePath File system path or URL to the stored file String
fileType MIME type (e.g., "application/pdf") String
size File size in bytes Number
createdAt Upload timestamp Date

Mongoose Schema

import mongoose from 'mongoose';

const attachmentSchema = new mongoose.Schema({
  documentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Document', required: true },
  versionId: { type: mongoose.Schema.Types.ObjectId, ref: 'Version' },
  uploadedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  fileName: { type: String, required: true },
  filePath: { type: String, required: true },
  fileType: { type: String },
  size: { type: Number },
  createdAt: { type: Date, default: Date.now }
});

// Add indexes for better query performance
attachmentSchema.index({ documentId: 1 });
attachmentSchema.index({ uploadedBy: 1 });
attachmentSchema.index({ createdAt: -1 });

export default mongoose.model('Attachment', attachmentSchema);

๐Ÿ‘ค User Collection

Fields

Field Name Description Type
_id Unique user ID (MongoDB ObjectId) ObjectId
firebaseUid Firebase Authentication UID String
displayName Full name or display name String
email User's email address String
role User role ("user" or "admin") String
profileImage Optional URL or path to profile image String
createdAt Timestamp for when the user account was created Date
lastLogin Timestamp of most recent login Date

Mongoose Schema

import mongoose from 'mongoose';

const userSchema = new mongoose.Schema({
  firebaseUid: { type: String, required: true, unique: true },
  displayName: { type: String },
  email: { type: String, required: true, unique: true },
  role: { type: String, enum: ['user', 'admin'], default: 'user' },
  profileImage: { type: String },
  createdAt: { type: Date, default: Date.now },
  lastLogin: { type: Date }
});

// Add indexes for authentication and queries
userSchema.index({ firebaseUid: 1 }, { unique: true });
userSchema.index({ email: 1 }, { unique: true });
userSchema.index({ role: 1 });
userSchema.index({ createdAt: 1 });

// Virtual for user's full profile
userSchema.virtual('profile').get(function() {
  return {
    id: this._id,
    displayName: this.displayName,
    email: this.email,
    role: this.role,
    profileImage: this.profileImage
  };
});

export default mongoose.model('User', userSchema);

๐Ÿ“‹ Log Collection

Fields

Field Name Description Type
_id Unique log entry ID ObjectId
level Log level ("info", "warn", "error", "debug") String
message Summary message or label for the log String
context Additional context (e.g., "Auth", "OpenAI", "Upload") String
userId (Optional) Reference to the user involved ObjectId
meta Additional metadata (stack trace, payloads, etc.) Mixed (Object)
timestamp When the log was created Date

Mongoose Schema

import mongoose from 'mongoose';

const logSchema = new mongoose.Schema({
  level: { type: String, enum: ['info', 'warn', 'error', 'debug'], required: true },
  message: { type: String, required: true },
  context: { type: String },
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  meta: { type: mongoose.Schema.Types.Mixed },
  timestamp: { type: Date, default: Date.now }
});

// Add indexes for log queries and cleanup
logSchema.index({ level: 1 });
logSchema.index({ timestamp: 1 });
logSchema.index({ context: 1 });
logSchema.index({ userId: 1 });

// TTL index to automatically delete old logs (30 days)
logSchema.index({ timestamp: 1 }, { expireAfterSeconds: 30 * 24 * 60 * 60 });

export default mongoose.model('Log', logSchema);

๐Ÿง  AI Suggestion Collection

Fields

Field Name Description Type
_id Unique suggestion ID ObjectId
documentId Reference to the document the suggestion was generated for ObjectId
versionId (Optional) Reference to the version the suggestion applies to ObjectId
type Type of suggestion ("tag", "summary", "title", etc.) String
suggestion Suggested value or output (e.g., array of tags, summary text) Mixed
modelUsed AI model used (e.g., "gpt-4") String
promptUsed Prompt sent to the AI for this suggestion String
createdBy Source: "system" (automatic) or "user" (manual rerun) String
createdAt Timestamp when suggestion was created Date

Mongoose Schema

import mongoose from 'mongoose';

const aiSuggestionSchema = new mongoose.Schema({
  documentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Document', required: true },
  versionId: { type: mongoose.Schema.Types.ObjectId, ref: 'Version' },
  type: { type: String, enum: ['tag', 'summary', 'title'], required: true },
  suggestion: { type: mongoose.Schema.Types.Mixed, required: true },
  modelUsed: { type: String },
  promptUsed: { type: String },
  createdBy: { type: String, enum: ['system', 'user'], default: 'system' },
  createdAt: { type: Date, default: Date.now }
});

// Add indexes for efficient queries
aiSuggestionSchema.index({ documentId: 1 });
aiSuggestionSchema.index({ type: 1 });
aiSuggestionSchema.index({ createdAt: -1 });

// Compound index for document-specific suggestions
aiSuggestionSchema.index({ documentId: 1, type: 1, createdAt: -1 });

export default mongoose.model('AiSuggestion', aiSuggestionSchema);

๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘ Access Control Collection

Used to manage shared document access between users. Each entry defines the level of permission a specific user has on a specific document.

Fields

Field Name Description Type
_id Unique access control entry ID ObjectId
documentId ID of the shared document ObjectId
userId ID of the user being granted access ObjectId
permission Access level ("viewer" or "editor") String
grantedBy ID of the user (typically owner or admin) who granted access ObjectId
createdAt Timestamp when access was granted Date

Mongoose Schema

import mongoose from 'mongoose';

const accessControlSchema = new mongoose.Schema({
  documentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Document', required: true },
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  permission: { type: String, enum: ['viewer', 'editor'], required: true },
  grantedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  createdAt: { type: Date, default: Date.now }
});

// Compound unique index to prevent duplicate permissions
accessControlSchema.index({ documentId: 1, userId: 1 }, { unique: true });
accessControlSchema.index({ userId: 1 });
accessControlSchema.index({ grantedBy: 1 });

export default mongoose.model('AccessControl', accessControlSchema);

โญ Favorite Collection

Tracks documents that users have marked as favorites for quick access or bookmarking.

Fields

Field Name Description Type
_id Unique favorite entry ID ObjectId
userId ID of the user who favorited ObjectId
documentId ID of the favorited document ObjectId
createdAt Timestamp when favorite was created Date

Mongoose Schema

import mongoose from 'mongoose';

const favoriteSchema = new mongoose.Schema({
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  documentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Document', required: true },
  createdAt: { type: Date, default: Date.now }
});

// Compound unique index to prevent duplicate favorites
favoriteSchema.index({ userId: 1, documentId: 1 }, { unique: true });
favoriteSchema.index({ userId: 1, createdAt: -1 });
favoriteSchema.index({ documentId: 1 });

export default mongoose.model('Favorite', favoriteSchema);

๐Ÿ—๏ธ Session Collection

Used for managing user sessions and authentication tokens.

Fields

Field Name Description Type
_id Unique session ID ObjectId
sessionId Session identifier token String
userId Reference to the user ObjectId
ipAddress IP address of the session String
userAgent Browser/client user agent String
isActive Whether session is currently active Boolean
expiresAt Session expiration timestamp Date
createdAt Session creation timestamp Date
lastActivity Last activity timestamp Date

Mongoose Schema

import mongoose from 'mongoose';

const sessionSchema = new mongoose.Schema({
  sessionId: { type: String, required: true, unique: true },
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  ipAddress: { type: String },
  userAgent: { type: String },
  isActive: { type: Boolean, default: true },
  expiresAt: { type: Date, required: true },
  createdAt: { type: Date, default: Date.now },
  lastActivity: { type: Date, default: Date.now }
});

// TTL index for automatic session cleanup
sessionSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
sessionSchema.index({ sessionId: 1 }, { unique: true });
sessionSchema.index({ userId: 1 });
sessionSchema.index({ isActive: 1 });

export default mongoose.model('Session', sessionSchema);

๐Ÿ“Š Analytics Collection

Used for tracking user activity and document analytics.

Fields

Field Name Description Type
_id Unique analytics entry ID ObjectId
userId Reference to the user (if logged in) ObjectId
documentId Reference to the document (if applicable) ObjectId
action Action type (view, edit, create, etc.) String
metadata Additional context data Object
ipAddress User's IP address String
userAgent Browser/client user agent String
timestamp When the action occurred Date

Mongoose Schema

import mongoose from 'mongoose';

const analyticsSchema = new mongoose.Schema({
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  documentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Document' },
  action: { 
    type: String, 
    enum: ['view', 'edit', 'create', 'delete', 'share', 'favorite', 'unfavorite', 'comment'],
    required: true 
  },
  metadata: { type: mongoose.Schema.Types.Mixed },
  ipAddress: { type: String },
  userAgent: { type: String },
  timestamp: { type: Date, default: Date.now }
});

// Indexes for analytics queries
analyticsSchema.index({ timestamp: -1 });
analyticsSchema.index({ action: 1, timestamp: -1 });
analyticsSchema.index({ userId: 1, timestamp: -1 });
analyticsSchema.index({ documentId: 1, timestamp: -1 });

// TTL index to automatically delete old analytics data (90 days)
analyticsSchema.index({ timestamp: 1 }, { expireAfterSeconds: 90 * 24 * 60 * 60 });

export default mongoose.model('Analytics', analyticsSchema);

๐Ÿ”ง Schema Utilities

Common Query Patterns

Find Documents with User Information

const documentsWithUsers = await Document
  .find({ isPublished: true })
  .populate('userId', 'displayName email')
  .select('title description tags createdAt')
  .sort({ createdAt: -1 })
  .limit(10);

Get Document with Full Version History

const documentWithHistory = await Document
  .findById(documentId)
  .populate({
    path: 'versionHistory',
    select: 'createdAt createdBy reason wordCount',
    populate: { path: 'createdBy', select: 'displayName' },
    options: { sort: { createdAt: -1 }, limit: 5 }
  });

Full-Text Search Across Documents

const searchResults = await Document
  .find({ $text: { $search: searchQuery } })
  .select('title description tags score')
  .sort({ score: { $meta: 'textScore' }, updatedAt: -1 });

Performance Considerations

  1. Use Indexes: All schemas include appropriate indexes for common query patterns
  2. Limit Population: Only populate fields you need
  3. Use Lean Queries: Add .lean() for read-only operations
  4. Implement Pagination: Use skip() and limit() for large datasets
  5. TTL Indexes: Automatic cleanup for logs, sessions, and analytics

Data Validation

All schemas include:

  • Required field validation
  • Enum constraints for limited-value fields
  • Unique indexes where appropriate
  • Pre-save hooks for data processing
  • Virtual fields for computed values

Migration Scripts

When updating schemas, create migration scripts:

// Example migration script
import mongoose from 'mongoose';
import Document from './models/Document.js';

async function addPublishedFields() {
  await Document.updateMany(
    { isPublished: { $exists: false } },
    { 
      $set: { 
        isPublished: false,
        viewCount: 0 
      } 
    }
  );
  console.log('Migration completed: Added published fields');
}