initial describe catalog database

This commit is contained in:
Evert Prants 2023-07-22 13:37:14 +03:00
parent b6460f1cda
commit 42d4c4e40c
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
31 changed files with 1080 additions and 2 deletions

View File

@ -1,10 +1,24 @@
import { Module } from '@nestjs/common'; import { Module, OnModuleInit } from '@nestjs/common';
import { CatalogController } from './catalog.controller'; import { CatalogController } from './catalog.controller';
import { CatalogService } from './catalog.service'; import { CatalogService } from './catalog.service';
import { ClientsModule } from '@nestjs/microservices'; import { ClientsModule } from '@nestjs/microservices';
import { makeKnex, makeTypeOrm, natsClient } from '@freeblox/shared'; import { makeKnex, makeTypeOrm, natsClient } from '@freeblox/shared';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import knex from 'knex';
import { ContentEntity } from './database/entities/content.entity';
import { ContentRevisionEntity } from './database/entities/content-revision.entity';
import {
ContentModerationEntity,
ContentModerationBanEntity,
} from './database/entities/content-moderation.entity';
import { ContentAssetEntity } from './database/entities/content-asset.entity';
import { ContentPriceEntity } from './database/entities/content-price.entity';
import { ContentOwnershipEntity } from './database/entities/content-ownership.entity';
import { ContentTradeEntity } from './database/entities/content-trade.entity';
import { ContentFavoriteEntity } from './database/entities/content-favorite.entity';
import { ContentVoteEntity } from './database/entities/content-vote.entity';
import { ContentReportEntity } from './database/entities/content-report.entity';
@Module({ @Module({
imports: [ imports: [
@ -17,6 +31,19 @@ import { TypeOrmModule } from '@nestjs/typeorm';
inject: [ConfigService], inject: [ConfigService],
useFactory: (config: ConfigService) => config.get('typeorm'), useFactory: (config: ConfigService) => config.get('typeorm'),
}), }),
TypeOrmModule.forFeature([
ContentEntity,
ContentRevisionEntity,
ContentModerationEntity,
ContentModerationBanEntity,
ContentAssetEntity,
ContentPriceEntity,
ContentOwnershipEntity,
ContentTradeEntity,
ContentFavoriteEntity,
ContentVoteEntity,
ContentReportEntity,
]),
ClientsModule.register([ ClientsModule.register([
natsClient('catalog'), natsClient('catalog'),
natsClient('auth'), natsClient('auth'),
@ -26,4 +53,12 @@ import { TypeOrmModule } from '@nestjs/typeorm';
controllers: [CatalogController], controllers: [CatalogController],
providers: [CatalogService], providers: [CatalogService],
}) })
export class CatalogModule {} export class CatalogModule implements OnModuleInit {
constructor(private readonly config: ConfigService) {}
async onModuleInit() {
const knexInstance = knex(this.config.get('knex'));
await knexInstance.migrate.latest();
// await knexInstance.seed.run();
}
}

View File

@ -0,0 +1,44 @@
import { Expose } from 'class-transformer';
import {
Column,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { ContentAssetType } from '../../enums/content-asset-type.enum';
import { ContentRevisionEntity } from './content-revision.entity';
import { ContentEntity } from './content.entity';
@Entity('content_asset')
@Expose()
export class ContentAssetEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'content_id' })
contentId: number;
@Column({ name: 'revision_id' })
revisionId: number;
@Column({ name: 'asset_id', type: 'uuid' })
assetId: string;
@Column({ type: 'string', enum: ContentAssetType })
type: ContentAssetType;
@Column({ name: 'type_name', nullable: true })
typeName?: string;
@Column()
index: number;
@ManyToOne(() => ContentEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'content_id' })
content: ContentEntity;
@ManyToOne(() => ContentRevisionEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'revision_id' })
revision: ContentRevisionEntity;
}

View File

@ -0,0 +1,31 @@
import { Exclude, Expose } from 'class-transformer';
import {
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { ContentEntity } from './content.entity';
import { MetaEntity } from '@freeblox/shared';
@Entity('content_favorite')
@Expose()
export class ContentFavoriteEntity extends MetaEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'content_id' })
@Exclude()
contentId: number;
@Column({ name: 'user_id', type: 'uuid', nullable: true })
@Index()
@Exclude()
userId: string;
@ManyToOne(() => ContentEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'content_id' })
content: ContentEntity;
}

View File

