The UUID library now uses overrides instead of the inherited Buffer methods for better deserialization. TODO: add conversions across layers from database to controller. There is initial effort in implementing Auth0.master
@@ -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 | |||
@@ -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. |
@@ -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<Props> = ({ | |||
<FormContents> | |||
{ | |||
typeof (defaultValues.id as unknown) === 'string' | |||
defaultValues.id | |||
&& ( | |||
<input | |||
type="hidden" | |||
name="id" | |||
defaultValue={defaultValues.id} | |||
defaultValue={Uuid.parse(defaultValues.id.toString()).toString()} | |||
/> | |||
) | |||
} | |||
{ | |||
defaultValues.composerUserId | |||
&& ( | |||
<input | |||
type="hidden" | |||
name="composerId" | |||
defaultValue={Uuid.parse(defaultValues.composerUserId.toString()).toString()} | |||
/> | |||
) | |||
} | |||
<input | |||
type="hidden" | |||
name="composerId" | |||
defaultValue={defaultValues.composerId} | |||
/> | |||
<Primary> | |||
<TextInput | |||
label={labels['name'] || 'Name'} | |||
@@ -218,7 +224,7 @@ const CreateRingtoneForm: FC<Props> = ({ | |||
<ActionButton | |||
onClick={typeof togglePlayback === 'function' ? togglePlayback({ formRef, setPlayTimestamp, setPlaying, }) : undefined} | |||
> | |||
{playing ? '⏹' : '▶️'} | |||
{playing ? '■' : '▶'} | |||
</ActionButton> | |||
</OtherTools> | |||
</Toolbar> | |||
@@ -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<Props> = ({ | |||
<Brand /> | |||
} | |||
userLink={ | |||
<div> | |||
User | |||
</div> | |||
<Link | |||
href={{ | |||
query: { | |||
popup: 'user', | |||
}, | |||
}} | |||
> | |||
<Avatar | |||
src={composer.picture} | |||
/> | |||
</Link> | |||
} | |||
topBarComponent={TopBarComponent} | |||
sidebarMenuComponent={SidebarMenuComponent} | |||
@@ -125,7 +141,7 @@ const CreateRingtoneTemplate: FC<Props> = ({ | |||
onSubmit={onSubmit} | |||
defaultValues={{ | |||
...currentRingtone, | |||
composerId: composer.id, | |||
composerUserId: composer.id, | |||
}} | |||
action="/api/a/create/ringtone" | |||
labels={{ | |||
@@ -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) | |||
} | |||
@@ -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<models.Ringtone>): FetchClientParams => ({ | |||
method: Method.POST, | |||
url: ['', 'api', 'ringtones'].join('/'), | |||
body: JSON.stringify(body), | |||
headers: { | |||
'Content-Type': 'application/json', | |||
'Authorization': `Bearer ${token}` | |||
}, | |||
}) | |||
@@ -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<Props> = ({ | |||
const [hydrated, setHydrated] = useState(false) | |||
const [ringtoneClient, setRingtoneClient] = useState<RingtoneClient>(null) | |||
const [composerClient, setComposerClient] = useState<ComposerClient>(null) | |||
const theUser = useUser() | |||
useEffect(() => { | |||
setHydrated(true) | |||
@@ -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<Props> = ({ | |||
export const getServerSideProps: GetServerSideProps = async () => { | |||
const user = { | |||
id: '00000000-0000-0000-000000000000', | |||
id: '00000000-0000-0000-0000-000000000000', | |||
name: 'TheoryOfNekomata', | |||
bio: '', | |||
} | |||
@@ -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 { | |||
@@ -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<string, unknown> | |||
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) | |||
} | |||
} |
@@ -1,7 +1,5 @@ | |||
PORT= | |||
DATABASE_URL= | |||
DATABASE_USERNAME= | |||
DATABASE_PASSWORD= | |||
@@ -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" | |||
}, | |||
@@ -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") | |||
} | |||
@@ -16,6 +16,8 @@ const app: FastifyPluginAsync<AppOptions> = 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<AppOptions> = async ( | |||
dir: join(__dirname, 'routes'), | |||
options: opts, | |||
}); | |||
}; | |||
export default app; | |||
export {app}; |
@@ -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<Credentials>, | |||
}> | |||
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() | |||
} | |||
} | |||
} |
@@ -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 }) | |||
} |
@@ -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<Credentials> | |||
} | |||
@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<Credentials> { | |||
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 | |||
} | |||
} |
@@ -0,0 +1,6 @@ | |||
import {DependencyContainer} from 'tsyringe'; | |||
import {CredentialsServiceImpl} from './service'; | |||
export default (container: DependencyContainer) => { | |||
container.register('CredentialsService', { useClass: CredentialsServiceImpl }) | |||
} |
@@ -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<Credentials> | |||
} | |||
@singleton() | |||
export class CredentialsServiceImpl { | |||
async request(): Promise<Credentials> { | |||
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, | |||
} | |||
} | |||
} |
@@ -0,0 +1,6 @@ | |||
export default interface Credentials { | |||
[k: string]: string | number | unknown, | |||
accessToken: string, | |||
expiresIn: number, | |||
tokenType: string, | |||
} |
@@ -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() | |||
@@ -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<models.Ringtone> | |||
browseByComposer(composerUserId: Uuid, skip?: number, take?: number): Promise<models.Ringtone[]> | |||
browse(skip?: number, take?: number): Promise<models.Ringtone[]> | |||
search(q?: string): Promise<models.Ringtone> | |||
search(q?: string): Promise<models.Ringtone[]> | |||
create(data: Partial<models.Ringtone>): Promise<models.Ringtone> | |||
update(data: Partial<models.Ringtone>): Promise<models.Ringtone> | |||
softDelete(id: Uuid): Promise<models.Ringtone> | |||
@@ -14,6 +15,15 @@ export default interface RingtoneService { | |||
hardDelete(id: Uuid): Promise<void> | |||
} | |||
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<models.Ringtone[]> { | |||
const rawData = await this.prismaClient.ringtone.findMany({ | |||
where: { | |||
composer: { | |||
id: { | |||
equals: composerUserId, | |||
} | |||
} | |||
}, | |||
skip, | |||
take, | |||
}) | |||
return rawData.map(serializeRingtone) | |||
} | |||
async get(id: Uuid): Promise<models.Ringtone> { | |||
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<models.Ringtone[]> { | |||
return this.prismaClient.ringtone.findMany({ | |||
const rawData = await this.prismaClient.ringtone.findMany({ | |||
skip, | |||
take, | |||
}) | |||
return rawData.map(serializeRingtone) | |||
} | |||
async search(q: string = ''): Promise<models.Ringtone[]> { | |||
@@ -54,31 +82,33 @@ export class RingtoneServiceImpl { | |||
async create(data: Partial<models.Ringtone>): Promise<models.Ringtone> { | |||
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<models.Ringtone>): Promise<models.Ringtone> { | |||
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<models.Ringtone> { | |||
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<models.Ringtone> { | |||
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<void> { | |||
@@ -6,7 +6,8 @@ import PasswordService from '../password/service' | |||
export default interface UserService { | |||
get(id: Uuid): Promise<models.UserProfile> | |||
getByUsername(username: string): Promise<models.UserProfile> | |||
getByUsername(username: string): Promise<models.User> | |||
getProfileByUsername(username: string): Promise<models.UserProfile> | |||
browse(skip?: number, take?: number): Promise<models.UserProfile[]> | |||
search(q?: string): Promise<models.UserProfile[]> | |||
create(profile: Partial<models.UserProfile>, username: string, newPassword: string, confirmNewPassword: string): Promise<models.UserProfile> | |||
@@ -38,7 +39,21 @@ export class UserServiceImpl implements UserService { | |||
return user | |||
} | |||
async getByUsername(username: string): Promise<models.UserProfile> { | |||
async getByUsername(username: string): Promise<models.User> { | |||
const user = await this.prismaClient.user.findUnique({ | |||
where: { | |||
username, | |||
}, | |||
}) | |||
if (!user) { | |||
throw new Error('User not found!') | |||
} | |||
return user | |||
} | |||
async getProfileByUsername(username: string): Promise<models.UserProfile> { | |||
const user = await this.prismaClient.userProfile.findFirst({ | |||
where: { | |||
user: { | |||
@@ -0,0 +1,9 @@ | |||
import FastifyAuth0Verify, { FastifyAuth0VerifyOptions } from 'fastify-auth0-verify'; | |||
import fp from 'fastify-plugin'; | |||
export default fp<FastifyAuth0VerifyOptions>(async (fastify, opts) => { | |||
fastify.register(FastifyAuth0Verify, { | |||
domain: process.env.AUTH0_DOMAIN, | |||
secret: process.env.AUTH0_SECRET | |||
}) | |||
}); |
@@ -0,0 +1,14 @@ | |||
import {FastifyPluginAsync} from 'fastify' | |||
import {container} from 'tsyringe' | |||
import AuthController from '../../../modules/auth/controller' | |||
const auth: FastifyPluginAsync = async (fastify): Promise<void> => { | |||
const ringtoneController = container.resolve<AuthController>('AuthController') | |||
fastify.route({ | |||
url: '/log-in', | |||
method: 'POST', | |||
handler: ringtoneController.logIn, | |||
}) | |||
} | |||
export default auth |
@@ -4,13 +4,52 @@ import RingtoneController from '../../../modules/ringtone/controller' | |||
const ringtones: FastifyPluginAsync = async (fastify): Promise<void> => { | |||
const ringtoneController = container.resolve<RingtoneController>('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 |
@@ -3,7 +3,8 @@ | |||
"compilerOptions": { | |||
"outDir": "dist", | |||
"experimentalDecorators": true, | |||
"emitDecoratorMetadata": true | |||
"emitDecoratorMetadata": true, | |||
"strict": false | |||
}, | |||
"include": [ | |||
"src/**/*.ts" | |||
@@ -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" | |||