entities, init schema migration
This commit is contained in:
parent
51344d2018
commit
968d83e58b
|
@ -0,0 +1,51 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class InitSchema1695130227420 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE users (
|
||||
id serial4 NOT NULL,
|
||||
"name" varchar NOT NULL,
|
||||
"password" varchar NOT NULL,
|
||||
email varchar NOT NULL,
|
||||
CONSTRAINT "users_pk" PRIMARY KEY (id),
|
||||
CONSTRAINT "users_email_unique" UNIQUE (email)
|
||||
);
|
||||
|
||||
CREATE TABLE swipes (
|
||||
id serial4 NOT NULL,
|
||||
liked bool NOT NULL,
|
||||
"createdAt" timestamp NOT NULL DEFAULT now(),
|
||||
"sourceUserId" int4 NULL,
|
||||
"targetUserId" int4 NULL,
|
||||
CONSTRAINT "PK_bb38af5831e2c084a78e3622ff6" PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
ALTER TABLE swipes ADD CONSTRAINT "swipes_target_user_fk" FOREIGN KEY ("targetUserId") REFERENCES users(id);
|
||||
ALTER TABLE swipes ADD CONSTRAINT "swipes_source_user_fk" FOREIGN KEY ("sourceUserId") REFERENCES users(id);
|
||||
|
||||
CREATE TABLE pairs (
|
||||
id serial4 NOT NULL,
|
||||
CONSTRAINT "pairs_pk" PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE "pair-members" (
|
||||
"pairId" int4 NOT NULL,
|
||||
"userId" int4 NOT NULL,
|
||||
CONSTRAINT "pair_members_pk" PRIMARY KEY ("pairId", "userId")
|
||||
);
|
||||
|
||||
ALTER TABLE "pair-members" ADD CONSTRAINT "pair_members_user_fk" FOREIGN KEY ("userId") REFERENCES users(id);
|
||||
ALTER TABLE "pair-members" ADD CONSTRAINT "pair_members_pair_fk" FOREIGN KEY ("pairId") REFERENCES pairs(id);
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
DROP TABLE users;
|
||||
DROP TABLE swipes;
|
||||
DROP TABLE pairs;
|
||||
DROP TABLE "pair-members";
|
||||
`);
|
||||
}
|
||||
}
|
|
@ -17,7 +17,9 @@
|
|||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"typeorm": "ts-node ./node_modules/typeorm/cli",
|
||||
"migrate:run": "npm run typeorm migration:run -- -d ./typeorm.config.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^9.0.0",
|
||||
|
@ -51,6 +53,7 @@
|
|||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "29.5.1",
|
||||
"@types/lodash": "^4.14.198",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "18.16.12",
|
||||
"@types/passport-jwt": "^3.0.9",
|
||||
"@types/passport-local": "^1.0.35",
|
||||
|
@ -87,4 +90,4 @@
|
|||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@ import { Module } from '@nestjs/common';
|
|||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { SwipesModule } from './swipes/swipes.module';
|
||||
import { PairsModule } from './pairs/pairs.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
@ -15,8 +17,11 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
entities: ['dist/**/*.entity.js'],
|
||||
// synchronize: true,
|
||||
// logging: ['query']
|
||||
}),
|
||||
SwipesModule,
|
||||
PairsModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
import helmet from 'helmet';
|
||||
import { AppModule } from './app.module';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { Entity, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
import { Pair } from './pair.entity';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
|
||||
@Entity('pair-members')
|
||||
export class PairMember {
|
||||
@PrimaryColumn('int', { name: 'pairId' })
|
||||
@ManyToOne(() => Pair, (pair) => pair.pairMembers)
|
||||
pair: Pair;
|
||||
|
||||
@PrimaryColumn('int', { name: 'userId' })
|
||||
@ManyToOne(() => User)
|
||||
user: User;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { PairMember } from './pair-member.entity';
|
||||
|
||||
@Entity('pairs')
|
||||
export class Pair {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@OneToMany(() => PairMember, (pairMember) => pairMember.pair)
|
||||
pairMembers: PairMember[];
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { PairsService } from './pairs.service';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Pair } from './entities/pair.entity';
|
||||
import { PairMember } from './entities/pair-member.entity';
|
||||
import { UsersModule } from 'src/users/users.module';
|
||||
import { Swipe } from 'src/swipes/entities/swipe.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Pair, PairMember, Swipe]), UsersModule],
|
||||
providers: [PairsService],
|
||||
exports: [PairsService],
|
||||
})
|
||||
export class PairsModule {}
|
|
@ -0,0 +1,77 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Pair } from './entities/pair.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { PairMember } from './entities/pair-member.entity';
|
||||
import { UsersService } from 'src/users/users.service';
|
||||
import {
|
||||
UserIdNotExistsException,
|
||||
UserNotBelongsToPair,
|
||||
} from 'src/utils/errors';
|
||||
import { Swipe } from 'src/swipes/entities/swipe.entity';
|
||||
|
||||
@Injectable()
|
||||
export class PairsService {
|
||||
constructor(
|
||||
@InjectRepository(Pair)
|
||||
private readonly pairsRepository: Repository<Pair>,
|
||||
@InjectRepository(PairMember)
|
||||
private readonly pairMembersRepository: Repository<PairMember>,
|
||||
private readonly usersService: UsersService,
|
||||
) {}
|
||||
|
||||
async createPair(userIds: [number, number]) {
|
||||
const users = await this.usersService.getByIds(userIds);
|
||||
if (users.length !== 2) {
|
||||
throw new UserIdNotExistsException();
|
||||
}
|
||||
|
||||
const pair = await this.pairsRepository.manager.transaction(
|
||||
async (manager) => {
|
||||
const pair = this.pairsRepository.create();
|
||||
await manager.save(pair);
|
||||
const pairMembers = this.pairMembersRepository.create([
|
||||
{
|
||||
pair,
|
||||
user: { id: userIds[0] },
|
||||
},
|
||||
{
|
||||
pair,
|
||||
user: { id: userIds[1] },
|
||||
},
|
||||
]);
|
||||
await manager.save(pairMembers);
|
||||
return pair;
|
||||
},
|
||||
);
|
||||
|
||||
return pair;
|
||||
}
|
||||
|
||||
async destroyPair(userId: number, pairId: number) {
|
||||
const pair = await this.pairsRepository.findOne({
|
||||
where: { id: pairId },
|
||||
relations: { pairMembers: { user: true } },
|
||||
});
|
||||
const members = pair.pairMembers;
|
||||
if (!members.some((member: PairMember) => member.user.id === userId)) {
|
||||
throw new UserNotBelongsToPair();
|
||||
}
|
||||
const anotherMember = members.find(
|
||||
(member: PairMember) => member.user.id !== userId,
|
||||
);
|
||||
|
||||
await this.pairsRepository.manager.transaction(async (manager) => {
|
||||
await manager.delete(PairMember, { pair });
|
||||
await manager.delete(Pair, { id: pairId });
|
||||
await manager.update(
|
||||
Swipe,
|
||||
{
|
||||
sourceUser: { id: userId },
|
||||
targetUser: { id: anotherMember.user.id },
|
||||
},
|
||||
{ liked: false },
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
|
||||
@Entity('swipes')
|
||||
export class Swipe {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.swipes)
|
||||
sourceUser: User;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.swiped)
|
||||
targetUser: User;
|
||||
|
||||
@Column()
|
||||
liked: boolean;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamp' })
|
||||
createdAt: Date;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Swipe } from './entities/swipe.entity';
|
||||
import { SwipesService } from './swipes.service';
|
||||
import { UsersModule } from 'src/users/users.module';
|
||||
import { PairsModule } from 'src/pairs/pairs.module';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Swipe]), UsersModule, PairsModule],
|
||||
providers: [SwipesService],
|
||||
})
|
||||
export class SwipesModule {}
|
|
@ -0,0 +1,58 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Swipe } from './entities/swipe.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { UsersService } from 'src/users/users.service';
|
||||
import { UserIdNotExistsException } from 'src/utils/errors';
|
||||
import { PairsService } from 'src/pairs/pairs.service';
|
||||
|
||||
@Injectable()
|
||||
export class SwipesService {
|
||||
constructor(
|
||||
@InjectRepository(Swipe)
|
||||
private readonly swipesRepository: Repository<Swipe>,
|
||||
private readonly usersService: UsersService,
|
||||
private readonly pairsService: PairsService,
|
||||
) {}
|
||||
|
||||
async createSwipe(
|
||||
sourceUserId: number,
|
||||
targetUserId: number,
|
||||
liked: boolean,
|
||||
) {
|
||||
const users = await this.usersService.getByIds([
|
||||
sourceUserId,
|
||||
targetUserId,
|
||||
]);
|
||||
if (users.length !== 2) {
|
||||
throw new UserIdNotExistsException();
|
||||
}
|
||||
|
||||
const swipe = this.swipesRepository.create({
|
||||
sourceUser: { id: sourceUserId },
|
||||
targetUser: { id: targetUserId },
|
||||
liked,
|
||||
});
|
||||
await this.swipesRepository.save(swipe);
|
||||
|
||||
if (liked) {
|
||||
const answeringLikeSwipe = await this.swipesRepository.findBy({
|
||||
sourceUser: { id: targetUserId },
|
||||
targetUser: { id: sourceUserId },
|
||||
});
|
||||
if (answeringLikeSwipe) {
|
||||
await this.pairsService.createPair([sourceUserId, targetUserId]); // TODO use transaction?
|
||||
}
|
||||
}
|
||||
|
||||
return swipe;
|
||||
}
|
||||
|
||||
async getSwipesByUser(sourceUserId: number) {
|
||||
const user = await this.usersService.getById(sourceUserId);
|
||||
if (!user) {
|
||||
throw new UserIdNotExistsException();
|
||||
}
|
||||
return this.swipesRepository.findBy({ sourceUser: { id: sourceUserId } });
|
||||
}
|
||||
}
|
|
@ -1,11 +1,7 @@
|
|||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Swipe } from 'src/swipes/entities/swipe.entity';
|
||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
export enum UserRole {
|
||||
ADMIN = 'admin',
|
||||
STANDARD = 'standard',
|
||||
}
|
||||
|
||||
@Entity('Users')
|
||||
@Entity('users')
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
@ -19,10 +15,9 @@ export class User {
|
|||
@Column({ unique: true })
|
||||
email: string;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: UserRole,
|
||||
default: UserRole.STANDARD,
|
||||
})
|
||||
role: UserRole;
|
||||
@OneToMany(() => Swipe, (swipe) => swipe.sourceUser)
|
||||
swipes: Swipe[];
|
||||
|
||||
@OneToMany(() => Swipe, (swipe) => swipe.targetUser)
|
||||
swiped: Swipe[];
|
||||
}
|
||||
|
|
|
@ -2,12 +2,16 @@ import { Injectable } from '@nestjs/common';
|
|||
import { CreateUserDto } from './dto/create-user.dto';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { User } from './entities/user.entity';
|
||||
import { Repository } from 'typeorm';
|
||||
import { In, Not, Repository } from 'typeorm';
|
||||
import {
|
||||
UserEmailNotExistsException,
|
||||
UserIdNotExistsException,
|
||||
} from 'src/utils/errors';
|
||||
|
||||
interface GetOptions {
|
||||
limit: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(
|
||||
|
@ -31,9 +35,38 @@ export class UsersService {
|
|||
return user;
|
||||
}
|
||||
|
||||
async getByIds(ids: number[]) {
|
||||
return this.usersRepository.findBy({ id: In(ids) });
|
||||
}
|
||||
|
||||
async create(userData: CreateUserDto) {
|
||||
const user = this.usersRepository.create(userData);
|
||||
await this.usersRepository.save(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
async get(userId: number, options: GetOptions) {
|
||||
// TODO get user preferences
|
||||
|
||||
const user = await this.usersRepository.findBy({ id: userId });
|
||||
if (!user) {
|
||||
throw new UserIdNotExistsException();
|
||||
}
|
||||
|
||||
const users = await this.usersRepository.find({
|
||||
where: {
|
||||
swiped: {
|
||||
sourceUser: {
|
||||
id: Not(userId),
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: {
|
||||
swiped: true,
|
||||
},
|
||||
take: options.limit || 10,
|
||||
});
|
||||
|
||||
return users;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,3 +29,9 @@ export class UserIdNotExistsException extends HttpException {
|
|||
super('User with this id does not exist', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
export class UserNotBelongsToPair extends HttpException {
|
||||
constructor() {
|
||||
super('User do not belongs to this pair', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { DataSource } from 'typeorm';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
config();
|
||||
|
||||
const configService = new ConfigService();
|
||||
|
||||
export default new DataSource({
|
||||
type: 'postgres',
|
||||
host: configService.get('DB_HOST'),
|
||||
port: +configService.get('DB_PORT'),
|
||||
username: configService.get('DB_USER'),
|
||||
password: configService.get('DB_PASSWORD'),
|
||||
database: configService.get('DB_NAME'),
|
||||
entities: ['dist/**/*.entity.js'],
|
||||
migrations: ['migrations/**/*'],
|
||||
});
|
|
@ -1053,6 +1053,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
||||
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
|
||||
|
||||
"@types/multer@^1.4.7":
|
||||
version "1.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.7.tgz#89cf03547c28c7bbcc726f029e2a76a7232cc79e"
|
||||
integrity sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
|
||||
"@types/node@*":
|
||||
version "20.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.0.tgz#9d7daa855d33d4efec8aea88cd66db1c2f0ebe16"
|
||||
|
|
Loading…
Reference in New Issue