@ -0,0 +1,107 @@
import { MetaEntity } from '@freeblox/shared';
import { Exclude, Expose, Type } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { ContentEntity } from './content.entity';
import { ContentRevisionEntity } from './content-revision.entity';
import { ModeratorAction } from '../../enums/moderation-action.enum';
import { RejectionReason } from '../../enums/rejection-reason.enum';
import {
IsBoolean,
IsEnum,
IsNumber,
IsOptional,
IsString,
} from 'class-validator';
import { BanEntity } from 'apps/auth/src/database/entities/ban.entity';
@Entity('content_moderation')
@Expose()
export class ContentModerationEntity extends MetaEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'content_id' })
@IsNumber()
contentId: number;
@Column({ name: 'revision_id' })
@IsNumber()
revisionId: number;
@Column({ name: 'user_id', type: 'uuid', nullable: true })
@Exclude()
userId: string;
@Column({
type: 'enum',
enum: ModeratorAction,
default: ModeratorAction.PENDING,
})
@IsEnum(ModeratorAction)
action: ModeratorAction;
@Column({
type: 'enum',
enum: RejectionReason,
nullable: true,
name: 'rejection_reason',
})
@IsEnum(RejectionReason)
rejectionReason?: RejectionReason;
@Column({ nullable: true })
@IsString()
@IsOptional()
description?: string;
@Column({ default: false })
@IsBoolean()
@IsOptional()
penalty: boolean;
@Column({ default: false, name: 'asset_delete' })
@IsBoolean()
@IsOptional()
assetDelete: boolean;
@CreateDateColumn({ name: 'created_at' })
@Expose()
createdAt: Date;
@CreateDateColumn({ name: 'decided_at' })
decidedAt: Date;
@ManyToOne(() => ContentEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'content_id' })
content: ContentEntity;
@ManyToOne(() => ContentRevisionEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'revision_id' })
revision: ContentRevisionEntity;
@OneToMany(() => ContentModerationBanEntity, (ban) => ban.moderation)
@Type(() => ContentModerationBanEntity)
bans?: ContentModerationBanEntity[];
}
@Entity('content_moderation_ban')
export class ContentModerationBanEntity {
@Column({ nullable: true, name: 'ban_id' })
banId: number;
@ManyToOne(() => ContentModerationEntity, (mod) => mod.bans)
@JoinColumn({ name: 'content_moderation_id' })
@Expose()
moderation: ContentModerationEntity;
@Expose()
ban?: BanEntity;
}

View File

@ -0,0 +1,82 @@
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { ContentEntity } from './content.entity';
import { Currency } from '../../enums/currency.enum';
import { UserMetaEntity } from '@freeblox/shared';
@Entity('content_ownership')
@Expose()
export class ContentOwnershipEntity extends UserMetaEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'content_id' })
contentId: number;
@Column({ name: 'user_id', type: 'uuid', nullable: true })
@Exclude()
userId: string;
@Column({ name: 'previous_ownership_id', nullable: true })
@Exclude()
previousOwnershipId: string;
@Column({ name: 'purchase_price', nullable: true })
@Index()
purchasePrice: number;
@Column({
type: 'enum',
enum: Currency,
name: 'purchase_currency',
nullable: true,
})
@Index()
purchaseCurrency: Currency;
@Column({ nullable: true })
@Index()
serial: number;
@ManyToOne(() => ContentEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'content_id' })
@Exclude()
content: ContentEntity;
@ManyToOne(() => ContentOwnershipEntity, { onDelete: 'SET NULL' })
@JoinColumn({ name: 'previous_ownership_id' })
@Exclude()
previous?: ContentOwnershipEntity;
@CreateDateColumn({ name: 'created_at' })
@Expose()
createdAt: Date;
@Column({ type: Date, name: 'ended_at', nullable: true })
endedAt: Date;
@Column({ type: Date, name: 'expires_at', nullable: true })
expiresAt: Date;
/**
* is ownership expired
*/
get expired() {
return this.expiresAt.getTime() < Date.now();
}
/**
* is ownership invalid (Expired or ended)
*/
get invalid() {
return !!this.endedAt || this.expired;
}
}

View File

