Skip to content

Instantly share code, notes, and snippets.

@Roms1383
Created March 30, 2020 17:53
Show Gist options
  • Save Roms1383/c2b190677796d389b4d33350c06c80ce to your computer and use it in GitHub Desktop.
Save Roms1383/c2b190677796d389b4d33350c06c80ce to your computer and use it in GitHub Desktop.
Medium - Combining decorators for easy data manipulation
import { Note } from '@kroms/notes/notes.entity'
import { NotesModule } from '@kroms/notes/notes.module'
import { User } from '@kroms/notes/users.entity'
import { INestApplication, Injectable, Module } from '@nestjs/common'
import { NestFactory } from '@nestjs/core'
import {
InjectRepository,
TypeOrmModule,
TypeOrmModuleOptions,
} from '@nestjs/typeorm'
import * as chalk from 'chalk'
import Axios from 'axios'
import { Repository } from 'typeorm'
import * as generate from './seed'
require('dotenv').config()
const HOST = process.env.SERVER_HOST || 'localhost'
const PORT = +process.env.SERVER_PORT || 3000
const options: TypeOrmModuleOptions = {
type: (process.env.TYPEORM_CONNECTION as 'mariadb') || 'mysql' || 'mariadb',
host: process.env.TYPEORM_HOST || 'localhost',
port: +process.env.TYPEORM_PORT || 3306,
username: process.env.TYPEORM_USERNAME || 'root',
password: process.env.TYPEORM_PASSWORD || 'root',
database: process.env.TYPEORM_DATABASE || 'test_nestjs_decorators',
entities: [User, Note],
synchronize: true,
dropSchema: true,
}
const COUNT_USERS = 3
const COUNT_NOTES = 5
let users
let notes
@Injectable()
class SeederService {
constructor(
@InjectRepository(User) private readonly users_repo: Repository<User>,
@InjectRepository(Note) private readonly notes_repo: Repository<Note>,
) {}
public async seed(): Promise<void> {
const generatedUsers = generate.users(COUNT_USERS).slice()
const { generatedMaps: usersIds } = await this.users_repo.manager.connection
.createQueryBuilder()
.insert()
.into(User)
.values(generatedUsers)
.execute()
users = usersIds
.map(({ id }, index) => ({ id, ...generatedUsers[index] }))
.slice()
const generatedNotes = generate.notes(COUNT_NOTES, users).slice()
const { generatedMaps: notesIds } = await this.notes_repo.manager.connection
.createQueryBuilder()
.insert()
.into(Note)
.values(generatedNotes)
.execute()
notes = notesIds
.map(({ id }, index) => ({ id, ...generatedNotes[index] }))
.slice()
// console.info(chalk.green('seeded', '\n', '-----'));
// console.info(users);
// console.info(notes);
}
}
@Module({
imports: [TypeOrmModule.forRoot(options), NotesModule],
providers: [SeederService],
})
class AppModule {}
const bootstrap = async (): Promise<INestApplication> => {
const app = await NestFactory.create(AppModule)
const seeder = app.get(SeederService)
await seeder.seed()
await app.listen(PORT)
return app
}
const teardown = async (app: INestApplication): Promise<void> => {
await app.close()
app = undefined
}
describe('tests', () => {
let app
beforeAll(async () => {
app = await bootstrap()
})
afterAll(async () => {
await teardown(app)
})
describe('notes', () => {
it('can fetch all the notes', async () => {
const { data } = await Axios.get(`http://${HOST}:${PORT}/notes`)
// console.info(chalk.magenta('received', '\n', '-----'));
// console.info(data);
expect(data).toBeDefined()
expect(data).toHaveLength(COUNT_NOTES)
expect(data).toEqual(
expect.arrayContaining(notes.map(expect.objectContaining)),
)
})
})
})
export const TIME = 'YYYY-MM-DDTHH:mm:ss.SSSZ'
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common'
import { classToPlain } from 'class-transformer'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
@Injectable()
export class IOInterceptor implements NestInterceptor {
intercept(_context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(map(output => classToPlain(output)))
}
}
import { Controller, Get, UseInterceptors } from '@nestjs/common'
import * as chalk from 'chalk'
import { IOInterceptor } from './io.interceptor'
import { Note } from './notes.entity'
import { NotesService } from './notes.service'
@Controller('notes')
@UseInterceptors(IOInterceptor)
export class NotesController {
constructor(private readonly service: NotesService) {}
@Get()
async fetch(): Promise<Note[]> {
try {
const notes = await this.service.fetch()
// console.info(chalk.blue('sending back', '\n', '-----'));
// console.info(notes);
return notes
} catch (e) {
console.error(e)
}
}
}
import { Exclude, Expose, Transform } from 'class-transformer'
import { IsDateString, IsUUID } from 'class-validator'
import { Moment, utc } from 'moment'
import {
Column,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm'
import { TIME } from './constants'
import { User } from './users.entity'
@Entity()
export class Note {
@PrimaryGeneratedColumn('uuid')
@IsUUID()
@Expose()
id: string
@Column()
@Expose()
description: string
// validation
@IsDateString()
// from db to class
@Column({
type: 'varchar',
transformer: {
from: value => utc(value),
to: value => utc(value).format(TIME),
},
})
// from plain to class
@Transform(value => utc(value), { toClassOnly: true })
// from class to plain
@Transform(value => utc(value).format(TIME), { toPlainOnly: true })
// expose when transformed into plain
@Expose()
on: Moment
@ManyToOne(
type => User,
user => user.notes,
)
@JoinColumn({ name: 'owner' })
@Exclude()
user: User
@Column({ nullable: false })
@Expose()
owner: string
}
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { NotesController } from './notes.controller'
import { Note } from './notes.entity'
import { NotesService } from './notes.service'
import { User } from './users.entity'
@Module({
imports: [TypeOrmModule.forFeature([User, Note])],
providers: [NotesService],
controllers: [NotesController],
exports: [TypeOrmModule],
})
export class NotesModule {}
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { Note } from './notes.entity'
@Injectable()
export class NotesService {
constructor(
@InjectRepository(Note) private readonly repository: Repository<Note>,
) {}
async fetch(): Promise<Note[]> {
return this.repository.find({ relations: ['user'] })
}
}
import { Note } from '@kroms/notes/notes.entity'
import { User } from '@kroms/notes/users.entity'
import * as Chance from 'chance'
import * as crypto from 'crypto'
import * as moment from 'moment'
import { TIME } from '@kroms/notes/constants'
const chance = Chance()
export const users = (howMany: number): User[] => {
const users: User[] = []
let user: User
for (let i = 0; i < howMany; i++) {
user = new User()
user.firstname = chance.first()
user.lastname = chance.last()
user.email = chance.email()
user.password = crypto
.createHmac('sha256', 'secret-key')
.update('password')
.digest('hex')
users.push(user)
}
return users
}
export const notes = (howMany: number, owners?: User[]): Note[] => {
const notes: Note[] = []
let note: Note
for (let i = 0; i < howMany; i++) {
note = new Note()
note.description = chance.sentence({ words: 5 })
note.on = (moment
.utc(chance.date({ year: 1983 }))
.format(TIME) as unknown) as moment.Moment
note.owner = owners[Math.floor(Math.random() * (owners.length - 1))].id
notes.push(note)
}
return notes
}
import { Exclude, Expose, Type } from 'class-transformer'
import { IsEmail, IsHash, IsUUID, MinLength } from 'class-validator'
import {
Column,
Entity,
PrimaryGeneratedColumn,
OneToMany,
JoinColumn,
} from 'typeorm'
import { Note } from './notes.entity'
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
@IsUUID()
@Expose()
id: string
@Column({ nullable: true })
@MinLength(2)
@Expose()
firstname?: string
@Column({ nullable: true })
@Expose()
lastname?: string
@Column({ length: 255 })
@IsEmail()
@Expose()
email!: string
@Column()
@IsHash('sha256')
@Exclude()
password: string
@Type(() => Note)
@OneToMany(
type => Note,
note => note.owner,
)
@JoinColumn({ name: 'notes' })
@Exclude()
notes?: Note[]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment