diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index ed98a75..3fa7910 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -15,12 +15,13 @@ - [ ] In the front-end, the client provides a ringtone ID to request a ringtone from the back-end. - [ ] In the back-end, the server sends the ringtone data to the front-end. - As a client, I want to browse ringtones. - - [ ] In the front-end, the client provides an optional skip and take arguments to request multiple ringtones from the + - [ ] In the front-end, the client provides optional skip and take arguments to request multiple ringtones from the back-end. - [X] In the back-end, the server sends the ringtones to the front-end. - As a client, I want to view the ringtones made by a composer. - - [ ] In the front-end - - [ ] In the back-end + - [ ] In the front-end, the client provides a composer's user ID and optional skip and take arguments to request + multiple ringtones from the back-end. + - [X] In the back-end, the server sends the ringtones to the front-end. ## Search @@ -30,7 +31,7 @@ front-end. - As a client, I want to search for composers. - [ ] In the front-end, the client requests provides a search keyword to request for composers from the back-end. - - [ ] In the back-end, the server retrieves the composers whose name matches the search keyword provided by the + - [X] In the back-end, the server retrieves the composers whose name matches the search keyword provided by the front-end. ## Composer diff --git a/SAMPLES.md b/SAMPLES.md new file mode 100644 index 0000000..8092976 --- /dev/null +++ b/SAMPLES.md @@ -0,0 +1,2 @@ +Name: Happy Birthday +Data: 8g4 8g4 4a4 4g4 4c5 2b4 8g4 8g4 4a4 4g4 4d5 2c5 8g4 8g4 4g5 4e5 8c5 8c5 4b4 4a4 8f5 8f5 4e5 4c5 4d5 2c5. diff --git a/packages/app-web/src/components/organisms/forms/CreateRingtone/index.tsx b/packages/app-web/src/components/organisms/forms/CreateRingtone/index.tsx index 386a5f2..666d2f1 100644 --- a/packages/app-web/src/components/organisms/forms/CreateRingtone/index.tsx +++ b/packages/app-web/src/components/organisms/forms/CreateRingtone/index.tsx @@ -1,5 +1,7 @@ -import {CSSProperties, FC, FormEventHandler, useEffect, useRef, useState} from 'react'; +import {FC, FormEventHandler, useEffect, useRef, useState} from 'react'; import {models} from '@tonality/library-common'; +import Uuid from '@tonality/library-uuid'; +import {ALLOWED_DURATIONS, toRawDuration} from '@tonality/library-song-utils'; import { LeftSidebarWithMenu } from '@theoryofnekomata/viewfinder' import styled from 'styled-components'; import MusicalKeyboard from '@theoryofnekomata/react-musical-keyboard'; @@ -8,7 +10,6 @@ import TextArea from '../../../molecules/forms/TextArea'; import ActionButton from '../../../molecules/forms/ActionButton'; import ToggleButton from '../../../molecules/forms/ToggleButton'; import NumericInput from '../../../molecules/forms/NumericInput'; -import {ALLOWED_DURATIONS, toRawDuration} from '@tonality/library-song-utils'; import Timer from '../../../molecules/timer/Timer'; const KeyboardContainer = styled(LeftSidebarWithMenu.ContentContainer)({ @@ -150,20 +151,25 @@ const CreateRingtoneForm: FC = ({ { - typeof (defaultValues.id as unknown) === 'string' + defaultValues.id && ( + ) + } + { + defaultValues.composerUserId + && ( + ) } - = ({ - {playing ? '⏹' : '▶️'} + {playing ? '■' : '▶'} diff --git a/packages/app-web/src/components/templates/CreateRingtone/index.tsx b/packages/app-web/src/components/templates/CreateRingtone/index.tsx index b8bd0c8..7a5d5c4 100644 --- a/packages/app-web/src/components/templates/CreateRingtone/index.tsx +++ b/packages/app-web/src/components/templates/CreateRingtone/index.tsx @@ -19,10 +19,18 @@ const Padding = styled('div')({ padding: '2rem 0', }) +const Avatar = styled('img')({ + objectFit: 'cover', + objectPosition: 'center', + width: '3rem', + height: '3rem', + borderRadius: '50%', +}) + type Props = { onSearch?: FormEventHandler, onSubmit?: FormEventHandler, - composer: models.Composer, + composer: any, currentRingtone?: models.Ringtone, composerRingtones: models.Ringtone[], @@ -55,9 +63,17 @@ const CreateRingtoneTemplate: FC = ({ } userLink={ -
- User -
+ + + } topBarComponent={TopBarComponent} sidebarMenuComponent={SidebarMenuComponent} @@ -125,7 +141,7 @@ const CreateRingtoneTemplate: FC = ({ onSubmit={onSubmit} defaultValues={{ ...currentRingtone, - composerId: composer.id, + composerUserId: composer.id, }} action="/api/a/create/ringtone" labels={{ diff --git a/packages/app-web/src/modules/ringtone/client.ts b/packages/app-web/src/modules/ringtone/client.ts index 249aba3..c88be99 100644 --- a/packages/app-web/src/modules/ringtone/client.ts +++ b/packages/app-web/src/modules/ringtone/client.ts @@ -14,7 +14,12 @@ export default class RingtoneClient { save = async (e: FormEvent & { submitter: HTMLInputElement | HTMLButtonElement }) => { e.preventDefault() - const values = getFormValues(e.target as HTMLFormElement, { submitter: e.submitter }) + const {tempo, name, data} = getFormValues(e.target as HTMLFormElement, { submitter: e.submitter }) + const values = { + name, + data, + tempo: Number(tempo), + } const response = await this.fetchClient(endpoints.create(values)) alert(response.statusText) } diff --git a/packages/app-web/src/modules/ringtone/endpoints.ts b/packages/app-web/src/modules/ringtone/endpoints.ts index 6c224de..cee3d37 100644 --- a/packages/app-web/src/modules/ringtone/endpoints.ts +++ b/packages/app-web/src/modules/ringtone/endpoints.ts @@ -1,12 +1,15 @@ import {models} from '@tonality/library-common'; import {FetchClientParams, Method} from '../../utils/api/fetch'; +const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ijc3bnFZZjV1b0lRQ25xbld0UndaMyJ9.eyJpc3MiOiJodHRwczovL21vZGFsLmpwLmF1dGgwLmNvbS8iLCJzdWIiOiJYOE1PT2JKdk5QdG5lRDVMbzVJYjY0a1c1dVRWR2hVdUBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tb2RhbC5qcC5hdXRoMC5jb20vYXBpL3YyLyIsImlhdCI6MTYyMzA3Mzg5NywiZXhwIjoxNjIzMTYwMjk3LCJhenAiOiJYOE1PT2JKdk5QdG5lRDVMbzVJYjY0a1c1dVRWR2hVdSIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.WiRaFG375CbyHN3XIhf6joY3CYh89h7Xwt6vReWDOS7wKEMVRtXvd-VEBwxf_ibfIAdrB5qibjF5JwtCl18Kd8m2fEDVwj8z8qUKyChPipNezCGfbfqz6Kv_ykf06KEBwypsVwdk2YhAcxdspWuilMUPizAPkno8GXbjFYOpeZobwA4Y50zeKDWRP6SPCM94dlN7zf3myu98wBqOk4KiXH-cyO_dIVF42KTnnZlVfkEmJoLJUmSUUbRNPwrx6k-eQ2uP0whvwfhZRwo5u0uVxnnQBcEK0fTQ9CDJPKxbwULFkjfN0nLfxOLcRMdPFMNtcWEFcFDr6LHGnqDCEG4lEw' + export const create = (body: Partial): FetchClientParams => ({ method: Method.POST, url: ['', 'api', 'ringtones'].join('/'), body: JSON.stringify(body), headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` }, }) diff --git a/packages/app-web/src/pages/my/create/ringtone/index.tsx b/packages/app-web/src/pages/my/create/ringtone/index.tsx index 5412abe..e8db04e 100644 --- a/packages/app-web/src/pages/my/create/ringtone/index.tsx +++ b/packages/app-web/src/pages/my/create/ringtone/index.tsx @@ -1,6 +1,7 @@ import {GetServerSideProps, NextPage} from 'next' import {useEffect, useState} from 'react'; import {models} from '@tonality/library-common' +import {useUser} from '@auth0/nextjs-auth0' import CreateRingtoneTemplate from '../../../../components/templates/CreateRingtone' import RingtoneClient from '../../../../modules/ringtone/client' import {getSession, withPageAuthRequired} from '@auth0/nextjs-auth0'; @@ -9,7 +10,7 @@ import SoundManager from '../../../../utils/sound/SoundManager'; import ComposerClient from '../../../../modules/composer/client'; type Props = { - user: models.Composer, + user: models.User, composerRingtones: models.Ringtone[], } @@ -20,6 +21,7 @@ const MyCreateRingtonePage: NextPage = ({ const [hydrated, setHydrated] = useState(false) const [ringtoneClient, setRingtoneClient] = useState(null) const [composerClient, setComposerClient] = useState(null) + const theUser = useUser() useEffect(() => { setHydrated(true) diff --git a/packages/app-web/src/pages/ringtones/index.tsx b/packages/app-web/src/pages/ringtones/index.tsx index 8150b64..652a9f0 100644 --- a/packages/app-web/src/pages/ringtones/index.tsx +++ b/packages/app-web/src/pages/ringtones/index.tsx @@ -4,7 +4,7 @@ import {models} from '@tonality/library-common' import RingtoneClient from '../../modules/ringtone/client' type Props = { - user: models.Composer, + user: models.User, composerRingtones: models.Ringtone[], } @@ -32,7 +32,7 @@ const MyCreateRingtonePage: NextPage = ({ export const getServerSideProps: GetServerSideProps = async () => { const user = { - id: '00000000-0000-0000-000000000000', + id: '00000000-0000-0000-0000-000000000000', name: 'TheoryOfNekomata', bio: '', } diff --git a/packages/library-song-utils/src/index.ts b/packages/library-song-utils/src/index.ts index 711319c..c1248d5 100644 --- a/packages/library-song-utils/src/index.ts +++ b/packages/library-song-utils/src/index.ts @@ -1,4 +1,4 @@ -import {Note, parseNote, PITCH_CLASSES} from "./note"; +import {Note, parseNote, PITCH_CLASSES} from './note'; import {toRawDuration} from './duration'; export interface Playable { diff --git a/packages/library-uuid/src/index.ts b/packages/library-uuid/src/index.ts index cc046d7..eb26a1e 100644 --- a/packages/library-uuid/src/index.ts +++ b/packages/library-uuid/src/index.ts @@ -1,18 +1,42 @@ import {parse, stringify, v4} from 'uuid'; +function toString(this: Uuid) { + return stringify(this) +} + +function toBuffer(this: Uuid) { + return Buffer.from(this) +} + +const constructUuidBase = (bytes: any) => { + const uuidBase = Buffer.from(bytes) as Uuid + uuidBase.toString = toString.bind(uuidBase); + const uuidBaseExtend = uuidBase as unknown as Record + uuidBaseExtend['toBuffer'] = toBuffer.bind(uuidBase) + return uuidBase +} + +export interface UuidBuffer extends Buffer { + toBuffer?(): Buffer, +} + export default class Uuid extends Buffer { - static new() { + static new(): UuidBuffer { const raw = v4() const bytes = parse(raw) as Uint8Array - return Uuid.from(bytes) as Uuid + return constructUuidBase(bytes) + } + + static from(bytes: any): UuidBuffer { + return constructUuidBase(bytes) } - toString() { + toString(): string { return stringify(this) } - static parse(s: string) { + static parse(s: string): UuidBuffer { const bytes = parse(s) as Uint8Array - return Uuid.from(bytes) as Uuid + return constructUuidBase(bytes) } } diff --git a/packages/service-core/.env.example b/packages/service-core/.env.example index 94a6cfa..0005bf6 100644 --- a/packages/service-core/.env.example +++ b/packages/service-core/.env.example @@ -1,7 +1,5 @@ PORT= -DATABASE_URL= - DATABASE_USERNAME= DATABASE_PASSWORD= diff --git a/packages/service-core/package.json b/packages/service-core/package.json index 0319f24..f8ad523 100644 --- a/packages/service-core/package.json +++ b/packages/service-core/package.json @@ -17,10 +17,11 @@ "license": "MIT", "dependencies": { "@abraham/reflection": "^0.8.0", - "@prisma/client": "^2.24.1", + "@prisma/client": "2.21.2", "bcrypt": "^5.0.1", "dotenv": "^9.0.2", "fastify": "^3.0.0", + "fastify-auth0-verify": "^0.5.2", "fastify-autoload": "^3.3.1", "fastify-cli": "^2.11.0", "fastify-cors": "^6.0.1", @@ -28,6 +29,7 @@ "fastify-sensible": "^3.1.0", "knex": "^0.95.5", "tsyringe": "^4.5.0", + "unfetch": "^4.2.0", "uuid": "^8.3.2" }, "devDependencies": { @@ -39,7 +41,7 @@ "cross-env": "^7.0.3", "fastify-tsconfig": "^1.0.1", "jest": "^26.6.3", - "prisma": "^2.24.1", + "prisma": "2.21.2", "ts-jest": "^26.5.6", "typescript": "^4.1.3" }, diff --git a/packages/service-core/prisma/schema.prisma b/packages/service-core/prisma/schema.prisma index e426132..f78bda8 100644 --- a/packages/service-core/prisma/schema.prisma +++ b/packages/service-core/prisma/schema.prisma @@ -16,7 +16,7 @@ model Ringtone { createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @default(now()) @map("updated_at") deletedAt DateTime? @map("deleted_at") - user User @relation(fields: [composerUserId], references: [id]) + composer User @relation(fields: [composerUserId], references: [id]) @@map("ringtone") } diff --git a/packages/service-core/src/app.ts b/packages/service-core/src/app.ts index 35d1a7e..cdbda61 100644 --- a/packages/service-core/src/app.ts +++ b/packages/service-core/src/app.ts @@ -16,6 +16,8 @@ const app: FastifyPluginAsync = async ( // Place here your custom code! const modules = await Promise.all([ import('./global'), + import('./modules/credentials'), + import('./modules/auth'), import('./modules/password'), import('./modules/user'), import('./modules/ringtone'), @@ -39,8 +41,6 @@ const app: FastifyPluginAsync = async ( dir: join(__dirname, 'routes'), options: opts, }); - }; export default app; -export {app}; diff --git a/packages/service-core/src/modules/auth/controller.ts b/packages/service-core/src/modules/auth/controller.ts new file mode 100644 index 0000000..04c862a --- /dev/null +++ b/packages/service-core/src/modules/auth/controller.ts @@ -0,0 +1,30 @@ +import {inject, singleton} from 'tsyringe'; +import AuthService from './service'; +import {Controller, ControllerResponse} from '../../utils/helpers'; +import Credentials from '../credentials/type'; + +type AuthController = Controller<{ + logIn: ControllerResponse, +}> + +export default AuthController + +@singleton() +export class AuthControllerImpl implements AuthController { + constructor( + @inject('AuthService') + private readonly authService: AuthService + ) {} + + logIn = async (request: any, reply: any) => { + try { + const data = await this.authService.logIn(request.body['username'], request.body['password']) + return { + data + } + } catch (err) { + reply.raw.statusMessage = 'Invalid Credentials' + reply.unauthorized() + } + } +} diff --git a/packages/service-core/src/modules/auth/index.ts b/packages/service-core/src/modules/auth/index.ts new file mode 100644 index 0000000..a1f95bd --- /dev/null +++ b/packages/service-core/src/modules/auth/index.ts @@ -0,0 +1,8 @@ +import {DependencyContainer} from 'tsyringe'; +import {AuthServiceImpl} from './service'; +import {AuthControllerImpl} from './controller'; + +export default (container: DependencyContainer) => { + container.register('AuthController', { useClass: AuthControllerImpl }) + container.register('AuthService', { useClass: AuthServiceImpl }) +} diff --git a/packages/service-core/src/modules/auth/service.ts b/packages/service-core/src/modules/auth/service.ts new file mode 100644 index 0000000..3159f57 --- /dev/null +++ b/packages/service-core/src/modules/auth/service.ts @@ -0,0 +1,37 @@ +import {inject, singleton} from 'tsyringe'; +import UserService from '../user/service'; +import PasswordService from '../password/service'; +import CredentialsService from '../credentials/service'; +import Credentials from '../credentials/type'; + +export default interface AuthService { + logIn(username: string, password: string): Promise +} + +@singleton() +export class AuthServiceImpl implements AuthService { + constructor( + @inject('UserService') + private readonly userService: UserService, + @inject('PasswordService') + private readonly passwordService: PasswordService, + @inject('CredentialsService') + private readonly credentialsService: CredentialsService, + ) {} + + async logIn(username: string, password: string): Promise { + const user = await this.userService.getByUsername(username) + if (!user) { + throw new Error('Invalid credentials.') + } + + const valid = this.passwordService.compare(password, user.password) + if (!valid) { + throw new Error('Invalid credentials.') + } + + const credentials = await this.credentialsService.request() + credentials.profile = await this.userService.getProfileByUsername(username) + return credentials + } +} diff --git a/packages/service-core/src/modules/credentials/index.ts b/packages/service-core/src/modules/credentials/index.ts new file mode 100644 index 0000000..5484556 --- /dev/null +++ b/packages/service-core/src/modules/credentials/index.ts @@ -0,0 +1,6 @@ +import {DependencyContainer} from 'tsyringe'; +import {CredentialsServiceImpl} from './service'; + +export default (container: DependencyContainer) => { + container.register('CredentialsService', { useClass: CredentialsServiceImpl }) +} diff --git a/packages/service-core/src/modules/credentials/service.ts b/packages/service-core/src/modules/credentials/service.ts new file mode 100644 index 0000000..c1ede09 --- /dev/null +++ b/packages/service-core/src/modules/credentials/service.ts @@ -0,0 +1,34 @@ +import {URL} from 'url'; +import unfetch from 'unfetch'; +import Credentials from './type'; +import {singleton} from 'tsyringe'; + +export default interface CredentialsService { + request(): Promise +} + +@singleton() +export class CredentialsServiceImpl { + async request(): Promise { + const tokenUrl = new URL('/oauth/token', process.env.AUTH0_DOMAIN) + const audienceUrl = new URL('/api/v2', process.env.AUTH0_DOMAIN) + const response = await unfetch(tokenUrl.toString(), { + method: 'POST', + body: JSON.stringify({ + client_id: process.env.AUTH0_CLIENT_ID, + client_secret: process.env.AUTH0_SECRET, + audience: audienceUrl.toString(), + grant_type: 'client_credentials', + }) + }) + if (!response.ok) { + throw new Error('Unable to request credentials.') + } + const { access_token: accessToken, expires_in: expiresIn, token_type: tokenType } = await response.json() + return { + accessToken, + expiresIn, + tokenType, + } + } +} diff --git a/packages/service-core/src/modules/credentials/type.ts b/packages/service-core/src/modules/credentials/type.ts new file mode 100644 index 0000000..61d7500 --- /dev/null +++ b/packages/service-core/src/modules/credentials/type.ts @@ -0,0 +1,6 @@ +export default interface Credentials { + [k: string]: string | number | unknown, + accessToken: string, + expiresIn: number, + tokenType: string, +} diff --git a/packages/service-core/src/modules/ringtone/controller.ts b/packages/service-core/src/modules/ringtone/controller.ts index f953958..b942932 100644 --- a/packages/service-core/src/modules/ringtone/controller.ts +++ b/packages/service-core/src/modules/ringtone/controller.ts @@ -18,6 +18,12 @@ type RingtoneController = Controller<{ export default RingtoneController +const serializeRingtone = d => ({ + ...d, + composerUserId: d.composerUserId.toString(), + id: d.id.toString(), +}) + @singleton() export class RingtoneControllerImpl implements RingtoneController { constructor( @@ -28,7 +34,8 @@ export class RingtoneControllerImpl implements RingtoneController { get = async (request: any, reply: any) => { try { const id = Uuid.parse(request.params['id']) - const data = await this.ringtoneService.get(id) + const rawData = await this.ringtoneService.get(id) + const data = serializeRingtone(rawData) if (typeof (data.deletedAt as Date) !== 'undefined') { reply.raw.statusMessage = 'Ringtone Deleted Previously' reply.gone() @@ -58,10 +65,12 @@ export class RingtoneControllerImpl implements RingtoneController { const skip = !isNaN(skipNumber) ? skipNumber : 0 const take = !isNaN(takeNumber) ? takeNumber : 10 + const rawData = await this.ringtoneService.browse(skip, take) + const data = rawData.map(serializeRingtone) reply.raw.statusMessage = 'Multiple Ringtones Retrieved' return { - data: await this.ringtoneService.browse(skip, take), + data, skip, take, } @@ -74,9 +83,12 @@ export class RingtoneControllerImpl implements RingtoneController { search = async (request: any, reply: any) => { try { const { 'q': query } = request.query + const rawData = await this.ringtoneService.search(query) + const data = rawData.map(serializeRingtone) + reply.raw.statusMessage = 'Search Results Retrieved' return { - data: await this.ringtoneService.search(query), + data, } } catch (err) { reply.raw.statusMessage = 'Search Error' @@ -85,12 +97,15 @@ export class RingtoneControllerImpl implements RingtoneController { } create = async (request: any, reply: any) => { + // TODO parse and validate body + try { - const { composerUserId, ...etcBody } = request.body - const data = await this.ringtoneService.create({ - ...etcBody, - composerUserId: Uuid.parse(composerUserId), + const rawData = await this.ringtoneService.create({ + ...request.body, + // TODO map auth credentials to user + composerUserId: Uuid.parse(request.body.composerUserId), }) + const data = serializeRingtone(rawData) reply.raw.statusMessage = 'Ringtone Created' reply.raw.statusCode = 201 return { @@ -106,10 +121,11 @@ export class RingtoneControllerImpl implements RingtoneController { try { // TODO validate data const id = Uuid.parse(request.params['id']) - const data = await this.ringtoneService.update({ + const rawData = await this.ringtoneService.update({ ...request.body, id, }) + const data = serializeRingtone(rawData) if (data.deletedAt) { reply.raw.statusMessage = 'Ringtone Deleted Previously' reply.gone() @@ -133,7 +149,8 @@ export class RingtoneControllerImpl implements RingtoneController { try { // TODO validate data const id = Uuid.parse(request.params['id']) - const data = await this.ringtoneService.softDelete(id) + const rawData = await this.ringtoneService.softDelete(id) + const data = serializeRingtone(rawData) if (!data) { reply.raw.statusMessage = 'Ringtone Not Found' reply.notFound() @@ -153,7 +170,8 @@ export class RingtoneControllerImpl implements RingtoneController { try { // TODO validate data const id = Uuid.parse(request.params['id']) - const data = await this.ringtoneService.undoDelete(id) + const rawData = await this.ringtoneService.undoDelete(id) + const data = serializeRingtone(rawData) if (!data) { reply.raw.statusMessage = 'Ringtone Not Found' reply.notFound() diff --git a/packages/service-core/src/modules/ringtone/service.ts b/packages/service-core/src/modules/ringtone/service.ts index bb076ba..b95bce9 100644 --- a/packages/service-core/src/modules/ringtone/service.ts +++ b/packages/service-core/src/modules/ringtone/service.ts @@ -1,12 +1,13 @@ import {inject, singleton} from 'tsyringe' import {models} from '@tonality/library-common' -import Uuid from '@tonality/library-uuid' +import Uuid, {UuidBuffer} from '@tonality/library-uuid'; import {PrismaClient} from '@prisma/client' export default interface RingtoneService { get(id: Uuid): Promise + browseByComposer(composerUserId: Uuid, skip?: number, take?: number): Promise browse(skip?: number, take?: number): Promise - search(q?: string): Promise + search(q?: string): Promise create(data: Partial): Promise update(data: Partial): Promise softDelete(id: Uuid): Promise @@ -14,6 +15,15 @@ export default interface RingtoneService { hardDelete(id: Uuid): Promise } +const serializeRingtone = d => { + return ({ + ...d, + composerUserId: Uuid.from(d.composerUserId).toString(), + id: Uuid.from(d.id).toString(), + }) +} + + @singleton() export class RingtoneServiceImpl { constructor( @@ -21,6 +31,22 @@ export class RingtoneServiceImpl { private readonly prismaClient: PrismaClient, ) {} + async browseByComposer(composerUserId: Uuid, skip: number = 0, take: number = 10): Promise { + const rawData = await this.prismaClient.ringtone.findMany({ + where: { + composer: { + id: { + equals: composerUserId, + } + } + }, + skip, + take, + }) + + return rawData.map(serializeRingtone) + } + async get(id: Uuid): Promise { const ringtone = await this.prismaClient.ringtone.findUnique({ where: { @@ -32,14 +58,16 @@ export class RingtoneServiceImpl { throw new Error('Ringtone not found!') } - return ringtone + return serializeRingtone(ringtone) } async browse(skip: number = 0, take: number = 10): Promise { - return this.prismaClient.ringtone.findMany({ + const rawData = await this.prismaClient.ringtone.findMany({ skip, take, }) + + return rawData.map(serializeRingtone) } async search(q: string = ''): Promise { @@ -54,31 +82,33 @@ export class RingtoneServiceImpl { async create(data: Partial): Promise { const { createdAt, updatedAt, deletedAt, composerUserId, name, ...safeData } = data - return this.prismaClient.ringtone.create({ + const rawData = await this.prismaClient.ringtone.create({ data: { ...safeData, - id: Uuid.new(), - composerUserId: composerUserId as Uuid, + id: Uuid.new().toBuffer(), + composerUserId: (composerUserId as UuidBuffer).toBuffer(), name: name as string, }, }) + return serializeRingtone(rawData) } async update(data: Partial): Promise { const { createdAt, updatedAt, deletedAt, ...safeData } = data - return this.prismaClient.ringtone.update({ + const rawData = await this.prismaClient.ringtone.update({ where: { - id: data.id as Uuid, + id: (data.id as UuidBuffer).toBuffer(), }, data: { ...safeData, updatedAt: new Date(), }, }) + return serializeRingtone(rawData) } async softDelete(id: Uuid): Promise { - return this.prismaClient.ringtone.update({ + const rawData = await this.prismaClient.ringtone.update({ where: { id, }, @@ -86,10 +116,11 @@ export class RingtoneServiceImpl { deletedAt: new Date(), }, }) + return serializeRingtone(rawData) } async undoDelete(id: Uuid): Promise { - return this.prismaClient.ringtone.update({ + const rawData = this.prismaClient.ringtone.update({ where: { id, }, @@ -97,6 +128,7 @@ export class RingtoneServiceImpl { deletedAt: null, }, }) + return serializeRingtone(rawData) } async hardDelete(id: Uuid): Promise { diff --git a/packages/service-core/src/modules/user/service.ts b/packages/service-core/src/modules/user/service.ts index 5ae50c7..89da95c 100644 --- a/packages/service-core/src/modules/user/service.ts +++ b/packages/service-core/src/modules/user/service.ts @@ -6,7 +6,8 @@ import PasswordService from '../password/service' export default interface UserService { get(id: Uuid): Promise - getByUsername(username: string): Promise + getByUsername(username: string): Promise + getProfileByUsername(username: string): Promise browse(skip?: number, take?: number): Promise search(q?: string): Promise create(profile: Partial, username: string, newPassword: string, confirmNewPassword: string): Promise @@ -38,7 +39,21 @@ export class UserServiceImpl implements UserService { return user } - async getByUsername(username: string): Promise { + async getByUsername(username: string): Promise { + const user = await this.prismaClient.user.findUnique({ + where: { + username, + }, + }) + + if (!user) { + throw new Error('User not found!') + } + + return user + } + + async getProfileByUsername(username: string): Promise { const user = await this.prismaClient.userProfile.findFirst({ where: { user: { diff --git a/packages/service-core/src/plugins/auth0-verify.ts b/packages/service-core/src/plugins/auth0-verify.ts new file mode 100644 index 0000000..509d645 --- /dev/null +++ b/packages/service-core/src/plugins/auth0-verify.ts @@ -0,0 +1,9 @@ +import FastifyAuth0Verify, { FastifyAuth0VerifyOptions } from 'fastify-auth0-verify'; +import fp from 'fastify-plugin'; + +export default fp(async (fastify, opts) => { + fastify.register(FastifyAuth0Verify, { + domain: process.env.AUTH0_DOMAIN, + secret: process.env.AUTH0_SECRET + }) +}); diff --git a/packages/service-core/src/routes/api/auth/index.ts b/packages/service-core/src/routes/api/auth/index.ts new file mode 100644 index 0000000..df5e111 --- /dev/null +++ b/packages/service-core/src/routes/api/auth/index.ts @@ -0,0 +1,14 @@ +import {FastifyPluginAsync} from 'fastify' +import {container} from 'tsyringe' +import AuthController from '../../../modules/auth/controller' + +const auth: FastifyPluginAsync = async (fastify): Promise => { + const ringtoneController = container.resolve('AuthController') + fastify.route({ + url: '/log-in', + method: 'POST', + handler: ringtoneController.logIn, + }) +} + +export default auth diff --git a/packages/service-core/src/routes/api/ringtones/index.ts b/packages/service-core/src/routes/api/ringtones/index.ts index 4d9cdc1..5eee8b0 100644 --- a/packages/service-core/src/routes/api/ringtones/index.ts +++ b/packages/service-core/src/routes/api/ringtones/index.ts @@ -4,13 +4,52 @@ import RingtoneController from '../../../modules/ringtone/controller' const ringtones: FastifyPluginAsync = async (fastify): Promise => { const ringtoneController = container.resolve('RingtoneController') - fastify.get('/', ringtoneController.browse) - fastify.get('/:id', ringtoneController.get) - fastify.post('/', ringtoneController.create) - fastify.patch('/:id', ringtoneController.update) - fastify.post('/:id/delete', ringtoneController.softDelete) - fastify.delete('/:id/delete', ringtoneController.undoDelete) - fastify.delete('/:id', ringtoneController.hardDelete) + fastify.route({ + url: '/:id', + method: 'GET', + handler: ringtoneController.get, + }) + + fastify.route({ + url: '/', + method: 'GET', + handler: ringtoneController.browse, + }) + + fastify.route({ + url: '/', + method: 'POST', + handler: ringtoneController.create, + preValidation: fastify.authenticate, + }) + + fastify.route({ + url: '/:id', + method: 'PATCH', + handler: ringtoneController.update, + preValidation: fastify.authenticate, + }) + + fastify.route({ + url: '/:id/delete', + method: 'POST', + handler: ringtoneController.softDelete, + preValidation: fastify.authenticate, + }) + + fastify.route({ + url: '/:id/delete', + method: 'DELETE', + handler: ringtoneController.undoDelete, + preValidation: fastify.authenticate, + }) + + fastify.route({ + url: '/:id', + method: 'DELETE', + handler: ringtoneController.hardDelete, + preValidation: fastify.authenticate, + }) } export default ringtones diff --git a/packages/service-core/tsconfig.json b/packages/service-core/tsconfig.json index 1818d86..2455d05 100644 --- a/packages/service-core/tsconfig.json +++ b/packages/service-core/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "outDir": "dist", "experimentalDecorators": true, - "emitDecoratorMetadata": true + "emitDecoratorMetadata": true, + "strict": false }, "include": [ "src/**/*.ts" diff --git a/packages/service-core/yarn.lock b/packages/service-core/yarn.lock index 4999165..a1896b3 100644 --- a/packages/service-core/yarn.lock +++ b/packages/service-core/yarn.lock @@ -515,22 +515,22 @@ semver "^7.3.4" tar "^6.1.0" -"@prisma/client@^2.24.1": - version "2.24.1" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.24.1.tgz#c4f26fb4d768dd52dd20a17e626f10e69cc0b85c" - integrity sha512-vllhf36g3oI98GF1Q5IPmnR5MYzBPeCcl/Xiz6EAi4DMOxE069o9ka5BAqYbUG2USx8JuKw09QdMnDrp3Kyn8g== +"@prisma/client@2.21.2": + version "2.21.2" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.21.2.tgz#ca8489832da1d61add429390210be4d7896e5e29" + integrity sha512-UjkOXYpxLuHyoMDsP2m0LTcxhrjQa1dEOLFe3aDrO/BLrs/2yUxyPdtwSKxizRXFzuXSGkKIK225vcjZRuMpAg== dependencies: - "@prisma/engines-version" "2.24.1-2.18095475d5ee64536e2f93995e48ad800737a9e4" + "@prisma/engines-version" "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d" -"@prisma/engines-version@2.24.1-2.18095475d5ee64536e2f93995e48ad800737a9e4": - version "2.24.1-2.18095475d5ee64536e2f93995e48ad800737a9e4" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-2.24.1-2.18095475d5ee64536e2f93995e48ad800737a9e4.tgz#2c5813ef98bcbe659b18b521f002f5c8aabbaae2" - integrity sha512-60Do+ByVfHnhJ2id5h/lXOZnDQNIf5pz3enkKWOmyr744Z2IxkBu65jRckFfMN5cPtmXDre/Ay/GKm0aoeLwrw== +"@prisma/engines-version@2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d": + version "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d.tgz#b749bae4173eb766dafc298aaa7d883c2dbe555b" + integrity sha512-9/fE1gdPWmjbMjXUJjrTMt848TsgEnSjZCcJ1wu9OAcRlAKKJBLehftqC3gSEShDijvMYgeTdGU5snMpwmv4vg== -"@prisma/engines@2.24.1-2.18095475d5ee64536e2f93995e48ad800737a9e4": - version "2.24.1-2.18095475d5ee64536e2f93995e48ad800737a9e4" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.24.1-2.18095475d5ee64536e2f93995e48ad800737a9e4.tgz#7e542d510f0c03f41b73edbb17254f5a0b272a4d" - integrity sha512-29/xO9kqeQka+wN5Ev10l5L4XQXNVXdPToJs1M29VZ2imQsNsL4rtz26m3qGM54IoGWwwfTVdvuVRxKnDl2rig== +"@prisma/engines@2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d": + version "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d.tgz#aafed60c9506bc766e49ea60b9f8ce7da2385bc6" + integrity sha512-L57tvSoom2GDWDqik4wrAUBvLTAv5MTm2OOzNMBKsv0w5cX7ONoZ8KnGQN+csmdJpQVBs93dIvIBm72OO+l/9Q== "@sinonjs/commons@^1.7.0": version "1.8.3" @@ -620,6 +620,13 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" +"@types/jsonwebtoken@^8.5.0": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#56958cb2d80f6d74352bd2e501a018e2506a8a84" + integrity sha512-rNAPdomlIUX0i0cg2+I+Q1wOUr531zHBQ+cV/28PJ39bSPKjahatZZ2LMuhiguETkCgLVzfruw/ZvNMNkKoSzw== + dependencies: + "@types/node" "*" + "@types/node@*", "@types/node@^15.0.0": version "15.0.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-15.0.3.tgz#ee09fcaac513576474c327da5818d421b98db88a" @@ -1082,6 +1089,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -1263,6 +1275,11 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +clone@2.x: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + close-with-grace@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/close-with-grace/-/close-with-grace-1.1.0.tgz#91a48cf2019b5ae6e67b0255a32abcfd9bbca233" @@ -1606,6 +1623,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + electron-to-chromium@^1.3.723: version "1.3.727" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz#857e310ca00f0b75da4e1db6ff0e073cc4a91ddf" @@ -1841,6 +1865,24 @@ fast-safe-stringify@^2.0.7: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== +fastfall@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/fastfall/-/fastfall-1.5.1.tgz#3fee03331a49d1d39b3cdf7a5e9cd66f475e7b94" + integrity sha1-P+4DMxpJ0dObPN96XpzWb0dee5Q= + dependencies: + reusify "^1.0.0" + +fastify-auth0-verify@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fastify-auth0-verify/-/fastify-auth0-verify-0.5.2.tgz#26d7e51d9f251d515f3edde97669ac21ecd0aa67" + integrity sha512-MHNolhH4BRRQEzUWHdSfa03/Aeas/NYvjaRTopF17ovta55ErWJa3e6UpxmXKjMArFP/NeyZkcZ3ZBci71Fz1Q== + dependencies: + fastify-jwt "^3.0.0" + fastify-plugin "^3.0.0" + http-errors "^1.7.3" + node-cache "^5.0.1" + node-fetch "^2.6.1" + fastify-autoload@^3.3.1: version "3.7.1" resolved "https://registry.yarnpkg.com/fastify-autoload/-/fastify-autoload-3.7.1.tgz#64dd843c5fe340d4c42d3d9353521e70c4c28b4f" @@ -1887,6 +1929,17 @@ fastify-error@^0.3.0: resolved "https://registry.yarnpkg.com/fastify-error/-/fastify-error-0.3.1.tgz#8eb993e15e3cf57f0357fc452af9290f1c1278d2" integrity sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ== +fastify-jwt@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fastify-jwt/-/fastify-jwt-3.0.0.tgz#d0cf2bfc02cf12b3826f534258f559baf9c8518f" + integrity sha512-gFo6AQRz71TEteKisNcgXNYei1QzNBzXH59J00PBoRHhK3YtADF/teQxrKTyR2GeIBwuQnVJNRtsFRgLd7adTA== + dependencies: + "@types/jsonwebtoken" "^8.5.0" + fastify-plugin "^3.0.0" + http-errors "^1.8.0" + jsonwebtoken "^8.5.1" + steed "^1.1.3" + fastify-plugin@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/fastify-plugin/-/fastify-plugin-3.0.0.tgz#cf1b8c8098e3b5a7c8c30e6aeb06903370c054ca" @@ -1936,13 +1989,29 @@ fastify@^3.0.0: semver "^7.3.2" tiny-lru "^7.0.0" -fastq@^1.6.1: +fastparallel@^2.2.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/fastparallel/-/fastparallel-2.4.0.tgz#65fbec1a5e5902494be772cf5765cbaaece08688" + integrity sha512-sacwQ7wwKlQXsa7TN24UvMBLZNLmVcPhmxccC9riFqb3N+fSczJL8eWdnZodZ/KijGVgNBBfvF/NeXER08uXnQ== + dependencies: + reusify "^1.0.4" + xtend "^4.0.2" + +fastq@^1.3.0, fastq@^1.6.1: version "1.11.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== dependencies: reusify "^1.0.4" +fastseries@^1.7.0: + version "1.7.2" + resolved "https://registry.yarnpkg.com/fastseries/-/fastseries-1.7.2.tgz#d22ce13b9433dff3388d91dbd6b8bda9b21a0f4b" + integrity sha1-0izhO5Qz3/M4jZHb1ri9qbIaD0s= + dependencies: + reusify "^1.0.0" + xtend "^4.0.0" + fb-watchman@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" @@ -2279,7 +2348,7 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-errors@^1.7.3: +http-errors@^1.7.3, http-errors@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507" integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A== @@ -3079,6 +3148,22 @@ json5@2.x, json5@^2.1.2: dependencies: minimist "^1.2.5" +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -3089,6 +3174,23 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -3186,6 +3288,41 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.toarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" @@ -3453,6 +3590,13 @@ node-addon-api@^3.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== +node-cache@^5.0.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d" + integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg== + dependencies: + clone "2.x" + node-emoji@^1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" @@ -3943,12 +4087,12 @@ pretty-ms@^5.0.0: dependencies: parse-ms "^2.1.0" -prisma@^2.24.1: - version "2.24.1" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-2.24.1.tgz#f8f4cb8baf407a71800256160277f69603bd43a3" - integrity sha512-L+ykMpttbWzpTNsy+PPynnEX/mS1s5zs49euXBrMjxXh1M6/f9MYlTNAj+iP90O9ZSaURSpNa+1jzatPghqUcQ== +prisma@2.21.2: + version "2.21.2" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-2.21.2.tgz#a73b4cbe92a884aa98b317684d6741871b5e94a5" + integrity sha512-Ux9ovDIUHsMNLGLtuo6BBKCuuBVLpZmhM2LXF+VBUQvsbmsVfp3u5CRyHGEqaZqMibYQJISy7YZYF/RgozHKkQ== dependencies: - "@prisma/engines" "2.24.1-2.18095475d5ee64536e2f93995e48ad800737a9e4" + "@prisma/engines" "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d" process-nextick-args@~2.0.0: version "2.0.1" @@ -4193,7 +4337,7 @@ ret@~0.2.0: resolved "https://registry.yarnpkg.com/ret/-/ret-0.2.2.tgz#b6861782a1f4762dce43402a71eb7a283f44573c" integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ== -reusify@^1.0.4: +reusify@^1.0.0, reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== @@ -4295,7 +4439,7 @@ semver-store@^0.3.0: resolved "https://registry.yarnpkg.com/semver-store/-/semver-store-0.3.0.tgz#ce602ff07df37080ec9f4fb40b29576547befbe9" integrity sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg== -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -4571,6 +4715,17 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= +steed@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/steed/-/steed-1.1.3.tgz#f1525dd5adb12eb21bf74749537668d625b9abc5" + integrity sha1-8VJd1a2xLrIb90dJU3Zo1iW5q8U= + dependencies: + fastfall "^1.5.0" + fastparallel "^2.2.0" + fastq "^1.3.0" + fastseries "^1.7.0" + reusify "^1.0.0" + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -4948,6 +5103,11 @@ typescript@^4.1.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -5188,6 +5348,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xtend@^4.0.0, xtend@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"