@ -0,0 +1,35 @@
import { Exclude, Expose } from 'class-transformer';
import {
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { ContentEntity } from './content.entity';
import { Currency } from '../../enums/currency.enum';
import { UserMetaEntity } from '@freeblox/shared';
@Entity('content_price')
@Expose()
export class ContentPriceEntity extends UserMetaEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'content_id' })
contentId: number;
@Column()
@Index()
price: number;
@Column({ type: 'enum', enum: Currency })
@Index()
currency: Currency;
@ManyToOne(() => ContentEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'content_id' })
@Exclude()
content: ContentEntity;
}

View File

@ -0,0 +1,82 @@
import { MetaEntity } from '@freeblox/shared';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
JoinTable,
ManyToMany,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { ContentEntity } from './content.entity';
import { ContentRevisionEntity } from './content-revision.entity';
import { IsNumber, IsOptional, IsString } from 'class-validator';
import { ReportStatus } from '../../enums/report-status.enum';
import { ContentModerationEntity } from './content-moderation.entity';
@Entity('content_report')
@Expose()
export class ContentReportEntity extends MetaEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'content_id' })
@IsNumber()
contentId: number;
@Column({ name: 'revision_id' })
revisionId: number;
@Column({ name: 'user_id', type: 'uuid' })
@Exclude()
userId: string;
@Column({ name: 'moderator_id', type: 'uuid', nullable: true })
@Exclude()
moderatorId?: string;
@Column()
@IsString()
reason: string;
@Column()
@IsString()
description: string;
@Column({ nullable: true })
@IsString()
@IsOptional()
notes?: string;
@Column({ type: 'enum', enum: ReportStatus })
status: ReportStatus;
@CreateDateColumn({ name: 'created_at' })
@Expose()
createdAt: Date;
@CreateDateColumn({ name: 'resolved_at' })
resolvedAt: Date;
@ManyToOne(() => ContentEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'content_id' })
content: ContentEntity;
@ManyToOne(() => ContentRevisionEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'revision_id' })
revision?: ContentRevisionEntity;
@ManyToMany(() => ContentModerationEntity)
@JoinTable({
name: 'content_report_action',
joinColumn: {
name: 'content_report_id',
},
inverseJoinColumn: {
name: 'content_moderation_id',
},
})
actions?: ContentModerationEntity[];
}

View File

@ -0,0 +1,35 @@
import { UserMetaEntity } from '@freeblox/shared';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { ContentEntity } from './content.entity';
@Entity('content_revision')
@Exclude()
export class ContentRevisionEntity extends UserMetaEntity {
@PrimaryGeneratedColumn()
@Expose()
id: number;
@Column({ name: 'content_id' })
contentId: number;
@CreateDateColumn({ name: 'created_at' })
@Expose()
createdAt: Date;
@DeleteDateColumn({ name: 'deleted_at' })
@Expose()
deletedAt?: Date;
@ManyToOne(() => ContentEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'content_id' })
content: ContentEntity;
}

View File

@ -0,0 +1,58 @@
import { Exclude, Expose } from 'class-transformer';
import {
Column,
Entity,
Index,
JoinTable,
ManyToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { MetaEntity } from '@freeblox/shared';
import { TradeStatus } from '../../enums/trade-status.enum';
import { ContentOwnershipEntity } from './content-ownership.entity';
@Entity('content_trade')
@Expose()
export class ContentTradeEntity extends MetaEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', type: 'uuid' })
@Exclude()
userId: string;
@Column({ name: 'recipient_id', type: 'uuid' })
@Exclude()
recipientId: string;
@Column({ type: 'enum', enum: TradeStatus })
@Index()
status: TradeStatus;
@Column()
description: string;
@ManyToMany(() => ContentOwnershipEntity)
@JoinTable({
name: 'content_trade_user_content',
joinColumn: {
name: 'content_trade_id',
},
inverseJoinColumn: {
name: 'content_ownership_id',
},
})
userItems: ContentOwnershipEntity[];
@ManyToMany(() => ContentOwnershipEntity)
@JoinTable({
name: 'content_trade_recipient_content',
joinColumn: {
name: 'content_trade_id',
},
inverseJoinColumn: {
name: 'content_ownership_id',
},
})
recipientItems: ContentOwnershipEntity[];
}

View File

@ -0,0 +1,36 @@
import { Exclude, Expose } from 'class-transformer';
import {
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { ContentEntity } from './content.entity';
import { MetaEntity } from '@freeblox/shared';
import { Vote } from '../../enums/vote.enum';
@Entity('content_vote')
@Expose()
export class ContentVoteEntity extends MetaEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'content_id' })
@Exclude()
contentId: number;
@Column({ name: 'user_id', type: 'uuid', nullable: true })
@Index()
@Exclude()
userId: string;
@Column({ type: 'enum', enum: Vote })
vote: Vote;
@ManyToOne(() => ContentEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'content_id' })
@Exclude()
content: ContentEntity;
}

