File size: 3,365 Bytes
87cb242
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import mongoose, { Schema, Document, Model } from 'mongoose';
import { CONFIG } from '../config';
import { logger } from '../utils/logger';

// ── Connection ────────────────────────────────────────────────────────────────

let isConnected = false;

export async function connectMongo(): Promise<void> {
  if (isConnected) return;

  mongoose.set('strictQuery', true);

  await mongoose.connect(CONFIG.MONGO_URI, {
    maxPoolSize: 10,           // Conserve connections on free tier
    serverSelectionTimeoutMS: 5000,
    socketTimeoutMS: 30000,
  });

  isConnected = true;
  logger.info('MongoDB connected');

  mongoose.connection.on('error', (err: Error) =>
    logger.error('MongoDB connection error', { error: err.message })
  );
  mongoose.connection.on('disconnected', () => {
    isConnected = false;
    logger.warn('MongoDB disconnected');
  });
}

// ── User Profile Model ────────────────────────────────────────────────────────

export interface IUser extends Document {
  uid: string;
  displayName: string;
  avatarUrl?: string;
  friends: string[];         // Array of UIDs
  blockedUsers: string[];
  settings: {
    notifications: boolean;
    theme: 'light' | 'dark';
  };
  createdAt: Date;
  updatedAt: Date;
}

const UserSchema = new Schema<IUser>(
  {
    uid: { type: String, required: true, unique: true, index: true },
    displayName: { type: String, required: true, maxlength: 64 },
    avatarUrl: { type: String, default: null },
    friends: { type: [String], default: [] },
    blockedUsers: { type: [String], default: [] },
    settings: {
      notifications: { type: Boolean, default: true },
      theme: { type: String, enum: ['light', 'dark'], default: 'dark' },
    },
  },
  {
    timestamps: true,
    // Lean queries by default for read-heavy paths
  }
);

// Compound index for friend-list lookups
UserSchema.index({ uid: 1, friends: 1 });

export const UserModel: Model<IUser> = mongoose.model<IUser>('User', UserSchema);

// ── Persistent Message Log (optional, for history) ────────────────────────────

export interface IMessage extends Document {
  fromUid: string;
  toUid: string;
  type: 'text' | 'media';
  content: string;           // Text body or Supabase URL
  mediaType?: string;        // MIME type for media messages
  delivered: boolean;
  createdAt: Date;
}

const MessageSchema = new Schema<IMessage>(
  {
    fromUid: { type: String, required: true, index: true },
    toUid: { type: String, required: true, index: true },
    type: { type: String, enum: ['text', 'media'], required: true },
    content: { type: String, required: true, maxlength: 4096 },
    mediaType: { type: String, default: null },
    delivered: { type: Boolean, default: false, index: true },
  },
  {
    timestamps: true,
    // TTL index: auto-delete messages older than 30 days to save Atlas space
    expireAfterSeconds: 60 * 60 * 24 * 30,
  }
);

MessageSchema.index({ fromUid: 1, toUid: 1, createdAt: -1 });

export const MessageModel: Model<IMessage> = mongoose.model<IMessage>(
  'Message',
  MessageSchema
);