الملفات
back_end_oudelaa/src/modules/posts/schemas/post.schema.ts
boutmoun123 acd8d0d8cf
بعض الفحوصات معلقة
Deploy To Ghaymah / deploy (push) Waiting to run
Improve backend media readiness for mobile clients
2026-05-25 23:18:00 +03:00

237 أسطر
6.9 KiB
TypeScript

import { Prop, Schema, SchemaFactory, raw } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';
import { ModerationStatus } from '../../../common/enums/moderation-status.enum';
import { PostType } from '../../../common/enums/post-type.enum';
import { PostVisibility } from '../../../common/enums/post-visibility.enum';
import { ProcessingStatus } from '../../../common/enums/processing-status.enum';
import { buildPostMediaResponse } from '../../../common/utils/post-media-response.util';
import {
resolveManagedFileUrl,
resolveManagedFileUrlRecord,
resolveManagedFileUrlRecords,
resolveManagedFileUrls,
} from '../../../common/utils/public-url.util';
import { User } from '../../users/schemas/user.schema';
export type PostDocument = HydratedDocument<Post>;
export type PostMediaVariantSet = {
originalUrl: string;
lowUrl: string;
mediumUrl: string;
highUrl: string;
};
export type PostImageItem = {
url: string;
caption: string;
altText: string;
order: number;
};
const mediaVariantSetSchema = raw({
originalUrl: { type: String, default: '' },
lowUrl: { type: String, default: '' },
mediumUrl: { type: String, default: '' },
highUrl: { type: String, default: '' },
});
const imageItemSchema = raw({
url: { type: String, default: '' },
caption: { type: String, default: '' },
altText: { type: String, default: '' },
order: { type: Number, default: 0 },
});
@Schema({ timestamps: true, versionKey: false })
export class Post {
@Prop({ type: Types.ObjectId, ref: User.name, required: true, index: true })
authorId!: Types.ObjectId;
@Prop({ type: Types.ObjectId, ref: 'Post', default: null, index: true })
repostOfPostId?: Types.ObjectId | null;
@Prop({ type: Types.ObjectId, ref: 'Post', default: null, index: true })
quoteOfPostId?: Types.ObjectId | null;
@Prop({ default: '', trim: true, maxlength: 2200, required: true })
content!: string;
@Prop({ default: '' })
videoUrl!: string;
@Prop({ default: '' })
hlsUrl!: string;
@Prop({ default: '' })
audioUrl!: string;
@Prop({ type: Number, min: 1, max: 7200, default: null })
durationSeconds!: number | null;
@Prop({ default: '' })
thumbnailUrl!: string;
@Prop({ type: mediaVariantSetSchema, default: null })
thumbnailVariants!: PostMediaVariantSet | null;
@Prop({ default: '', trim: true, maxlength: 80 })
style!: string;
@Prop({ default: '', trim: true, maxlength: 80 })
maqam!: string;
@Prop({ default: '', trim: true, maxlength: 40 })
rhythmSignature!: string;
@Prop({ type: [Number], default: [] })
waveformPeaks!: number[];
@Prop({ type: [String], default: [] })
imageUrls!: string[];
@Prop({ type: [imageItemSchema], default: [] })
imageItems!: PostImageItem[];
@Prop({ type: [mediaVariantSetSchema], default: [] })
imageVariants!: PostMediaVariantSet[];
@Prop({ type: [Types.ObjectId], ref: User.name, default: [], index: true })
taggedUserIds!: Types.ObjectId[];
@Prop({ type: [String], default: [] })
mentionUsernames!: string[];
@Prop({ type: [Types.ObjectId], ref: User.name, default: [], index: true })
collaboratorIds!: Types.ObjectId[];
@Prop({ default: '' })
location!: string;
@Prop({ type: Number, min: -90, max: 90, default: null })
latitude!: number | null;
@Prop({ type: Number, min: -180, max: 180, default: null })
longitude!: number | null;
@Prop({ enum: PostType, default: PostType.TEXT, index: true })
postType!: PostType;
@Prop({
type: String,
enum: Object.values(ProcessingStatus),
default: ProcessingStatus.READY,
index: true,
})
processingStatus!: ProcessingStatus;
@Prop({ enum: PostVisibility, default: PostVisibility.PUBLIC, index: true })
visibility!: PostVisibility;
@Prop({ default: 0, min: 0 })
likesCount!: number;
@Prop({ default: 0, min: 0 })
commentsCount!: number;
@Prop({ default: 0, min: 0 })
savesCount!: number;
@Prop({ default: 0, min: 0 })
shareCount!: number;
@Prop({ default: 0, min: 0 })
viewCount!: number;
@Prop({ default: 0, min: 0 })
playCount!: number;
@Prop({ default: false, index: true })
commentsDisabled!: boolean;
@Prop({ default: false })
commentsFollowersOnly!: boolean;
@Prop({ type: [String], default: [] })
commentFilterKeywords!: string[];
@Prop({ default: false, index: true })
pinnedToProfile!: boolean;
@Prop({ default: false, index: true })
isArchived!: boolean;
@Prop({ type: [String], default: [], index: true })
hashtags!: string[];
@Prop({
type: String,
enum: Object.values(ModerationStatus),
default: ModerationStatus.ACTIVE,
index: true,
})
moderationStatus!: ModerationStatus;
@Prop({ default: '', maxlength: 300 })
moderationReason!: string;
@Prop({ default: false, index: true })
isDeleted!: boolean;
@Prop({ type: Date, default: null })
deletedAt?: Date | null;
@Prop({ type: Types.ObjectId, ref: User.name, default: null })
deletedBy?: Types.ObjectId | null;
}
export const PostSchema = SchemaFactory.createForClass(Post);
PostSchema.index({ authorId: 1, createdAt: -1 });
PostSchema.index({ repostOfPostId: 1, createdAt: -1 });
PostSchema.index({ quoteOfPostId: 1, createdAt: -1 });
PostSchema.index({ visibility: 1, createdAt: -1 });
PostSchema.index({ postType: 1, createdAt: -1 });
PostSchema.index({ processingStatus: 1, createdAt: -1 });
PostSchema.index({ hashtags: 1, createdAt: -1 });
PostSchema.index({ taggedUserIds: 1, createdAt: -1 });
PostSchema.index({ collaboratorIds: 1, createdAt: -1 });
PostSchema.index({ authorId: 1, pinnedToProfile: -1, createdAt: -1 });
PostSchema.index({ authorId: 1, isArchived: 1, createdAt: -1 });
PostSchema.index({ moderationStatus: 1, createdAt: -1 });
PostSchema.index({ authorId: 1, isDeleted: 1, createdAt: -1 });
PostSchema.index({ visibility: 1, isDeleted: 1, createdAt: -1 });
PostSchema.index({
visibility: 1,
isDeleted: 1,
likesCount: -1,
commentsCount: -1,
savesCount: -1,
shareCount: -1,
viewCount: -1,
playCount: -1,
createdAt: -1,
});
const transformManagedPostFiles = (_doc: unknown, ret: any) => {
ret.imageUrls = resolveManagedFileUrls(ret.imageUrls);
ret.imageItems = Array.isArray(ret.imageItems)
? ret.imageItems.map((item: PostImageItem) => ({
...item,
url: resolveManagedFileUrl(item.url),
}))
: [];
ret.imageVariants = resolveManagedFileUrlRecords(ret.imageVariants);
ret.videoUrl = resolveManagedFileUrl(ret.videoUrl);
ret.hlsUrl = resolveManagedFileUrl(ret.hlsUrl);
ret.audioUrl = resolveManagedFileUrl(ret.audioUrl);
ret.thumbnailUrl = resolveManagedFileUrl(ret.thumbnailUrl);
ret.thumbnailVariants = resolveManagedFileUrlRecord(ret.thumbnailVariants);
ret.processingStatus = ret.processingStatus ?? ProcessingStatus.READY;
ret.media = buildPostMediaResponse(ret);
return ret;
};
PostSchema.set('toJSON', { transform: transformManagedPostFiles });
PostSchema.set('toObject', { transform: transformManagedPostFiles });