View File

@ -0,0 +1,109 @@
import { UserMetaEntity } from '@freeblox/shared';
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Privacy } from '../../enums/privacy.enum';
import { ContentType } from '../../enums/content-type.enum';
import { Exclude, Expose } from 'class-transformer';
import { UserEntity } from 'apps/auth/src/database/entities/user.entity';
import {
IsBoolean,
IsEnum,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
MaxLength,
} from 'class-validator';
@Entity('content')
@Expose()
export class ContentEntity extends UserMetaEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 255 })
@Index()
@IsString()
@IsNotEmpty()
@MaxLength(255)
name: string;
@Column()
@IsString()
@IsNotEmpty()
@MaxLength(5000)
description: string;
@Column({ type: 'uuid', name: 'user_id' })
userId: string;
@Column({ name: 'parent_id', nullable: true })
parentId: number;
@Column({ default: false })
@IsBoolean()
@IsOptional()
restricted: boolean;
@Column({ default: false })
@IsBoolean()
@IsOptional()
onsale: boolean;
@Column({ default: false })
@IsBoolean()
@IsOptional()
published: boolean;
@Column({ default: true, name: 'comments_enabled' })
@IsBoolean()
@IsOptional()
commentsEnabled: boolean;
@Column({ default: false, name: 'open_source' })
@IsBoolean()
@IsOptional()
openSource: boolean;
@Column({ type: 'enum', enum: Privacy, default: Privacy.PUBLIC })
@IsEnum(Privacy)
privacy: Privacy;
@Column({ type: 'string', enum: ContentType })
@IsEnum(ContentType)
@Index()
type: ContentType;
@Column({ unsigned: true, nullable: true })
@IsNumber()
@IsOptional()
stock: number;
@Column({ nullable: true })
@IsString()
@IsOptional()
license: string;
@ManyToOne(() => ContentEntity)
@JoinColumn({ name: 'parent_id' })
@Exclude()
parent?: ContentEntity;
@CreateDateColumn({ name: 'created_at' })
@Expose()
createdAt: Date;
@DeleteDateColumn({ name: 'deleted_at' })
@Exclude()
deletedAt?: Date;
user?: UserEntity;
}

View File

