448 lines
12 KiB
TypeScript
448 lines
12 KiB
TypeScript
import 'reflect-metadata';
|
|
import { BaseModel } from '../../../../src/framework/models/BaseModel';
|
|
import { Model, Field, HasMany, BelongsTo, HasOne, BeforeCreate, AfterCreate } from '../../../../src/framework/models/decorators';
|
|
|
|
// Force field registration by manually setting up field configurations
|
|
function setupFieldConfigurations() {
|
|
// User Profile fields
|
|
if (!UserProfile.fields) {
|
|
(UserProfile as any).fields = new Map();
|
|
}
|
|
UserProfile.fields.set('userId', { type: 'string', required: true });
|
|
UserProfile.fields.set('bio', { type: 'string', required: false });
|
|
UserProfile.fields.set('location', { type: 'string', required: false });
|
|
UserProfile.fields.set('website', { type: 'string', required: false });
|
|
UserProfile.fields.set('socialLinks', { type: 'object', required: false });
|
|
UserProfile.fields.set('interests', { type: 'array', required: false, default: [] });
|
|
UserProfile.fields.set('createdAt', { type: 'number', required: false, default: () => Date.now() });
|
|
UserProfile.fields.set('updatedAt', { type: 'number', required: false, default: () => Date.now() });
|
|
|
|
// User fields
|
|
if (!User.fields) {
|
|
(User as any).fields = new Map();
|
|
}
|
|
User.fields.set('username', { type: 'string', required: true, unique: true });
|
|
User.fields.set('email', { type: 'string', required: true, unique: true });
|
|
User.fields.set('displayName', { type: 'string', required: false });
|
|
User.fields.set('avatar', { type: 'string', required: false });
|
|
User.fields.set('isActive', { type: 'boolean', required: false, default: true });
|
|
User.fields.set('roles', { type: 'array', required: false, default: [] });
|
|
User.fields.set('createdAt', { type: 'number', required: false });
|
|
User.fields.set('lastLoginAt', { type: 'number', required: false });
|
|
|
|
// Category fields
|
|
if (!Category.fields) {
|
|
(Category as any).fields = new Map();
|
|
}
|
|
Category.fields.set('name', { type: 'string', required: true, unique: true });
|
|
Category.fields.set('slug', { type: 'string', required: true, unique: true });
|
|
Category.fields.set('description', { type: 'string', required: false });
|
|
Category.fields.set('color', { type: 'string', required: false });
|
|
Category.fields.set('isActive', { type: 'boolean', required: false, default: true });
|
|
Category.fields.set('createdAt', { type: 'number', required: false, default: () => Date.now() });
|
|
|
|
// Post fields
|
|
if (!Post.fields) {
|
|
(Post as any).fields = new Map();
|
|
}
|
|
Post.fields.set('title', { type: 'string', required: true });
|
|
Post.fields.set('slug', { type: 'string', required: true, unique: true });
|
|
Post.fields.set('content', { type: 'string', required: true });
|
|
Post.fields.set('excerpt', { type: 'string', required: false });
|
|
Post.fields.set('authorId', { type: 'string', required: true });
|
|
Post.fields.set('categoryId', { type: 'string', required: false });
|
|
Post.fields.set('tags', { type: 'array', required: false, default: [] });
|
|
Post.fields.set('status', { type: 'string', required: false, default: 'draft' });
|
|
Post.fields.set('featuredImage', { type: 'string', required: false });
|
|
Post.fields.set('isFeatured', { type: 'boolean', required: false, default: false });
|
|
Post.fields.set('viewCount', { type: 'number', required: false, default: 0 });
|
|
Post.fields.set('likeCount', { type: 'number', required: false, default: 0 });
|
|
Post.fields.set('createdAt', { type: 'number', required: false });
|
|
Post.fields.set('updatedAt', { type: 'number', required: false });
|
|
Post.fields.set('publishedAt', { type: 'number', required: false });
|
|
|
|
// Comment fields
|
|
if (!Comment.fields) {
|
|
(Comment as any).fields = new Map();
|
|
}
|
|
Comment.fields.set('content', { type: 'string', required: true });
|
|
Comment.fields.set('postId', { type: 'string', required: true });
|
|
Comment.fields.set('authorId', { type: 'string', required: true });
|
|
Comment.fields.set('parentId', { type: 'string', required: false });
|
|
Comment.fields.set('isApproved', { type: 'boolean', required: false, default: true });
|
|
Comment.fields.set('likeCount', { type: 'number', required: false, default: 0 });
|
|
Comment.fields.set('createdAt', { type: 'number', required: false });
|
|
Comment.fields.set('updatedAt', { type: 'number', required: false });
|
|
}
|
|
|
|
// User Profile Model
|
|
@Model({
|
|
scope: 'global',
|
|
type: 'docstore'
|
|
})
|
|
export class UserProfile extends BaseModel {
|
|
@Field({ type: 'string', required: true })
|
|
userId: string;
|
|
|
|
@Field({ type: 'string', required: false })
|
|
bio?: string;
|
|
|
|
@Field({ type: 'string', required: false })
|
|
location?: string;
|
|
|
|
@Field({ type: 'string', required: false })
|
|
website?: string;
|
|
|
|
@Field({ type: 'object', required: false })
|
|
socialLinks?: {
|
|
twitter?: string;
|
|
github?: string;
|
|
linkedin?: string;
|
|
};
|
|
|
|
@Field({ type: 'array', required: false, default: [] })
|
|
interests: string[];
|
|
|
|
@Field({ type: 'number', required: false, default: () => Date.now() })
|
|
createdAt: number;
|
|
|
|
@Field({ type: 'number', required: false, default: () => Date.now() })
|
|
updatedAt: number;
|
|
|
|
@BelongsTo(() => User, 'userId')
|
|
user: User;
|
|
}
|
|
|
|
// User Model
|
|
@Model({
|
|
scope: 'global',
|
|
type: 'docstore'
|
|
})
|
|
export class User extends BaseModel {
|
|
@Field({ type: 'string', required: true, unique: true })
|
|
username: string;
|
|
|
|
@Field({ type: 'string', required: true, unique: true })
|
|
email: string;
|
|
|
|
@Field({ type: 'string', required: false })
|
|
displayName?: string;
|
|
|
|
@Field({ type: 'string', required: false })
|
|
avatar?: string;
|
|
|
|
@Field({ type: 'boolean', required: false, default: true })
|
|
isActive: boolean;
|
|
|
|
@Field({ type: 'array', required: false, default: [] })
|
|
roles: string[];
|
|
|
|
@Field({ type: 'number', required: false })
|
|
createdAt: number;
|
|
|
|
@Field({ type: 'number', required: false })
|
|
lastLoginAt?: number;
|
|
|
|
@HasMany(() => Post, 'authorId')
|
|
posts: Post[];
|
|
|
|
@HasMany(() => Comment, 'authorId')
|
|
comments: Comment[];
|
|
|
|
@HasOne(() => UserProfile, 'userId')
|
|
profile: UserProfile;
|
|
|
|
@BeforeCreate()
|
|
setTimestamps() {
|
|
this.createdAt = Date.now();
|
|
}
|
|
|
|
// Helper methods
|
|
async updateLastLogin(): Promise<void> {
|
|
this.lastLoginAt = Date.now();
|
|
await this.save();
|
|
}
|
|
|
|
toJSON() {
|
|
const json = super.toJSON();
|
|
// Don't expose sensitive data in API responses
|
|
delete json.password;
|
|
return json;
|
|
}
|
|
}
|
|
|
|
// Category Model
|
|
@Model({
|
|
scope: 'global',
|
|
type: 'docstore'
|
|
})
|
|
export class Category extends BaseModel {
|
|
@Field({ type: 'string', required: true, unique: true })
|
|
name: string;
|
|
|
|
@Field({ type: 'string', required: true, unique: true })
|
|
slug: string;
|
|
|
|
@Field({ type: 'string', required: false })
|
|
description?: string;
|
|
|
|
@Field({ type: 'string', required: false })
|
|
color?: string;
|
|
|
|
@Field({ type: 'boolean', required: false, default: true })
|
|
isActive: boolean;
|
|
|
|
@Field({ type: 'number', required: false, default: () => Date.now() })
|
|
createdAt: number;
|
|
|
|
@HasMany(() => Post, 'categoryId')
|
|
posts: Post[];
|
|
|
|
@BeforeCreate()
|
|
generateSlug() {
|
|
if (!this.slug && this.name) {
|
|
this.slug = this.name
|
|
.toLowerCase()
|
|
.replace(/\s+/g, '-')
|
|
.replace(/[^a-z0-9-]/g, '');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Post Model
|
|
@Model({
|
|
scope: 'user',
|
|
type: 'docstore'
|
|
})
|
|
export class Post extends BaseModel {
|
|
@Field({ type: 'string', required: true })
|
|
title: string;
|
|
|
|
@Field({ type: 'string', required: true, unique: true })
|
|
slug: string;
|
|
|
|
@Field({ type: 'string', required: true })
|
|
content: string;
|
|
|
|
@Field({ type: 'string', required: false })
|
|
excerpt?: string;
|
|
|
|
@Field({ type: 'string', required: true })
|
|
authorId: string;
|
|
|
|
@Field({ type: 'string', required: false })
|
|
categoryId?: string;
|
|
|
|
@Field({ type: 'array', required: false, default: [] })
|
|
tags: string[];
|
|
|
|
@Field({ type: 'string', required: false, default: 'draft' })
|
|
status: 'draft' | 'published' | 'archived';
|
|
|
|
@Field({ type: 'string', required: false })
|
|
featuredImage?: string;
|
|
|
|
@Field({ type: 'boolean', required: false, default: false })
|
|
isFeatured: boolean;
|
|
|
|
@Field({ type: 'number', required: false, default: 0 })
|
|
viewCount: number;
|
|
|
|
@Field({ type: 'number', required: false, default: 0 })
|
|
likeCount: number;
|
|
|
|
@Field({ type: 'number', required: false })
|
|
createdAt: number;
|
|
|
|
@Field({ type: 'number', required: false })
|
|
updatedAt: number;
|
|
|
|
@Field({ type: 'number', required: false })
|
|
publishedAt?: number;
|
|
|
|
@BelongsTo(() => User, 'authorId')
|
|
author: User;
|
|
|
|
@BelongsTo(() => Category, 'categoryId')
|
|
category: Category;
|
|
|
|
@HasMany(() => Comment, 'postId')
|
|
comments: Comment[];
|
|
|
|
@BeforeCreate()
|
|
setTimestamps() {
|
|
const now = Date.now();
|
|
this.createdAt = now;
|
|
this.updatedAt = now;
|
|
|
|
// Generate slug before validation if missing
|
|
if (!this.slug && this.title) {
|
|
this.slug = this.title
|
|
.toLowerCase()
|
|
.replace(/\s+/g, '-')
|
|
.replace(/[^a-z0-9-]/g, '');
|
|
}
|
|
}
|
|
|
|
@AfterCreate()
|
|
finalizeSlug() {
|
|
// Add unique identifier to slug after creation to ensure uniqueness
|
|
if (this.slug && this.id) {
|
|
this.slug = this.slug + '-' + this.id.slice(-8);
|
|
}
|
|
}
|
|
|
|
// Helper methods
|
|
async publish(): Promise<void> {
|
|
this.status = 'published';
|
|
this.publishedAt = Date.now();
|
|
this.updatedAt = Date.now();
|
|
await this.save();
|
|
}
|
|
|
|
async unpublish(): Promise<void> {
|
|
this.status = 'draft';
|
|
this.publishedAt = undefined;
|
|
this.updatedAt = Date.now();
|
|
await this.save();
|
|
}
|
|
|
|
async incrementViews(): Promise<void> {
|
|
this.viewCount += 1;
|
|
await this.save();
|
|
}
|
|
|
|
async like(): Promise<void> {
|
|
this.likeCount += 1;
|
|
await this.save();
|
|
}
|
|
|
|
async unlike(): Promise<void> {
|
|
if (this.likeCount > 0) {
|
|
this.likeCount -= 1;
|
|
await this.save();
|
|
}
|
|
}
|
|
|
|
async archive(): Promise<void> {
|
|
this.status = 'archived';
|
|
this.updatedAt = Date.now();
|
|
await this.save();
|
|
}
|
|
}
|
|
|
|
// Comment Model
|
|
@Model({
|
|
scope: 'user',
|
|
type: 'docstore'
|
|
})
|
|
export class Comment extends BaseModel {
|
|
@Field({ type: 'string', required: true })
|
|
content: string;
|
|
|
|
@Field({ type: 'string', required: true })
|
|
postId: string;
|
|
|
|
@Field({ type: 'string', required: true })
|
|
authorId: string;
|
|
|
|
@Field({ type: 'string', required: false })
|
|
parentId?: string; // For nested comments
|
|
|
|
@Field({ type: 'boolean', required: false, default: true })
|
|
isApproved: boolean;
|
|
|
|
@Field({ type: 'number', required: false, default: 0 })
|
|
likeCount: number;
|
|
|
|
@Field({ type: 'number', required: false })
|
|
createdAt: number;
|
|
|
|
@Field({ type: 'number', required: false })
|
|
updatedAt: number;
|
|
|
|
@BelongsTo(() => Post, 'postId')
|
|
post: Post;
|
|
|
|
@BelongsTo(() => User, 'authorId')
|
|
author: User;
|
|
|
|
@BelongsTo(() => Comment, 'parentId')
|
|
parent?: Comment;
|
|
|
|
@HasMany(() => Comment, 'parentId')
|
|
replies: Comment[];
|
|
|
|
@BeforeCreate()
|
|
setTimestamps() {
|
|
const now = Date.now();
|
|
this.createdAt = now;
|
|
this.updatedAt = now;
|
|
}
|
|
|
|
// Helper methods
|
|
async approve(): Promise<void> {
|
|
this.isApproved = true;
|
|
this.updatedAt = Date.now();
|
|
await this.save();
|
|
}
|
|
|
|
async like(): Promise<void> {
|
|
this.likeCount += 1;
|
|
await this.save();
|
|
}
|
|
|
|
async unlike(): Promise<void> {
|
|
if (this.likeCount > 0) {
|
|
this.likeCount -= 1;
|
|
await this.save();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Type definitions for API requests
|
|
export interface CreateUserRequest {
|
|
username: string;
|
|
email: string;
|
|
displayName?: string;
|
|
avatar?: string;
|
|
roles?: string[];
|
|
}
|
|
|
|
export interface CreateCategoryRequest {
|
|
name: string;
|
|
description?: string;
|
|
color?: string;
|
|
}
|
|
|
|
export interface CreatePostRequest {
|
|
title: string;
|
|
content: string;
|
|
excerpt?: string;
|
|
authorId: string;
|
|
categoryId?: string;
|
|
tags?: string[];
|
|
featuredImage?: string;
|
|
status?: 'draft' | 'published';
|
|
}
|
|
|
|
export interface CreateCommentRequest {
|
|
content: string;
|
|
postId: string;
|
|
authorId: string;
|
|
parentId?: string;
|
|
}
|
|
|
|
export interface UpdatePostRequest {
|
|
title?: string;
|
|
content?: string;
|
|
excerpt?: string;
|
|
categoryId?: string;
|
|
tags?: string[];
|
|
featuredImage?: string;
|
|
isFeatured?: boolean;
|
|
}
|
|
|
|
// Initialize field configurations after all models are defined
|
|
setupFieldConfigurations();
|