feat: stabilize social backend contracts

هذا الالتزام موجود في:
boutmoun123
2026-05-31 16:13:23 +03:00
الأصل ad6da6754d
التزام 49e132909e
40 ملفات معدلة مع 12037 إضافات و9562 حذوفات

عرض الملف

@@ -99,6 +99,12 @@ export class User {
@Prop({ default: false })
isEmailVerified!: boolean;
@Prop({ default: false, index: true })
isOnline!: boolean;
@Prop({ type: Date, default: null })
lastSeenAt?: Date | null;
@Prop({ default: '', trim: true, maxlength: 120 })
shopName!: string;

عرض الملف

@@ -288,11 +288,18 @@ export class UsersController {
return this.usersService.getProfileOverview(id, user.sub);
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':id/presence')
async getPresence(@Param('id') id: string) {
return this.usersService.getPresence(id);
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':id')
async findOne(@Param('id') id: string) {
return this.usersService.findPublicByIdOrFail(id);
async findOne(@CurrentUser() user: JwtPayload, @Param('id') id: string) {
return this.usersService.findPublicByIdForViewer(id, user.sub);
}
@ApiBearerAuth()

عرض الملف

@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { ClientSession, FilterQuery, Model, UpdateQuery } from 'mongoose';
import { ClientSession, FilterQuery, Model, Types, UpdateQuery } from 'mongoose';
import { User, UserDocument } from './schemas/user.schema';
@Injectable()
@@ -69,6 +69,12 @@ export class UsersRepository {
await this.userModel.findByIdAndUpdate(userId, { followingCount }, { new: false }).exec();
}
async setPresence(userId: string, isOnline: boolean, lastSeenAt: Date): Promise<void> {
await this.userModel
.findByIdAndUpdate(userId, { isOnline, lastSeenAt }, { new: false })
.exec();
}
async findMany(
filter: FilterQuery<UserDocument>,
skip: number,
@@ -78,6 +84,16 @@ export class UsersRepository {
return this.userModel.find(filter).sort(sort).skip(skip).limit(limit).exec();
}
async findManyByIds(ids: string[]): Promise<UserDocument[]> {
if (!ids.length) {
return [];
}
return this.userModel
.find({ _id: { $in: ids.map((id) => new Types.ObjectId(id)) } })
.exec();
}
async findByUsernames(usernames: string[]): Promise<UserDocument[]> {
if (!usernames.length) {
return [];

عرض الملف

@@ -237,6 +237,40 @@ export class UsersService {
return user;
}
async findPublicByIdForViewer(userId: string, viewerUserId: string) {
const user = await this.findPublicByIdOrFail(userId);
const isFollowing =
viewerUserId === userId
? false
: (await this.connection.collection('follows').countDocuments({
followerId: new Types.ObjectId(viewerUserId),
followingId: new Types.ObjectId(userId),
})) > 0;
return {
...user.toObject(),
isFollowing,
following: isFollowing,
isOwnProfile: viewerUserId === userId,
};
}
async getPresence(userId: string) {
const user = await this.findPublicByIdOrFail(userId);
return {
userId: user.id,
isOnline: user.isOnline ?? false,
lastSeenAt: user.lastSeenAt ?? null,
};
}
async setPresence(userId: string, isOnline: boolean): Promise<void> {
if (!Types.ObjectId.isValid(userId)) {
return;
}
await this.usersRepository.setPresence(userId, isOnline, new Date());
}
async findByEmailWithPassword(email: string): Promise<UserDocument | null> {
return this.usersRepository.findOneWithPassword({ email: email.toLowerCase() });
}
@@ -737,6 +771,7 @@ export class UsersService {
viewerState: {
isOwnProfile: viewerUserId === userId,
following: followingState,
isFollowing: followingState,
canMessage: viewerUserId !== userId,
},
};
@@ -749,6 +784,7 @@ export class UsersService {
viewerState: {
isOwnProfile: false,
following: false,
isFollowing: false,
canMessage: false,
},
};