@ -0,0 +1,41 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('content', (table) => {
table.increments('id').primary();
table.string('name', 255).notNullable().index();
table.text('description').notNullable();
table.uuid('user_id').nullable();
table.integer('parent_id').unsigned().nullable();
table.boolean('restricted').defaultTo(false);
table.boolean('onsale').defaultTo(false).index();
table.boolean('published').defaultTo(false);
table.boolean('comments_enabled').defaultTo(true);
table.boolean('open_source').defaultTo(false);
table
.enum('privacy', ['public', 'friends', 'unlisted', 'private'])
.notNullable()
.defaultTo('public');
table.string('type').notNullable().index().defaultTo('content');
table.integer('stock').unsigned().nullable();
table.text('license').nullable();
table.uuid('created_by').nullable();
table.uuid('updated_by').nullable();
table.timestamps(true, true);
table.timestamp('deleted_at');
table.foreign('parent_id').references('content.id').onDelete('CASCADE');
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('content');
}

View File

@ -0,0 +1,21 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('content_revision', (table) => {
table.increments('id').primary();
table.integer('content_id').unsigned().notNullable();
table.uuid('created_by').nullable();
table.uuid('updated_by').nullable();
table.timestamps(true, true);
table.timestamp('deleted_at');
table.foreign('content_id').references('content.id').onDelete('CASCADE');
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('content_revision');
}

View File

@ -0,0 +1,54 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
await Promise.all([
knex.schema.createTable('content_moderation', (table) => {
table.increments('id').primary();
table.integer('content_id').unsigned().notNullable();
table.integer('revision_id').unsigned().notNullable();
table.uuid('user_id').nullable();
table
.enum('action', ['pending', 'approve', 'reject', 'forward'])
.index()
.notNullable()
.defaultTo('pending');
table
.enum('rejection_reason', ['tos', 'illegal', 'dmca', 'other'])
.index()
.nullable();
table.text('description').nullable();
table.boolean('penalty').notNullable().defaultTo(false);
table.boolean('asset_delete').notNullable().defaultTo(false);
table.timestamps(true, true);
table.timestamp('decided_at');
table.foreign('content_id').references('content.id').onDelete('CASCADE');
table
.foreign('revision_id')
.references('content_revision.id')
.onDelete('CASCADE');
}),
knex.schema.createTable('content_moderation_ban', (table) => {
table.integer('content_moderation_id').unsigned().notNullable();
table.integer('ban_id').unsigned().nullable();
table
.foreign('content_moderation_id')
.references('content_moderation.id')
.onDelete('CASCADE');
}),
]);
}
export async function down(knex: Knex): Promise<void> {
await Promise.all([
knex.schema.dropTable('content_moderation'),
knex.schema.dropTable('content_moderation_ban'),
]);
}

View File

@ -0,0 +1,25 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('content_asset', (table) => {
table.increments('id').primary();
table.integer('content_id').unsigned().notNullable();
table.integer('revision_id').unsigned().notNullable();
table.uuid('asset_id').notNullable().index();
table.string('type').notNullable().index();
table.string('type_name').nullable();
table.integer('index').notNullable().defaultTo(0);
table.foreign('content_id').references('content.id').onDelete('CASCADE');
table
.foreign('revision_id')
.references('content_revision.id')
.onDelete('CASCADE');
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('content_asset');
}

View File

@ -0,0 +1,23 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('content_price', (table) => {
table.increments('id').primary();
table.integer('content_id').unsigned().notNullable();
table.integer('price').unsigned().notNullable().defaultTo(0).index();
table.enum('currency', ['whole', 'denom']).notNullable().index();
table.uuid('created_by').nullable();
table.uuid('updated_by').nullable();
table.timestamps(true, true);
table.foreign('content_id').references('content.id').onDelete('CASCADE');
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('content_price');
}

View File

@ -0,0 +1,40 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('content_ownership', (table) => {
table.increments('id').primary();
table.integer('content_id').unsigned().notNullable();
table.uuid('user_id').notNullable();
table
.enum('source', ['author', 'purchase', 'trade', 'gift'])
.notNullable()
.defaultTo('author')
.index();
table.integer('previous_ownership_id').unsigned().nullable();
table.integer('purchase_price').nullable();
table.enum('purchase_currency', ['whole', 'denom']).nullable();
table.integer('serial').unsigned().nullable();
table.uuid('created_by').nullable();
table.uuid('updated_by').nullable();
table.timestamps(true, true);
table.timestamp('ended_at').nullable();
table.timestamp('expires_at').nullable();
table.foreign('content_id').references('content.id').onDelete('CASCADE');
table
.foreign('previous_ownership_id')
.references('content_ownership.id')
.onDelete('CASCADE');
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('content_ownership');
}

View File

@ -0,0 +1,61 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
await Promise.all([
knex.schema.createTable('content_trade', (table) => {
table.increments('id').primary();
table.uuid('user_id').notNullable();
table.uuid('recipient_id').notNullable();
table
.enum('status', [
'sent',
'opened',
'rejected',
'return_to_sender',
'confirmed',
'cancelled',
])
.notNullable()
.defaultTo('sent')
.index();
table.text('description').nullable();
table.timestamps(true, true);
}),
knex.schema.createTable('content_trade_user_content', (table) => {
table.integer('content_trade_id').unsigned().notNullable();
table.integer('content_ownership_id').unsigned().notNullable();
table
.foreign('content_trade_id')
.references('content_trade.id')
.onDelete('CASCADE');
table
.foreign('content_ownership_id')
.references('content_ownership.id')
.onDelete('CASCADE');
}),
knex.schema.createTable('content_trade_recipient_content', (table) => {
table.integer('content_trade_id').unsigned().notNullable();
table.integer('content_ownership_id').unsigned().notNullable();
table
.foreign('content_trade_id')
.references('content_trade.id')
.onDelete('CASCADE');
table
.foreign('content_ownership_id')
.references('content_ownership.id')
.onDelete('CASCADE');
}),
]);
}
export async function down(knex: Knex): Promise<void> {
await Promise.all([
knex.schema.dropTable('content_trade'),
knex.schema.dropTable('content_trade_user_content'),
knex.schema.dropTable('content_trade_recipient_content'),
]);
}

View File

@ -0,0 +1,18 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('content_favorite', (table) => {
table.increments('id').primary();
table.integer('content_id').unsigned().notNullable();
table.uuid('user_id').index().notNullable();
table.timestamps(true, true);
table.foreign('content_id').references('content.id').onDelete('CASCADE');
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('content_favorite');
}

View File

@ -0,0 +1,20 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('content_vote', (table) => {
table.increments('id').primary();
table.integer('content_id').index().unsigned().notNullable();
table.uuid('user_id').notNullable();
table.enum('vote', ['up', 'down']).index().notNullable();
table.timestamps(true, true);
table.foreign('content_id').references('content.id').onDelete('CASCADE');
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('content_vote');
}

View File

@ -0,0 +1,47 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
await Promise.all([
knex.schema.createTable('content_report', (table) => {
table.increments('id').primary();
table.integer('content_id').index().unsigned().notNullable();
table.uuid('user_id').notNullable();
table.uuid('moderator_id').nullable();
table.text('reason').notNullable();
table.text('description').notNullable();
table.text('notes').nullable();
table
.enum('status', ['open', 'closed', 'invalid', 'resolved'])
.index()
.notNullable()
.defaultTo('open');
table.timestamps(true, true);
table.timestamp('resolved_at');
table.foreign('content_id').references('content.id').onDelete('CASCADE');
}),
knex.schema.createTable('content_report_action', (table) => {
table.integer('content_report_id').index().unsigned().notNullable();
table.integer('content_moderation_id').index().unsigned().notNullable();
table
.foreign('content_report_id')
.references('content_report.id')
.onDelete('CASCADE');
table
.foreign('content_moderation_id')
.references('content_moderation.id')
.onDelete('CASCADE');
}),
]);
}
export async function down(knex: Knex): Promise<void> {
await Promise.all([
knex.schema.dropTable('content_report'),
knex.schema.dropTable('content_report_action'),
]);
}

View File

@ -0,0 +1,11 @@
export enum ContentAssetType {
IMAGE = 'image',
TEXTURE = 'texture',
TEXTURE3D = 'texture3d',
MESH = 'mesh',
ANIMATION = 'animation',
GAMEOBJECT = 'gameobject',
WORLD = 'world',
CHARACTER = 'character',
SOUND = 'sound',
}

View File

@ -0,0 +1,18 @@
export enum ContentType {
CONTENT = 'content',
CHARACTER = 'character',
HAT = 'hat',
ACCESSORY = 'accessory',
FRONT = 'front',
BACK = 'back',
TOOL = 'tool',
MESH = 'mesh',
TEXTURE = 'texture',
GAMEOBJECT = 'gameobject',
SOUND = 'sound',
ANIMATION = 'animation',
COMMENT = 'comment',
STATUS = 'status',
GAME = 'game',
WORLD = 'world',
}

View File

@ -0,0 +1,4 @@
export enum Currency {
WHOLE = 'whole',
DENOMINATION = 'denom',
}

View File

@ -0,0 +1,6 @@
export enum ModeratorAction {
PENDING = 'pending',
APPROVE = 'approve',
REJECT = 'reject',
FORWARD = 'forward',
}

View File

@ -0,0 +1,6 @@
export enum Privacy {
PUBLIC = 'public',
FRIENDS = 'friends',
UNLISTED = 'unlisted',
PRIVATE = 'private',
}

View File

@ -0,0 +1,6 @@
export enum RejectionReason {
TERMS_OF_SERVICE = 'tos',
ILLEGAL_CONTENT = 'illegal',
DMCA = 'dmca',
OTHER = 'other',
}

View File

@ -0,0 +1,6 @@
export enum ReportStatus {
OPEN = 'open',
CLOSED = 'closed',
INVALID = 'invalid',
RESOLVED = 'resolved',
}

View File

@ -0,0 +1,8 @@
export enum TradeStatus {
SENT = 'sent',
OPENED = 'opened',
REJECTED = 'rejected',
RETURN_TO_SENDER = 'return_to_sender',
CONFIRMED = 'confirmed',
CANCELLED = 'cancelled',
}

View File

@ -0,0 +1,4 @@
export enum Vote {
UPVOTE = 'up',
DOWNVOTE = 'down',
}

View File

@ -0,0 +1,5 @@
import { getKnex } from '../../../libs/shared/src/';
module.exports = {
development: getKnex('catalog', __dirname, ['.ts', '.js']),
};