diff --git a/package-lock.json b/package-lock.json index 52e79a11..eb03a998 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vrt-backend", - "version": "0.0.8", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1301,15 +1301,15 @@ } }, "@prisma/cli": { - "version": "2.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.0.0-beta.5.tgz", - "integrity": "sha512-Kl3IqOq6sYdajnLA034SLJxFCB0IxXcrSLYR8W9XTOyt3RRP/TCUPp5yBVfO9B21Kwe9YrFujTbSD2qQaDg1QA==", + "version": "2.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.0.0-beta.6.tgz", + "integrity": "sha512-qawcjLN5c26T+IVZij5WjQocfsV6b1BtJIL3CqMQfMkDCNGo/zXsu+NB+uyZ+QRqctEuJQ36lemnY44+k6L8XA==", "dev": true }, "@prisma/client": { - "version": "2.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.0.0-beta.5.tgz", - "integrity": "sha512-Ov7rOV5h+nu+zSFG61OvOTsDjQX+QSa8AI0sYbNSsfL9MvEMtb7mMCiGsQRdHGvxbqErFuqlN83+eUCR/umb1Q==" + "version": "2.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.0.0-beta.6.tgz", + "integrity": "sha512-08AokCpMzL6SdxQVfShD7937t+sHntr6FJBpVKq5E/UFPVh0B2lUwU9YrA/MIAdRpGMg1wA+rdwRivtFIs5Law==" }, "@schematics/schematics": { "version": "0.901.0", diff --git a/package.json b/package.json index 35ad81f0..59ec80a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vrt-backend", - "version": "1.0.0", + "version": "1.1.0", "description": "", "author": "", "private": true, @@ -19,8 +19,7 @@ "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", - "docker:build_api": "npm run test && npm run test:e2e && docker build -t visualregressiontracker/api:$npm_package_version -f ./Dockerfile . && docker push visualregressiontracker/api:$npm_package_version", - "docker:build_migration": "docker build -t visualregressiontracker/migration:1.0.0 -f ./prisma/Dockerfile ./prisma" + "docker:build_api": "npm run test && npm run test:e2e && docker build -t visualregressiontracker/api:$npm_package_version -f ./Dockerfile . && docker push visualregressiontracker/api:$npm_package_version" }, "dependencies": { "@nestjs/common": "^7.0.0", @@ -30,7 +29,7 @@ "@nestjs/passport": "^7.0.0", "@nestjs/platform-express": "^7.0.0", "@nestjs/swagger": "^4.5.1", - "@prisma/client": "^2.0.0-beta.5", + "@prisma/client": "^2.0.0-beta.6", "bcryptjs": "^2.4.3", "class-transformer": "^0.2.3", "class-validator": "^0.11.1", @@ -52,7 +51,7 @@ "@nestjs/cli": "^7.0.0", "@nestjs/schematics": "^7.0.0", "@nestjs/testing": "^7.0.0", - "@prisma/cli": "^2.0.0-beta.5", + "@prisma/cli": "^2.0.0-beta.6", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.3", "@types/jest": "25.1.4", diff --git a/prisma/.dockerignore b/prisma/.dockerignore new file mode 100644 index 00000000..30bc1627 --- /dev/null +++ b/prisma/.dockerignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/prisma/.gitignore b/prisma/.gitignore new file mode 100644 index 00000000..5f93d95f --- /dev/null +++ b/prisma/.gitignore @@ -0,0 +1,36 @@ +# compiled output +/dist +/node_modules + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +/imageUploads \ No newline at end of file diff --git a/prisma/Dockerfile b/prisma/Dockerfile index a1d3ee35..bdf8bc62 100644 --- a/prisma/Dockerfile +++ b/prisma/Dockerfile @@ -1,10 +1,13 @@ FROM node:12-alpine -# Install @prisma/cli for the migration -RUN npm install -g @prisma/cli --unsafe-perm +WORKDIR /app -# Copy schema and migration folder -COPY schema.prisma /schema.prisma -COPY migrations/ /migrations/ +COPY . . -CMD [ "prisma", "migrate", "up", "-c", "--experimental"] \ No newline at end of file +RUN npm ci + +RUN chmod +x /app/entrypoint.sh + +ENTRYPOINT ["/app/entrypoint.sh"] + +CMD ["sh"] \ No newline at end of file diff --git a/prisma/entrypoint.sh b/prisma/entrypoint.sh new file mode 100644 index 00000000..449536b4 --- /dev/null +++ b/prisma/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -e + +echo Start applying migrations... + +# apply migration +npx prisma migrate up -c --experimental + +echo Seeding data... + +# seed data +npx ts-node seed.ts \ No newline at end of file diff --git a/prisma/package-lock.json b/prisma/package-lock.json new file mode 100644 index 00000000..439a5b88 --- /dev/null +++ b/prisma/package-lock.json @@ -0,0 +1,132 @@ +{ + "name": "vrt-migration", + "version": "1.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@prisma/cli": { + "version": "2.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.0.0-beta.6.tgz", + "integrity": "sha512-qawcjLN5c26T+IVZij5WjQocfsV6b1BtJIL3CqMQfMkDCNGo/zXsu+NB+uyZ+QRqctEuJQ36lemnY44+k6L8XA==", + "dev": true + }, + "@prisma/client": { + "version": "2.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.0.0-beta.6.tgz", + "integrity": "sha512-08AokCpMzL6SdxQVfShD7937t+sHntr6FJBpVKq5E/UFPVh0B2lUwU9YrA/MIAdRpGMg1wA+rdwRivtFIs5Law==" + }, + "@types/bcryptjs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz", + "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==", + "dev": true + }, + "@types/uuid-apikey": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@types/uuid-apikey/-/uuid-apikey-1.4.0.tgz", + "integrity": "sha512-fMUfWcgNH/PFVjkoMJXEFK52vwsifNqDwJ9CmfGTHkKSSbFpsgqa4IKbm/Cgi+dVtOX+6mhIgkEOqsDO1VMnyQ==", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "encode32": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/encode32/-/encode32-1.1.0.tgz", + "integrity": "sha1-DFS0X7MUrVUC48Iwy5Ws3F5c0d0=" + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "ts-node": { + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.1.tgz", + "integrity": "sha512-bdNz1L4ekHiJul6SHtZWs1ujEKERJnHs4HxN7rjTyyVOFf3HaJ6sLqe6aPG62XTzAB/63pKRh5jTSWL0D7bsvw==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz", + "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "uuid-apikey": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/uuid-apikey/-/uuid-apikey-1.4.6.tgz", + "integrity": "sha512-ulcRze13Ic9nKGWsUgGgc7XJFMKM8nR7Oxc/tEUQeFbGyJzk4M2uO/BhVG1zSgERN/n1eTS15GB6hi/rSC+YSA==", + "requires": { + "colors": "^1.4.0", + "commander": "^3.0.2", + "encode32": "^1.1.0", + "uuid": "^3.3.3" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/prisma/package.json b/prisma/package.json new file mode 100644 index 00000000..20788fbb --- /dev/null +++ b/prisma/package.json @@ -0,0 +1,23 @@ +{ + "name": "vrt-migration", + "version": "1.1.0", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "docker:build_migration": "docker build -t visualregressiontracker/migration:$npm_package_version -f ./Dockerfile ." + }, + "dependencies": { + "@prisma/client": "^2.0.0-beta.6", + "bcryptjs": "^2.4.3", + "uuid-apikey": "^1.4.6" + }, + "devDependencies": { + "ts-node": "^8.8.2", + "@prisma/cli": "^2.0.0-beta.6", + "@types/bcryptjs": "^2.4.2", + "@types/uuid-apikey": "^1.4.0", + "typescript": "^3.7.4" + } +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 00000000..d0456624 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,67 @@ +import { PrismaClient } from '@prisma/client'; +import uuidAPIKey from 'uuid-apikey'; +import { genSalt, hash } from 'bcryptjs'; + +const prisma = new PrismaClient({ + log: ['query'], +}); + +async function seed() { + await prisma.connect(); + console.log('Seeding default data...'); + await Promise.all([createDefaultUser(), createDefaultProject()]); + await prisma.disconnect(); +} + +seed() + .catch((e) => console.error('e', e)) + .finally(async () => await prisma.disconnect()); + +async function createDefaultUser() { + const userList = await prisma.user.findMany(); + console.log(userList); + if (userList.length === 0) { + const defaultEmail = 'visual-regression-tracker1@example.com'; + const defaultPassword = '123456'; + const salt = await genSalt(10); + await prisma.user + .create({ + data: { + email: defaultEmail, + firstName: 'fname', + lastName: 'lname', + apiKey: uuidAPIKey.create({ noDashes: true }).apiKey, + password: await hash(defaultPassword, salt), + }, + }) + .then((user) => { + console.log('###########################'); + console.log('## CREATING DEFAULT USER ##'); + console.log('###########################'); + console.log(''); + console.log(`The user with the email "${defaultEmail}" and password "${defaultPassword}" was created`); + console.log(`The Api key is: ${user.apiKey}`); + }); + } +} + +async function createDefaultProject() { + const projectList = await prisma.project.findMany(); + console.log(projectList); + if (projectList.length === 0) { + await prisma.project + .create({ + data: { + name: 'Default project', + }, + }) + .then((project) => { + console.log('##############################'); + console.log('## CREATING DEFAULT PROJECT ##'); + console.log('##############################'); + console.log(''); + console.log(`Project name ${project.name}`); + console.log(`Project key: ${project.id}`); + }); + } +} diff --git a/prisma/tsconfig.json b/prisma/tsconfig.json new file mode 100644 index 00000000..9453e7b6 --- /dev/null +++ b/prisma/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es2018", + "sourceMap": true, + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "esModuleInterop": true + }, + "exclude": ["node_modules"] + } + \ No newline at end of file diff --git a/src/projects/projects.service.ts b/src/projects/projects.service.ts index 575d746a..23d2f99e 100644 --- a/src/projects/projects.service.ts +++ b/src/projects/projects.service.ts @@ -10,24 +10,8 @@ export class ProjectsService { constructor( private prismaService: PrismaService, private buildsService: BuildsService, - private testVariationsService: TestVariationsService, - ) { - // create default project if there are none in DB - this.findAll().then(projects => { - if (projects.length === 0) { - this.create({ - name: 'Default project' - }).then(project => { - console.log('##############################'); - console.log('## CREATING DEFAULT PROJECT ##'); - console.log('##############################'); - console.log(''); - console.log(`Project name ${project.name}`); - console.log(`Project key: ${project.id}`); - }) - } - }) - } + private testVariationsService: TestVariationsService + ) {} async findAll(): Promise { return this.prismaService.project.findMany(); @@ -51,10 +35,9 @@ export class ProjectsService { }); try { - await Promise.all(project.builds.map(build => this.buildsService.remove(build.id))); + await Promise.all(project.builds.map((build) => this.buildsService.remove(build.id))); await Promise.all( - project.testVariations.map(testVariation => this.testVariationsService.remove(testVariation.id) - ), + project.testVariations.map((testVariation) => this.testVariationsService.remove(testVariation.id)) ); } catch (err) { console.log(err); diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 4eb31726..232c117a 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -10,31 +10,7 @@ import { UserLoginRequestDto } from './dto/user-login-request.dto'; @Injectable() export class UsersService { - constructor(private prismaService: PrismaService, private authService: AuthService) { - // create default user if there are none in DB - this.userList().then(userList => { - if (userList.length === 0) { - const defaultEmail = 'visual-regression-tracker@example.com'; - const defaultPassword = '123456'; - - this.create({ - email: defaultEmail, - password: defaultPassword, - firstName: 'fname', - lastName: 'lname' - }).then( - user => { - console.log('###########################'); - console.log('## CREATING DEFAULT USER ##'); - console.log('###########################'); - console.log(''); - console.log(`The user with the email "${defaultEmail}" and password "${defaultPassword}" was created`); - console.log(`The Api key is: ${user.apiKey}`); - } - ); - } - }); - } + constructor(private prismaService: PrismaService, private authService: AuthService) {} userList(): Promise { return this.prismaService.user.findMany(); @@ -57,10 +33,7 @@ export class UsersService { return new UserLoginResponseDto(userData, null); } catch (err) { if (err.original.constraint === 'user_email_key') { - throw new HttpException( - `User with email '${err.errors[0].value}' already exists`, - HttpStatus.CONFLICT, - ); + throw new HttpException(`User with email '${err.errors[0].value}' already exists`, HttpStatus.CONFLICT); } throw new HttpException(err, HttpStatus.INTERNAL_SERVER_ERROR); @@ -68,12 +41,12 @@ export class UsersService { } async findOne(id: string): Promise { - return this.prismaService.user.findOne({ where: { id } }) + return this.prismaService.user.findOne({ where: { id } }); } async get(id: string): Promise { - const user = await this.findOne(id) - return new UserDto(user) + const user = await this.findOne(id); + return new UserDto(user); } async update(id: string, userDto: UpdateUserDto): Promise { @@ -83,20 +56,20 @@ export class UsersService { email: userDto.email, firstName: userDto.firstName, lastName: userDto.lastName, - } - }) + }, + }); const token = this.authService.signToken(user); return new UserLoginResponseDto(user, token); } async generateNewApiKey(user: User): Promise { - const newApiKey = this.authService.generateApiKey() + const newApiKey = this.authService.generateApiKey(); await this.prismaService.user.update({ where: { id: user.id }, data: { - apiKey: newApiKey - } - }) + apiKey: newApiKey, + }, + }); return newApiKey; } @@ -104,30 +77,24 @@ export class UsersService { await this.prismaService.user.update({ where: { id: user.id }, data: { - password: await this.authService.encryptPassword(newPassword) - } - }) + password: await this.authService.encryptPassword(newPassword), + }, + }); return true; } async login(userLoginRequestDto: UserLoginRequestDto) { const user = await this.prismaService.user.findOne({ - where: { email: userLoginRequestDto.email } - }) + where: { email: userLoginRequestDto.email }, + }); if (!user) { - throw new HttpException( - 'Invalid email or password.', - HttpStatus.BAD_REQUEST, - ); + throw new HttpException('Invalid email or password.', HttpStatus.BAD_REQUEST); } const isMatch = await this.authService.compare(userLoginRequestDto.password, user.password); if (!isMatch) { - throw new HttpException( - 'Invalid email or password.', - HttpStatus.BAD_REQUEST, - ); + throw new HttpException('Invalid email or password.', HttpStatus.BAD_REQUEST); } const token = this.authService.signToken(user);