implement event persistence and improve Mongoose TypeScript patterns
- Add event persistence: confirmed events are now saved to MongoDB - Refactor Mongoose models to use virtuals for id field with IdVirtual interface - Update repositories to use toJSON() with consistent type casting - Add more test responses for chat (doctor, birthday, gym, etc.) - Show event description in ProposedEventCard - Change mongo-express port to 8083 - Update CLAUDE.md with Mongoose model pattern documentation
This commit is contained in:
@@ -16,7 +16,9 @@ export class MongoEventRepository implements EventRepository {
|
||||
}
|
||||
|
||||
async create(userId: string, data: CreateEventDTO): Promise<CalendarEvent> {
|
||||
throw new Error('Not implemented');
|
||||
const event = await EventModel.create({ userId, ...data });
|
||||
// NOTE: Casting required because Mongoose's toJSON() type doesn't reflect our virtual 'id' field
|
||||
return event.toJSON() as unknown as CalendarEvent;
|
||||
}
|
||||
|
||||
async update(id: string, data: UpdateEventDTO): Promise<CalendarEvent | null> {
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
import { User } from '@caldav/shared';
|
||||
import { UserRepository, CreateUserData } from '../../services/interfaces';
|
||||
import { UserModel, UserDocument } from './models';
|
||||
import { UserModel } from './models';
|
||||
|
||||
export class MongoUserRepository implements UserRepository {
|
||||
private toUser(doc: UserDocument): User {
|
||||
return {
|
||||
id: doc._id.toString(),
|
||||
email: doc.email,
|
||||
displayName: doc.displayName,
|
||||
createdAt: doc.createdAt,
|
||||
updatedAt: doc.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<User | null> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
async findByEmail(email: string): Promise<User | null> {
|
||||
const user = await UserModel.findOne({ email: email.toLowerCase() });
|
||||
return user ? this.toUser(user) : null;
|
||||
// NOTE: Casting required because Mongoose's toJSON() type doesn't reflect our virtual 'id' field
|
||||
return (user?.toJSON() as unknown as User) ?? null;
|
||||
}
|
||||
|
||||
async create(data: CreateUserData): Promise<User> {
|
||||
const user = await UserModel.create(data);
|
||||
return this.toUser(user);
|
||||
// NOTE: Casting required because Mongoose's toJSON() type doesn't reflect our virtual 'id' field
|
||||
return user.toJSON() as unknown as User;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import mongoose, { Schema, Document } from 'mongoose';
|
||||
import mongoose, { Schema, Document, Model } from 'mongoose';
|
||||
import { ChatMessage, Conversation, CreateEventDTO, UpdateEventDTO, ProposedEventChange } from '@caldav/shared';
|
||||
import { IdVirtual } from './types';
|
||||
|
||||
export interface ChatMessageDocument extends Omit<ChatMessage, 'id'>, Document {}
|
||||
export interface ConversationDocument extends Omit<Conversation, 'id'>, Document {}
|
||||
export interface ChatMessageDocument extends Omit<ChatMessage, 'id'>, Document {
|
||||
toJSON(): ChatMessage;
|
||||
}
|
||||
export interface ConversationDocument extends Omit<Conversation, 'id'>, Document {
|
||||
toJSON(): Conversation;
|
||||
}
|
||||
|
||||
const EventSchema = new Schema<CreateEventDTO>(
|
||||
{
|
||||
@@ -40,7 +45,7 @@ const ProposedChangeSchema = new Schema<ProposedEventChange>(
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
const ChatMessageSchema = new Schema<ChatMessageDocument>(
|
||||
const ChatMessageSchema = new Schema<ChatMessageDocument, Model<ChatMessageDocument, {}, {}, IdVirtual>, {}, {}, IdVirtual>(
|
||||
{
|
||||
conversationId: {
|
||||
type: String,
|
||||
@@ -61,9 +66,16 @@ const ChatMessageSchema = new Schema<ChatMessageDocument>(
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
virtuals: {
|
||||
id: {
|
||||
get() {
|
||||
return this._id.toString();
|
||||
},
|
||||
},
|
||||
},
|
||||
toJSON: {
|
||||
virtuals: true,
|
||||
transform: (_, ret: Record<string, unknown>) => {
|
||||
ret.id = String(ret._id);
|
||||
delete ret._id;
|
||||
delete ret.__v;
|
||||
return ret;
|
||||
@@ -72,7 +84,7 @@ const ChatMessageSchema = new Schema<ChatMessageDocument>(
|
||||
}
|
||||
);
|
||||
|
||||
const ConversationSchema = new Schema<ConversationDocument>(
|
||||
const ConversationSchema = new Schema<ConversationDocument, Model<ConversationDocument, {}, {}, IdVirtual>, {}, {}, IdVirtual>(
|
||||
{
|
||||
userId: {
|
||||
type: String,
|
||||
@@ -82,9 +94,16 @@ const ConversationSchema = new Schema<ConversationDocument>(
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
virtuals: {
|
||||
id: {
|
||||
get() {
|
||||
return this._id.toString();
|
||||
},
|
||||
},
|
||||
},
|
||||
toJSON: {
|
||||
virtuals: true,
|
||||
transform: (_, ret: Record<string, unknown>) => {
|
||||
ret.id = String(ret._id);
|
||||
delete ret._id;
|
||||
delete ret.__v;
|
||||
return ret;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import mongoose, { Schema, Document } from 'mongoose';
|
||||
import mongoose, { Schema, Document, Model } from 'mongoose';
|
||||
import { CalendarEvent } from '@caldav/shared';
|
||||
import { IdVirtual } from './types';
|
||||
|
||||
export interface EventDocument extends Omit<CalendarEvent, 'id'>, Document {}
|
||||
export interface EventDocument extends Omit<CalendarEvent, 'id'>, Document {
|
||||
toJSON(): CalendarEvent;
|
||||
}
|
||||
|
||||
const EventSchema = new Schema<EventDocument>(
|
||||
const EventSchema = new Schema<EventDocument, Model<EventDocument, {}, {}, IdVirtual>, {}, {}, IdVirtual>(
|
||||
{
|
||||
userId: {
|
||||
type: String,
|
||||
@@ -40,9 +43,16 @@ const EventSchema = new Schema<EventDocument>(
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
virtuals: {
|
||||
id: {
|
||||
get() {
|
||||
return this._id.toString();
|
||||
},
|
||||
},
|
||||
},
|
||||
toJSON: {
|
||||
virtuals: true,
|
||||
transform: (_, ret: Record<string, unknown>) => {
|
||||
ret.id = String(ret._id);
|
||||
delete ret._id;
|
||||
delete ret.__v;
|
||||
return ret;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import mongoose, { Schema, Document } from 'mongoose';
|
||||
import mongoose, { Schema, Document, Model } from 'mongoose';
|
||||
import { User } from '@caldav/shared';
|
||||
import { IdVirtual } from './types';
|
||||
|
||||
export interface UserDocument extends Omit<User, 'id'>, Document {}
|
||||
export interface UserDocument extends Omit<User, 'id'>, Document {
|
||||
toJSON(): User;
|
||||
}
|
||||
|
||||
const UserSchema = new Schema<UserDocument>(
|
||||
const UserSchema = new Schema<UserDocument, Model<UserDocument, {}, {}, IdVirtual>, {}, {}, IdVirtual>(
|
||||
{
|
||||
email: {
|
||||
type: String,
|
||||
@@ -24,9 +27,16 @@ const UserSchema = new Schema<UserDocument>(
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
virtuals: {
|
||||
id: {
|
||||
get() {
|
||||
return this._id.toString();
|
||||
},
|
||||
},
|
||||
},
|
||||
toJSON: {
|
||||
virtuals: true,
|
||||
transform: (_, ret: Record<string, unknown>) => {
|
||||
ret.id = String(ret._id);
|
||||
delete ret._id;
|
||||
delete ret.__v;
|
||||
delete ret.passwordHash;
|
||||
|
||||
4
apps/server/src/repositories/mongo/models/types.ts
Normal file
4
apps/server/src/repositories/mongo/models/types.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// Common virtual interface for all Mongoose models
|
||||
export interface IdVirtual {
|
||||
id: string;
|
||||
}
|
||||
Reference in New Issue
Block a user