Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// src/database/migrations/create-social-engagement-tables.migration.ts
import { MigrationInterface, QueryRunner, Table, TableIndex, TableForeignKey } from 'typeorm';

export class CreateSocialEngagementTables1715076125000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Create ContentType enum
await queryRunner.query(`
CREATE TYPE "content_type_enum" AS ENUM (
'trading_signal',
'market_analysis',
'community_post',
'comment'
)
`);

// Create EngagementType enum
await queryRunner.query(`
CREATE TYPE "engagement_type_enum" AS ENUM (
'like',
'dislike'
)
`);

// Create social_engagements table
await queryRunner.createTable(
new Table({
name: 'social_engagements',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
default: 'uuid_generate_v4()',
},
{
name: 'userId',
type: 'uuid',
isNullable: false,
},
{
name: 'contentId',
type: 'uuid',
isNullable: false,
},
{
name: 'contentType',
type: 'content_type_enum',
isNullable: false,
},
{
name: 'type',
type: 'engagement_type_enum',
isNullable: false,
},
{
name: 'metadata',
type: 'jsonb',
isNullable: true,
},
{
name: 'createdAt',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
},
{
name: 'updatedAt',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
},
],
}),
true,
);

// Create engagement_counters table
await queryRunner.createTable(
new Table({
name: 'engagement_counters',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
default: 'uuid_generate_v4()',
},
{
name: 'contentId',
type: 'uuid',
isNullable: false,
},
{
name: 'contentType',
type: 'content_type_enum',
isNullable: false,
},
{
name: 'likesCount',
type: 'integer',
default: 0,
},
{
name: 'dislikesCount',
type: 'integer',
default: 0,
},
{
name: 'updatedAt',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
},
],
}),
true,
);

// Create indexes
await queryRunner.createIndex(
'social_engagements',
new TableIndex({
name: 'IDX_social_engagements_user_content',
columnNames: ['userId', 'contentId', 'contentType'],
isUnique: true,
}),
);

await queryRunner.createIndex(
'social_engagements',
new TableIndex({
name: 'IDX_social_engagements_user',
columnNames: ['userId'],
}),
);

await queryRunner.createIndex(
'social_engagements',
new TableIndex({
name: 'IDX_social_engagements_content',
columnNames: ['contentId', 'contentType'],
}),
);

await queryRunner.createIndex(
'engagement_counters',
new TableIndex({
name: 'IDX_engagement_counters_content',
columnNames: ['contentId', 'contentType'],
isUnique: true,
}),
);

// Add foreign key for userId if users table exists
const usersTableExists = await queryRunner.hasTable('users');
if (usersTableExists) {
await queryRunner.createForeignKey(
'social_engagements',
new TableForeignKey({
columnNames: ['userId'],
referencedColumnNames: ['id'],
referencedTableName: 'users',
onDelete: 'CASCADE',
}),
);
}
}

public async down(queryRunner: QueryRunner): Promise<void> {
// Drop foreign keys first
const usersTableExists = await queryRunner.hasTable('users');
if (usersTableExists) {
const table = await queryRunner.getTable('social_engagements');
const foreignKey = table.foreignKeys.find(
(fk) => fk.columnNames.indexOf('userId') !== -1,
);
if (foreignKey) {
await queryRunner.dropForeignKey('social_engagements', foreignKey);
}
}

// Drop tables
await queryRunner.dropTable('engagement_counters');
await queryRunner.dropTable('social_engagements');

// Drop enums
await queryRunner.query(`DROP TYPE "engagement_type_enum"`);
await queryRunner.query(`DROP TYPE "content_type_enum"`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// src/shared/enums/content-type.enum.ts
export enum ContentType {
TRADING_SIGNAL = 'trading_signal',
MARKET_ANALYSIS = 'market_analysis',
COMMUNITY_POST = 'community_post',
COMMENT = 'comment',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// src/shared/enums/engagement-type.enum.ts
export enum EngagementType {
LIKE = 'like',
DISLIKE = 'dislike',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// src/shared/interfaces/content-polymorph.interface.ts
import { ContentType } from '../enums/content-type.enum';

export interface ContentPolymorph {
contentId: string;
contentType: ContentType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// src/social-engagement/dto/create-engagement.dto.ts
import { IsEnum, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
import { EngagementType } from '../../shared/enums/engagement-type.enum';
import { ContentType } from '../../shared/enums/content-type.enum';

export class CreateEngagementDto {
@IsNotEmpty()
@IsUUID()
contentId: string;

@IsNotEmpty()
@IsEnum(ContentType)
contentType: ContentType;

@IsNotEmpty()
@IsEnum(EngagementType)
type: EngagementType;

@IsOptional()
metadata?: Record<string, any>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// src/social-engagement/dto/engagement-response.dto.ts
import { EngagementType } from '../../shared/enums/engagement-type.enum';
import { ContentType } from '../../shared/enums/content-type.enum';

export class EngagementResponseDto {
id: string;
userId: string;
contentId: string;
contentType: ContentType;
type: EngagementType;
createdAt: Date;
updatedAt: Date;
counters: {
likes: number;
dislikes: number;
};
metadata?: Record<string, any>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// src/social-engagement/entities/engagement-counter.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, Index, UpdateDateColumn } from 'typeorm';
import { ContentType } from '../../shared/enums/content-type.enum';

@Entity('engagement_counters')
@Index(['contentId', 'contentType'], { unique: true })
export class EngagementCounter {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
@Index()
contentId: string;

@Column({
type: 'enum',
enum: ContentType,
})
contentType: ContentType;

@Column({ default: 0 })
likesCount: number;

@Column({ default: 0 })
dislikesCount: number;

@UpdateDateColumn()
updatedAt: Date;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// src/social-engagement/entities/social-engagement.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn, Index } from 'typeorm';
import { User } from '../../users/entities/user.entity';
import { EngagementType } from '../../shared/enums/engagement-type.enum';
import { ContentType } from '../../shared/enums/content-type.enum';

@Entity('social_engagements')
@Index(['userId', 'contentId', 'contentType'], { unique: true })
export class SocialEngagement {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
@Index()
userId: string;

@ManyToOne(() => User)
@JoinColumn({ name: 'userId' })
user: User;

@Column()
@Index()
contentId: string;

@Column({
type: 'enum',
enum: ContentType,
})
contentType: ContentType;

@Column({
type: 'enum',
enum: EngagementType,
})
type: EngagementType;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;

// Additional metadata can be stored as JSON
@Column({ type: 'jsonb', nullable: true })
metadata: Record<string, any>;
}
Loading
Loading