237 أسطر
6.9 KiB
TypeScript
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 });
|