Compare commits

..

19 Commits

Author SHA1 Message Date
Michael Schramm
c2c421baa6 fix node prune location
fixes https://github.com/ohmyform/ohmyform/issues/184
2022-07-28 08:01:08 +02:00
Michael Schramm
b6f0c9d64a fix creation of new logic elements 2022-03-27 20:41:27 +02:00
Michael Schramm
3b5a6c92af release 1.0.3 2022-03-27 11:31:47 +02:00
Michael Schramm
d86b02cf2e fix field resolvers, add start and end page to create 2022-03-27 11:30:17 +02:00
Michael Schramm
ef7cd7b3fb update node-gyp to latest version to enable build on osx 12.3 2022-03-26 17:19:54 +01:00
Michael Schramm
c71e87ec50 fix build 2022-03-14 16:40:50 +01:00
Michael Schramm
27aa5ebfb0 notifications / hooks / pages and buttons encode and decode their ids 2022-03-14 15:47:20 +01:00
Michael Schramm
7ea4f51f4b form hooks should only be queryable for form admins 2022-03-14 13:49:16 +01:00
Michael Schramm
b3cacb8481 fix missing encode / decode for form fields within submissions 2022-03-14 13:48:17 +01:00
Michael Schramm
6d85dc660a release 1.0.2 2022-03-13 23:43:18 +01:00
Michael Schramm
30c2bc9c05 fix error sending notification when field is not defined 2022-03-13 23:35:40 +01:00
Michael Schramm
99fe3d2fe0 release 1.0.1 2022-03-01 23:37:54 +01:00
Michael Schramm
5bc6047507 change default form layout to card 2022-03-01 15:17:39 +01:00
Michael Schramm
4a19bd5ca4 add new form by id pipe 2022-03-01 08:40:25 +01:00
Michael Schramm
fe3821ad42 stop exposing internal ids and switch over to hashids 2022-02-28 22:50:20 +01:00
Michael Schramm
2d3589e13a fix form delete 2022-02-28 22:48:39 +01:00
Michael Schramm
5b717a2963 only update user fields if they changed 2022-02-28 22:48:19 +01:00
Michael Schramm
1e3f47c11c update changelog for version 1.0.0 2022-02-28 08:23:34 +01:00
Michael Schramm
cfd8d288d4
add tests for migrations (#39) 2022-02-28 08:20:51 +01:00
68 changed files with 1149 additions and 271 deletions

View File

@ -5,10 +5,79 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
<!--
Template for next version
## [Unreleased]
### Added
### Changed
### Fixed
### Security
-->
## [Unreleased]
### Added
### Changed
### Fixed
- creation of new logic elements
- node prune location (https://github.com/ohmyform/ohmyform/issues/184)
### Security
## [1.0.3] - 2022-03-27
### Added
- add start and end page to form create call
### Changed
- notifications / hooks / pages and buttons encode and decode their ids
### Fixed
- missing encode / decode for form fields within submissions (https://github.com/ohmyform/ui/commit/30ff2c96bca20c1641d9cbb96c34cce934e1afea#r68602651)
- form field resolvers were missing
- node-gyp update to enable build on osx 12.3
- creating of new fields
### Security
- form hooks should only be queryable for form admins
## [1.0.2] - 2022-03-13
### Fixed
- error sending notification when field is not defined (https://github.com/ohmyform/ohmyform/issues/161)
## [1.0.1] - 2022-03-01
### Added
- allow one field nested data to be submitted
### Fixed
- only update user fields in update mutation if they changed
- form delete
- field submission without value field
### Security
- start using hashids to prevent insights into form ids (https://hashids.org/javascript/)
## [1.0.0] - 2022-02-28
### Added
- logic backend components
- forms now have multiple notification
- layout for forms
@ -19,6 +88,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- ability to load submission by id if token is present
- anonymous form submissions (fixes https://github.com/ohmyform/ohmyform/issues/108)
- ability to filter for partial / completed or empty submissions
- migration tests for all commits
### Changed

View File

@ -6,7 +6,7 @@ WORKDIR /usr/src/app
RUN apk update && apk add curl bash && rm -rf /var/cache/apk/*
# install node-prune (https://github.com/tj/node-prune)
RUN curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | bash -s -- -b /usr/local/bin
RUN curl -sf https://gobinaries.com/tj/node-prune | sh
# just copy everhing
COPY . .

View File

@ -1,6 +1,6 @@
{
"name": "ohmyform-api",
"version": "1.0.0",
"version": "1.0.3",
"description": "",
"author": "",
"license": "AGPL-3.0-or-later",
@ -51,6 +51,7 @@
"graphql-subscriptions": "^2.0.0",
"graphql-tools": "^8.2.0",
"handlebars": "^4.7.7",
"hashids": "^2.2.10",
"html-to-text": "^8.1.0",
"inquirer": "^8.2.0",
"ioredis": "^4.28.5",
@ -84,7 +85,7 @@
"@types/handlebars": "^4.1.0",
"@types/html-to-text": "^8.0.1",
"@types/inquirer": "^8.2.0",
"@types/jest": "27.4.1",
"@types/jest": "^27.4.1",
"@types/mjml": "^4.7.0",
"@types/node": "^17.0.21",
"@types/passport-jwt": "^3.0.6",
@ -99,6 +100,7 @@
"eslint-plugin-nestjs": "^1.2.3",
"eslint-plugin-unused-imports": "^2.0.0",
"jest": "^27.5.1",
"node-gyp": "^9.0.0",
"prettier": "^2.5.1",
"supertest": "^6.2.2",
"ts-jest": "27.1.3",

View File

@ -1,11 +1,13 @@
import { commands } from './command'
import { guards } from './guard'
import { pipes } from './pipe'
import { resolvers } from './resolver'
import { services } from './service'
export const providers = [
...resolvers,
...commands,
...services,
...guards,
...pipes,
...resolvers,
...services,
]

View File

@ -1,8 +1,8 @@
import { Field, ObjectType } from '@nestjs/graphql'
import { Field, ID, ObjectType } from '@nestjs/graphql'
@ObjectType('Deleted')
export class DeletedModel {
@Field()
@Field(() => ID)
id: string
constructor(id: string) {

View File

@ -3,6 +3,8 @@ import { PageButtonEntity } from '../../entity/page.button.entity'
@ObjectType('Button')
export class ButtonModel {
readonly _id: number
@Field(() => ID)
readonly id: string
@ -24,8 +26,9 @@ export class ButtonModel {
@Field({ nullable: true })
readonly color?: string
constructor(button: Partial<PageButtonEntity>) {
this.id = button.id.toString()
constructor(id: string, button: Partial<PageButtonEntity>) {
this._id = button.id
this.id = id
this.url = button.url
this.action = button.action
this.text = button.text

View File

@ -1,4 +1,5 @@
import { Field, InputType } from '@nestjs/graphql'
import { PageInput } from './page.input'
@InputType('FormCreateInput')
export class FormCreateInput {
@ -19,4 +20,10 @@ export class FormCreateInput {
@Field({ nullable: true })
readonly layout: string
@Field({ nullable: true })
readonly startPage: PageInput
@Field({ nullable: true })
readonly endPage: PageInput
}

View File

@ -3,6 +3,8 @@ import { FormFieldLogicEntity } from '../../entity/form.field.logic.entity'
@ObjectType('FormFieldLogic')
export class FormFieldLogicModel {
readonly _id: number
@Field(() => ID)
readonly id: string
@ -30,8 +32,9 @@ export class FormFieldLogicModel {
@Field()
readonly enabled: boolean
constructor(document: FormFieldLogicEntity) {
this.id = document.id.toString()
constructor(id: string, document: FormFieldLogicEntity) {
this._id = document.id
this.id = id
this.enabled = document.enabled
this.formula = document.formula

View File

@ -1,11 +1,10 @@
import { Field, ID, ObjectType } from '@nestjs/graphql'
import { FormFieldEntity } from '../../entity/form.field.entity'
import { FormFieldLogicModel } from './form.field.logic.model'
import { FormFieldOptionModel } from './form.field.option.model'
import { FormFieldRatingModel } from './form.field.rating.model'
@ObjectType('FormField')
export class FormFieldModel {
readonly _id: number
@Field(() => ID)
readonly id: string
@ -30,17 +29,9 @@ export class FormFieldModel {
@Field({ nullable: true })
readonly defaultValue: string
@Field(() => [FormFieldOptionModel])
readonly options: FormFieldOptionModel[]
@Field(() => [FormFieldLogicModel])
readonly logic: FormFieldLogicModel[]
@Field(() => FormFieldRatingModel, { nullable: true })
readonly rating: FormFieldRatingModel
constructor(document: FormFieldEntity) {
this.id = document.id.toString()
constructor(id: string, document: FormFieldEntity) {
this._id = document.id
this.id = id
this.idx = document.idx
this.title = document.title
this.slug = document.slug
@ -48,8 +39,5 @@ export class FormFieldModel {
this.description = document.description
this.required = document.required
this.defaultValue = document.defaultValue
this.options = document.options?.map(option => new FormFieldOptionModel(option)) || []
this.logic = document.logic?.map(logic => new FormFieldLogicModel(logic)) || []
this.rating = document.rating ? new FormFieldRatingModel(document.rating) : null
}
}

View File

@ -3,6 +3,8 @@ import { FormFieldOptionEntity } from '../../entity/form.field.option.entity'
@ObjectType('FormFieldOption')
export class FormFieldOptionModel {
readonly _id: number
@Field(() => ID)
readonly id: string
@ -15,8 +17,9 @@ export class FormFieldOptionModel {
@Field()
readonly value: string
constructor(option: FormFieldOptionEntity) {
this.id = option.id.toString()
constructor(id: string, option: FormFieldOptionEntity) {
this._id = option.id
this.id = id
this.key = option.key
this.title = option.title
this.value = option.value

View File

@ -3,6 +3,8 @@ import { FormHookEntity } from '../../entity/form.hook.entity'
@ObjectType('FormHook')
export class FormHookModel {
readonly _id: number
@Field(() => ID)
readonly id: string
@ -15,8 +17,9 @@ export class FormHookModel {
@Field({ nullable: true })
readonly format?: string
constructor(hook: FormHookEntity) {
this.id = hook.id.toString()
constructor(id, hook: FormHookEntity) {
this._id = hook.id
this.id = id
this.enabled = hook.enabled
this.url = hook.url
this.format = hook.format

View File

@ -3,6 +3,8 @@ import { FormEntity } from '../../entity/form.entity'
@ObjectType('Form')
export class FormModel {
readonly _id: number
@Field(() => ID)
readonly id: string
@ -24,8 +26,9 @@ export class FormModel {
@Field()
readonly anonymousSubmission: boolean
constructor(form: FormEntity) {
this.id = form.id.toString()
constructor(id: string, form: FormEntity) {
this._id = form.id
this.id = id
this.title = form.title
this.created = form.created
this.lastModified = form.lastModified

View File

@ -3,6 +3,8 @@ import { FormNotificationEntity } from '../../entity/form.notification.entity'
@ObjectType('FormNotification')
export class FormNotificationModel {
readonly _id: number
@Field(() => ID)
readonly id: string
@ -27,8 +29,9 @@ export class FormNotificationModel {
@Field()
readonly enabled: boolean
constructor(partial: Partial<FormNotificationEntity>) {
this.id = partial.id.toString()
constructor(id: string, partial: Partial<FormNotificationEntity>) {
this._id = partial.id
this.id = id
this.subject = partial.subject
this.htmlTemplate = partial.htmlTemplate
this.enabled = partial.enabled

View File

@ -18,6 +18,6 @@ export class PageInput {
@Field({ nullable: true })
readonly buttonText?: string
@Field(() => [ButtonInput])
@Field(() => [ButtonInput], { nullable: true })
readonly buttons: ButtonInput[]
}

View File

@ -1,9 +1,10 @@
import { Field, ID, ObjectType } from '@nestjs/graphql'
import { PageEntity } from '../../entity/page.entity'
import { ButtonModel } from './button.model'
@ObjectType('Page')
export class PageModel {
readonly _id: number
@Field(() => ID)
readonly id: string
@ -19,22 +20,18 @@ export class PageModel {
@Field({ nullable: true })
readonly buttonText?: string
@Field(() => [ButtonModel])
readonly buttons: ButtonModel[]
constructor(page: Partial<PageEntity>) {
constructor(id: string, page?: Partial<PageEntity>) {
if (!page) {
this.id = Math.random().toString()
this.id = id
this.show = false
this.buttons = []
return
}
this.id = page.id.toString()
this._id = page.id
this.id = id
this.show = page.show
this.title = page.title
this.paragraph = page.paragraph
this.buttonText = page.buttonText
this.buttons = (page.buttons || []).map(button => new ButtonModel(button))
}
}

View File

@ -7,8 +7,8 @@ export class ProfileModel extends UserModel {
@Field(() => [String])
readonly roles: string[]
constructor(user: UserEntity) {
super(user)
constructor(id: string, user: UserEntity) {
super(id, user)
this.roles = user.roles
}

View File

@ -3,6 +3,8 @@ import { SubmissionFieldEntity } from '../../entity/submission.field.entity'
@ObjectType('SubmissionField')
export class SubmissionFieldModel {
readonly _id: number
@Field(() => ID)
readonly id: string
@ -12,8 +14,9 @@ export class SubmissionFieldModel {
@Field()
readonly type: string
constructor(field: SubmissionFieldEntity) {
this.id = field.id.toString()
constructor(id: string, field: SubmissionFieldEntity) {
this._id = field.id
this.id = id
this.value = JSON.stringify(field.content)
this.type = field.type
}

View File

@ -5,8 +5,10 @@ import { GeoLocationModel } from './geo.location.model'
@ObjectType('Submission')
export class SubmissionModel {
readonly _id: number
@Field(() => ID)
readonly id: number
readonly id: string
@Field()
readonly ipAddr: string
@ -29,8 +31,9 @@ export class SubmissionModel {
@Field({ nullable: true })
readonly lastModified?: Date
constructor(submission: SubmissionEntity) {
this.id = submission.id
constructor(id: string, submission: SubmissionEntity) {
this._id = submission.id
this.id = id
this.ipAddr = submission.ipAddr
this.geoLocation = new GeoLocationModel(submission.geoLocation)

View File

@ -3,6 +3,8 @@ import { SubmissionEntity } from '../../entity/submission.entity'
@ObjectType('SubmissionProgress')
export class SubmissionProgressModel {
readonly _id: number
@Field(() => ID)
readonly id: string
@ -18,8 +20,9 @@ export class SubmissionProgressModel {
@Field({ nullable: true })
readonly lastModified?: Date
constructor(submission: Partial<SubmissionEntity>) {
this.id = submission.id.toString()
constructor(id: string, submission: Partial<SubmissionEntity>) {
this._id = submission.id
this.id = id
this.timeElapsed = submission.timeElapsed
this.percentageComplete = submission.percentageComplete

View File

@ -3,6 +3,8 @@ import { UserEntity } from '../../entity/user.entity'
@ObjectType('User')
export class UserModel {
readonly _id: number
@Field(() => ID)
readonly id: string
@ -36,8 +38,9 @@ export class UserModel {
@Field({ nullable: true })
readonly lastModified: Date
constructor(user: UserEntity) {
this.id = user.id.toString()
constructor(id: string, user: UserEntity) {
this._id = user.id
this.id = id
this.username = user.username
this.email = user.email

View File

@ -72,4 +72,10 @@ export class FormEntity {
@UpdateDateColumn()
public lastModified: Date
constructor(partial?: Partial<FormEntity>) {
if (partial) {
Object.assign(this, partial)
}
}
}

View File

@ -2,8 +2,12 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, RelationId } from 't
import { FormFieldEntity } from './form.field.entity'
import { SubmissionEntity } from './submission.entity'
export interface SubmissionFieldContent {
[key: string]: string | string[] | number | number[] | boolean | boolean[]
type Simple = string | number | boolean
export type SubmissionFieldContent = Simple | Simple[] | {
[key: string]: Simple | Simple[] | {
[key: string]: Simple | Simple[]
}
}
@Entity({ name: 'submission_field' })

View File

@ -0,0 +1,19 @@
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'
import { FormEntity } from '../../entity/form.entity'
import { FormService } from '../../service/form/form.service'
import { IdService } from '../../service/id.service'
@Injectable()
export class FormByIdPipe implements PipeTransform<string, Promise<FormEntity>> {
constructor(
private readonly formService: FormService,
private readonly idService: IdService,
) {
}
async transform(value: string, metadata: ArgumentMetadata): Promise<FormEntity> {
const id = this.idService.decode(value)
return await this.formService.findById(id)
}
}

3
src/pipe/form/index.ts Normal file
View File

@ -0,0 +1,3 @@
import { FormByIdPipe } from './form.by.id.pipe'
export const formPipes = [FormByIdPipe]

9
src/pipe/index.ts Normal file
View File

@ -0,0 +1,9 @@
import { formPipes } from './form'
import { submissionPipes } from './submission'
import { userPipes } from './user'
export const pipes = [
...formPipes,
...submissionPipes,
...userPipes,
]

View File

@ -0,0 +1,3 @@
import { SubmissionByIdPipe } from './submission.by.id.pipe'
export const submissionPipes = [SubmissionByIdPipe]

View File

@ -0,0 +1,19 @@
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'
import { SubmissionEntity } from '../../entity/submission.entity'
import { IdService } from '../../service/id.service'
import { SubmissionService } from '../../service/submission/submission.service'
@Injectable()
export class SubmissionByIdPipe implements PipeTransform<string, Promise<SubmissionEntity>> {
constructor(
private readonly submissionService: SubmissionService,
private readonly idService: IdService,
) {
}
async transform(value: string, metadata: ArgumentMetadata): Promise<SubmissionEntity> {
const id = this.idService.decode(value)
return await this.submissionService.findById(id)
}
}

3
src/pipe/user/index.ts Normal file
View File

@ -0,0 +1,3 @@
import { UserByIdPipe } from './user.by.id.pipe'
export const userPipes = [UserByIdPipe]

View File

@ -0,0 +1,19 @@
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'
import { UserEntity } from '../../entity/user.entity'
import { IdService } from '../../service/id.service'
import { UserService } from '../../service/user/user.service'
@Injectable()
export class UserByIdPipe implements PipeTransform<string, Promise<UserEntity>> {
constructor(
private readonly userService: UserService,
private readonly idService: IdService,
) {
}
async transform(value: string, metadata: ArgumentMetadata): Promise<UserEntity> {
const id = this.idService.decode(value)
return await this.userService.findById(id)
}
}

View File

@ -1,12 +1,10 @@
type ID = string | number
export class ContextCache<A = any> {
private cache: {
[key: string]: any
} = {}
public getCacheKey(type: string, id: ID): string {
public getCacheKey(type: string, id: number): string {
return `${type}:${id}`
}

View File

@ -7,12 +7,14 @@ import { FormModel } from '../../dto/form/form.model'
import { FormEntity } from '../../entity/form.entity'
import { UserEntity } from '../../entity/user.entity'
import { FormCreateService } from '../../service/form/form.create.service'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'
@Injectable()
export class FormCreateMutation {
constructor(
private readonly createService: FormCreateService
private readonly createService: FormCreateService,
private readonly idService: IdService,
) {
}
@ -27,6 +29,6 @@ export class FormCreateMutation {
cache.add(cache.getCacheKey(FormEntity.name, form.id), form)
return new FormModel(form)
return new FormModel(this.idService.encode(form.id), form)
}
}

View File

@ -3,15 +3,19 @@ import { Args, ID, Mutation } from '@nestjs/graphql'
import { Roles } from '../../decorator/roles.decorator'
import { User } from '../../decorator/user.decorator'
import { DeletedModel } from '../../dto/deleted.model'
import { FormEntity } from '../../entity/form.entity'
import { UserEntity } from '../../entity/user.entity'
import { FormByIdPipe } from '../../pipe/form/form.by.id.pipe'
import { FormDeleteService } from '../../service/form/form.delete.service'
import { FormService } from '../../service/form/form.service'
import { IdService } from '../../service/id.service'
@Injectable()
export class FormDeleteMutation {
constructor(
private readonly deleteService: FormDeleteService,
private readonly formService: FormService,
private readonly idService: IdService,
) {
}
@ -19,16 +23,14 @@ export class FormDeleteMutation {
@Roles('admin')
async deleteForm(
@User() user: UserEntity,
@Args({ name: 'id', type: () => ID}) id: string,
@Args('id', {type: () => ID}, FormByIdPipe) form: FormEntity,
): Promise<DeletedModel> {
const form = await this.formService.findById(id)
if (!form.isLive && !this.formService.isAdmin(form, user)) {
throw new Error('invalid form')
}
await this.deleteService.delete(id)
await this.deleteService.delete(form.id)
return new DeletedModel(id)
return new DeletedModel(this.idService.encode(form.id))
}
}

View File

@ -0,0 +1,73 @@
import { Context, Parent, ResolveField, Resolver } from '@nestjs/graphql'
import { FormFieldLogicModel } from '../../dto/form/form.field.logic.model'
import { FormFieldModel } from '../../dto/form/form.field.model'
import { FormFieldOptionModel } from '../../dto/form/form.field.option.model'
import { FormFieldRatingModel } from '../../dto/form/form.field.rating.model'
import { FormFieldEntity } from '../../entity/form.field.entity'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'
@Resolver(FormFieldModel)
export class FormFieldResolver {
constructor(
private readonly idService: IdService,
) {
}
@ResolveField(() => [FormFieldOptionModel])
async options(
@Parent() parent: FormFieldModel,
@Context('cache') cache: ContextCache,
): Promise<FormFieldOptionModel[]> {
const field = await cache.get<FormFieldEntity>(cache.getCacheKey(
FormFieldEntity.name,
parent._id
))
if (!field.options) {
return []
}
return field.options.map(option => new FormFieldOptionModel(
this.idService.encode(option.id),
option,
))
}
@ResolveField(() => [FormFieldLogicModel])
async logic(
@Parent() parent: FormFieldModel,
@Context('cache') cache: ContextCache,
): Promise<FormFieldLogicModel[]> {
const field = await cache.get<FormFieldEntity>(cache.getCacheKey(
FormFieldEntity.name,
parent._id
))
if (!field.logic) {
return []
}
return field.logic.map(logic => new FormFieldLogicModel(
this.idService.encode(logic.id),
logic,
))
}
@ResolveField(() => FormFieldRatingModel, { nullable: true })
async rating(
@Parent() parent: FormFieldModel,
@Context('cache') cache: ContextCache,
): Promise<FormFieldRatingModel> {
const field = await cache.get<FormFieldEntity>(cache.getCacheKey(
FormFieldEntity.name,
parent._id
))
if (!field.rating) {
return null
}
return new FormFieldRatingModel(field.rating)
}
}

View File

@ -7,12 +7,14 @@ import { FormPagerModel } from '../../dto/form/form.pager.model'
import { FormEntity } from '../../entity/form.entity'
import { UserEntity } from '../../entity/user.entity'
import { FormService } from '../../service/form/form.service'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'
@Injectable()
export class FormListQuery {
constructor(
private readonly formService: FormService,
private readonly idService: IdService,
) {
}
@ -34,7 +36,7 @@ export class FormListQuery {
forms.forEach(form => cache.add(cache.getCacheKey(FormEntity.name, form.id), form))
return new FormPagerModel(
forms.map(form => new FormModel(form)),
forms.map(form => new FormModel(this.idService.encode(form.id), form)),
total,
limit,
start,

View File

@ -4,30 +4,31 @@ import { User } from '../../decorator/user.decorator'
import { FormModel } from '../../dto/form/form.model'
import { FormEntity } from '../../entity/form.entity'
import { UserEntity } from '../../entity/user.entity'
import { FormByIdPipe } from '../../pipe/form/form.by.id.pipe'
import { FormService } from '../../service/form/form.service'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'
@Injectable()
export class FormQuery {
constructor(
private readonly formService: FormService,
private readonly idService: IdService,
) {
}
@Query(() => FormModel)
async getFormById(
getFormById(
@User() user: UserEntity,
@Args('id', {type: () => ID}) id,
@Args('id', {type: () => ID}, FormByIdPipe) form: FormEntity,
@Context('cache') cache: ContextCache,
): Promise<FormModel> {
const form = await this.formService.findById(id)
): FormModel {
if (!form.isLive && !this.formService.isAdmin(form, user)) {
throw new Error('invalid form')
}
cache.add(cache.getCacheKey(FormEntity.name, form.id), form)
return new FormModel(form)
return new FormModel(this.idService.encode(form.id), form)
}
}

View File

@ -9,14 +9,18 @@ import { FormNotificationModel } from '../../dto/form/form.notification.model'
import { PageModel } from '../../dto/form/page.model'
import { UserModel } from '../../dto/user/user.model'
import { FormEntity } from '../../entity/form.entity'
import { FormFieldEntity } from '../../entity/form.field.entity'
import { PageEntity } from '../../entity/page.entity'
import { UserEntity } from '../../entity/user.entity'
import { FormService } from '../../service/form/form.service'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'
@Resolver(() => FormModel)
export class FormResolver {
constructor(
private readonly formService: FormService,
private readonly idService: IdService,
) {
}
@ -26,20 +30,45 @@ export class FormResolver {
@Parent() parent: FormModel,
@Context('cache') cache: ContextCache,
): Promise<FormFieldModel[]> {
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent.id))
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))
return form.fields?.map(field => new FormFieldModel(field)).sort((a,b) => a.idx - b.idx) || []
if (!form.fields) {
return []
}
return form.fields
.sort((a,b) => a.idx - b.idx)
.map(field => {
cache.add(cache.getCacheKey(FormFieldEntity.name, field.id), field)
return new FormFieldModel(
this.idService.encode(field.id),
field,
)
})
}
@ResolveField(() => [FormHookModel])
@Roles('admin')
async hooks(
@User() user: UserEntity,
@Parent() parent: FormModel,
@Context('cache') cache: ContextCache,
): Promise<FormHookModel[]> {
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent.id))
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))
return form.hooks?.map(hook => new FormHookModel(hook)) || []
if (!this.formService.isAdmin(form, user)) {
throw new Error('no access to field')
}
if (!form.hooks) {
return []
}
return form.hooks.map(hook => new FormHookModel(
this.idService.encode(hook.id),
hook
))
}
@ResolveField(() => Boolean)
@ -49,7 +78,7 @@ export class FormResolver {
@Parent() parent: FormModel,
@Context('cache') cache: ContextCache,
): Promise<boolean> {
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent.id))
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))
if (!this.formService.isAdmin(form, user)) {
throw new Error('no access to field')
@ -65,13 +94,20 @@ export class FormResolver {
@Parent() parent: FormModel,
@Context('cache') cache: ContextCache,
): Promise<FormNotificationModel[]> {
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent.id))
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))
if (!this.formService.isAdmin(form, user)) {
throw new Error('no access to field')
}
return form.notifications?.map(notification => new FormNotificationModel(notification)) || []
if (!form.notifications) {
return []
}
return form.notifications.map(notification => new FormNotificationModel(
this.idService.encode(notification.id),
notification
))
}
@ResolveField(() => DesignModel)
@ -80,7 +116,7 @@ export class FormResolver {
@Parent() parent: FormModel,
@Context('cache') cache: ContextCache,
): Promise<DesignModel> {
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent.id))
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))
return new DesignModel(form.design)
}
@ -90,9 +126,21 @@ export class FormResolver {
@Parent() parent: FormModel,
@Context('cache') cache: ContextCache,
): Promise<PageModel> {
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent.id))
const { startPage } = await cache.get<FormEntity>(cache.getCacheKey(
FormEntity.name,
parent._id
))
return new PageModel(form.startPage)
if (startPage) {
cache.add(cache.getCacheKey(PageEntity.name, startPage.id), startPage)
return new PageModel(
this.idService.encode(startPage.id),
startPage
)
}
return new PageModel(Math.random().toString())
}
@ResolveField(() => PageModel)
@ -100,9 +148,18 @@ export class FormResolver {
@Parent() parent: FormModel,
@Context('cache') cache: ContextCache,
): Promise<PageModel> {
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent.id))
const { endPage } = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))
return new PageModel(form.endPage)
if (endPage) {
cache.add(cache.getCacheKey(PageEntity.name, endPage.id), endPage)
return new PageModel(
this.idService.encode(endPage.id),
endPage
)
}
return new PageModel(Math.random().toString())
}
@ResolveField(() => UserModel, { nullable: true })
@ -111,12 +168,12 @@ export class FormResolver {
@Parent() parent: FormModel,
@Context('cache') cache: ContextCache,
): Promise<UserModel> {
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent.id))
const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))
if (!form.admin) {
return null
}
return new UserModel(form.admin)
return new UserModel(this.idService.encode(form.admin.id), form.admin)
}
}

View File

@ -8,6 +8,7 @@ import { FormEntity } from '../../entity/form.entity'
import { UserEntity } from '../../entity/user.entity'
import { FormService } from '../../service/form/form.service'
import { FormUpdateService } from '../../service/form/form.update.service'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'
@Injectable()
@ -15,6 +16,7 @@ export class FormUpdateMutation {
constructor(
private readonly updateService: FormUpdateService,
private readonly formService: FormService,
private readonly idService: IdService,
) {
}
@ -25,7 +27,7 @@ export class FormUpdateMutation {
@Args({ name: 'form', type: () => FormUpdateInput }) input: FormUpdateInput,
@Context('cache') cache: ContextCache,
): Promise<FormModel> {
const form = await this.formService.findById(input.id)
const form = await this.formService.findById(this.idService.decode(input.id))
if (!form.isLive && !this.formService.isAdmin(form, user)) {
throw new Error('invalid form')
@ -35,6 +37,6 @@ export class FormUpdateMutation {
cache.add(cache.getCacheKey(FormEntity.name, form.id), form)
return new FormModel(form)
return new FormModel(this.idService.encode(form.id), form)
}
}

View File

@ -1,19 +1,23 @@
import { FormCreateMutation } from './form.create.mutation'
import { FormDeleteMutation } from './form.delete.mutation'
import { FormFieldResolver } from './form.field.resolver'
import { FormListQuery } from './form.list.query'
import { FormQuery } from './form.query'
import { FormResolver } from './form.resolver'
import { FormStatisticQuery } from './form.statistic.query'
import { FormStatisticResolver } from './form.statistic.resolver'
import { FormUpdateMutation } from './form.update.mutation'
import { PageResolver } from './page.resolver'
export const formResolvers = [
FormCreateMutation,
FormDeleteMutation,
FormFieldResolver,
FormQuery,
FormResolver,
FormListQuery,
FormStatisticQuery,
FormStatisticResolver,
FormUpdateMutation,
PageResolver,
]

View File

@ -0,0 +1,32 @@
import { Context, Parent, ResolveField, Resolver } from '@nestjs/graphql'
import { ButtonModel } from '../../dto/form/button.model'
import { FormModel } from '../../dto/form/form.model'
import { PageModel } from '../../dto/form/page.model'
import { PageEntity } from '../../entity/page.entity'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'
@Resolver(() => PageModel)
export class PageResolver {
constructor(
private readonly idService: IdService,
) {
}
@ResolveField(() => [ButtonModel])
async buttons(
@Parent() parent: FormModel,
@Context('cache') cache: ContextCache,
): Promise<ButtonModel[]> {
if (!parent._id) {
return []
}
const page = await cache.get<PageEntity>(cache.getCacheKey(PageEntity.name, parent._id))
return page.buttons.map(button => new ButtonModel(
this.idService.encode(button.id),
button
))
}
}

View File

@ -4,10 +4,16 @@ import { Roles } from '../../decorator/roles.decorator'
import { User } from '../../decorator/user.decorator'
import { ProfileModel } from '../../dto/profile/profile.model'
import { UserEntity } from '../../entity/user.entity'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'
@Injectable()
export class ProfileQuery {
constructor(
private readonly idService: IdService,
) {
}
@Query(() => ProfileModel)
@Roles('user')
public me(
@ -16,6 +22,6 @@ export class ProfileQuery {
): ProfileModel {
cache.add(cache.getCacheKey(UserEntity.name, user.id), user)
return new ProfileModel(user)
return new ProfileModel(this.idService.encode(user.id), user)
}
}

View File

@ -5,6 +5,7 @@ import { User } from '../../decorator/user.decorator'
import { ProfileModel } from '../../dto/profile/profile.model'
import { ProfileUpdateInput } from '../../dto/profile/profile.update.input'
import { UserEntity } from '../../entity/user.entity'
import { IdService } from '../../service/id.service'
import { ProfileUpdateService } from '../../service/profile/profile.update.service'
import { ContextCache } from '../context.cache'
@ -12,6 +13,7 @@ import { ContextCache } from '../context.cache'
export class ProfileUpdateMutation {
constructor(
private readonly updateService: ProfileUpdateService,
private readonly idService: IdService,
) {
}
@ -26,7 +28,7 @@ export class ProfileUpdateMutation {
cache.add(cache.getCacheKey(UserEntity.name, user.id), user)
return new ProfileModel(user)
return new ProfileModel(this.idService.encode(user.id), user)
}
@Mutation(() => ProfileModel)
@ -40,6 +42,6 @@ export class ProfileUpdateMutation {
cache.add(cache.getCacheKey(UserEntity.name, user.id), user)
return new ProfileModel(user)
return new ProfileModel(this.idService.encode(user.id), user)
}
}

View File

@ -4,12 +4,14 @@ import { SubmissionFieldModel } from '../../dto/submission/submission.field.mode
import { FormFieldEntity } from '../../entity/form.field.entity'
import { SubmissionFieldEntity } from '../../entity/submission.field.entity'
import { FormFieldService } from '../../service/form/form.field.service'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'
@Resolver(() => SubmissionFieldModel)
export class SubmissionFieldResolver {
constructor(
private readonly formFieldService: FormFieldService,
private readonly idService: IdService,
) {
}
@ -19,7 +21,7 @@ export class SubmissionFieldResolver {
@Context('cache') cache: ContextCache,
): Promise<FormFieldModel> {
const submissionField = await cache.get<SubmissionFieldEntity>(
cache.getCacheKey(SubmissionFieldEntity.name, parent.id)
cache.getCacheKey(SubmissionFieldEntity.name, parent._id)
)
const field = await cache.get<FormFieldEntity>(
@ -33,6 +35,9 @@ export class SubmissionFieldResolver {
return null
}
return new FormFieldModel(field)
return new FormFieldModel(
this.idService.encode(field.id),
field,
)
}
}

View File

@ -2,33 +2,31 @@ import { Injectable } from '@nestjs/common'
import { Args, Context, ID, Mutation } from '@nestjs/graphql'
import { User } from '../../decorator/user.decorator'
import { SubmissionProgressModel } from '../../dto/submission/submission.progress.model'
import { SubmissionSetFieldInput } from '../../dto/submission/submission.set.field.input'
import { SubmissionEntity } from '../../entity/submission.entity'
import { UserEntity } from '../../entity/user.entity'
import { SubmissionService } from '../../service/submission/submission.service'
import { SubmissionByIdPipe } from '../../pipe/submission/submission.by.id.pipe'
import { IdService } from '../../service/id.service'
import { SubmissionSetFieldService } from '../../service/submission/submission.set.field.service'
import { ContextCache } from '../context.cache'
@Injectable()
export class SubmissionFinishMutation {
constructor(
private readonly submissionService: SubmissionService,
private readonly setFieldService: SubmissionSetFieldService,
private readonly idService: IdService,
) {
}
@Mutation(() => SubmissionProgressModel)
async submissionFinish(
@User() user: UserEntity,
@Args({ name: 'submission', type: () => ID }) id: string,
@Args({ name: 'submission', type: () => ID }, SubmissionByIdPipe) submission: SubmissionEntity,
@Context('cache') cache: ContextCache,
): Promise<SubmissionProgressModel> {
const submission = await this.submissionService.findById(id)
await this.setFieldService.finishSubmission(submission)
cache.add(cache.getCacheKey(SubmissionEntity.name, submission.id), submission)
return new SubmissionProgressModel(submission)
return new SubmissionProgressModel(this.idService.encode(submission.id), submission)
}
}

View File

@ -4,31 +4,31 @@ import { User } from '../../decorator/user.decorator'
import { SubmissionModel } from '../../dto/submission/submission.model'
import { SubmissionPagerFilterInput } from '../../dto/submission/submission.pager.filter.input'
import { SubmissionPagerModel } from '../../dto/submission/submission.pager.model'
import { FormEntity } from '../../entity/form.entity'
import { SubmissionEntity } from '../../entity/submission.entity'
import { UserEntity } from '../../entity/user.entity'
import { FormService } from '../../service/form/form.service'
import { FormByIdPipe } from '../../pipe/form/form.by.id.pipe'
import { IdService } from '../../service/id.service'
import { SubmissionService } from '../../service/submission/submission.service'
import { ContextCache } from '../context.cache'
@Injectable()
export class SubmissionListQuery {
constructor(
private readonly formService: FormService,
private readonly submissionService: SubmissionService,
private readonly idService: IdService,
) {
}
@Query(() => SubmissionPagerModel)
async listSubmissions(
@User() user: UserEntity,
@Args('form', {type: () => ID}) id: string,
@Args('form', {type: () => ID}, FormByIdPipe) form: FormEntity,
@Args('start', {type: () => Int, defaultValue: 0, nullable: true}) start: number,
@Args('limit', {type: () => Int, defaultValue: 50, nullable: true}) limit: number,
@Args('filter', {type: () => SubmissionPagerFilterInput, defaultValue: new SubmissionPagerFilterInput()}) filter: SubmissionPagerFilterInput,
@Context('cache') cache: ContextCache,
): Promise<SubmissionPagerModel> {
const form = await this.formService.findById(id)
const [submissions, total] = await this.submissionService.find(
form,
start,
@ -42,7 +42,10 @@ export class SubmissionListQuery {
})
return new SubmissionPagerModel(
submissions.map(submission => new SubmissionModel(submission)),
submissions.map(submission => new SubmissionModel(
this.idService.encode(submission.id),
submission
)),
total,
limit,
start,

View File

@ -4,8 +4,9 @@ import { User } from '../../decorator/user.decorator'
import { SubmissionModel } from '../../dto/submission/submission.model'
import { SubmissionEntity } from '../../entity/submission.entity'
import { UserEntity } from '../../entity/user.entity'
import { SubmissionByIdPipe } from '../../pipe/submission/submission.by.id.pipe'
import { FormService } from '../../service/form/form.service'
import { SubmissionService } from '../../service/submission/submission.service'
import { IdService } from '../../service/id.service'
import { SubmissionTokenService } from '../../service/submission/submission.token.service'
import { ContextCache } from '../context.cache'
@ -13,20 +14,18 @@ import { ContextCache } from '../context.cache'
export class SubmissionQuery {
constructor(
private readonly formService: FormService,
private readonly submissionService: SubmissionService,
private readonly tokenService: SubmissionTokenService,
private readonly idService: IdService,
) {
}
@Query(() => SubmissionModel)
async getSubmissionById(
@User() user: UserEntity,
@Args('id', {type: () => ID}) id: string,
@Args('id', {type: () => ID}, SubmissionByIdPipe) submission: SubmissionEntity,
@Args('token', {nullable: true}) token: string,
@Context('cache') cache: ContextCache,
): Promise<SubmissionModel> {
const submission = await this.submissionService.findById(id)
if (
!await this.tokenService.verify(token, submission.tokenHash)
&& !this.formService.isAdmin(submission.form, user)
@ -36,6 +35,6 @@ export class SubmissionQuery {
cache.add(cache.getCacheKey(SubmissionEntity.name, submission.id), submission)
return new SubmissionModel(submission)
return new SubmissionModel(this.idService.encode(submission.id), submission)
}
}

View File

@ -5,10 +5,16 @@ import { SubmissionModel } from '../../dto/submission/submission.model'
import { SubmissionEntity } from '../../entity/submission.entity'
import { SubmissionFieldEntity } from '../../entity/submission.field.entity'
import { UserEntity } from '../../entity/user.entity'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'
@Resolver(() => SubmissionModel)
export class SubmissionResolver {
constructor(
private readonly idService: IdService,
) {
}
@ResolveField(() => [SubmissionFieldModel])
async fields(
@User() user: UserEntity,
@ -16,12 +22,12 @@ export class SubmissionResolver {
@Context('cache') cache: ContextCache,
): Promise<SubmissionFieldModel[]> {
const submission = await cache.get<SubmissionEntity>(
cache.getCacheKey(SubmissionEntity.name, parent.id)
cache.getCacheKey(SubmissionEntity.name, parent._id)
)
return submission.fields.map(field => {
cache.add(cache.getCacheKey(SubmissionFieldEntity.name, field.id), field)
return new SubmissionFieldModel(field)
return new SubmissionFieldModel(this.idService.encode(field.id), field)
})
}
}

View File

@ -5,6 +5,8 @@ import { SubmissionProgressModel } from '../../dto/submission/submission.progres
import { SubmissionSetFieldInput } from '../../dto/submission/submission.set.field.input'
import { SubmissionEntity } from '../../entity/submission.entity'
import { UserEntity } from '../../entity/user.entity'
import { SubmissionByIdPipe } from '../../pipe/submission/submission.by.id.pipe'
import { IdService } from '../../service/id.service'
import { SubmissionService } from '../../service/submission/submission.service'
import { SubmissionSetFieldService } from '../../service/submission/submission.set.field.service'
import { ContextCache } from '../context.cache'
@ -14,18 +16,17 @@ export class SubmissionSetFieldMutation {
constructor(
private readonly submissionService: SubmissionService,
private readonly setFieldService: SubmissionSetFieldService,
private readonly idService: IdService,
) {
}
@Mutation(() => SubmissionProgressModel)
async submissionSetField(
@User() user: UserEntity,
@Args({ name: 'submission', type: () => ID }) id: string,
@Args({ name: 'submission', type: () => ID }, SubmissionByIdPipe) submission: SubmissionEntity,
@Args({ name: 'field', type: () => SubmissionSetFieldInput }) input: SubmissionSetFieldInput,
@Context('cache') cache: ContextCache,
): Promise<SubmissionProgressModel> {
const submission = await this.submissionService.findById(id)
if (!await this.submissionService.isOwner(submission, input.token)) {
throw new Error('no access to submission')
}
@ -34,6 +35,6 @@ export class SubmissionSetFieldMutation {
cache.add(cache.getCacheKey(SubmissionEntity.name, submission.id), submission)
return new SubmissionProgressModel(submission)
return new SubmissionProgressModel(this.idService.encode(submission.id), submission)
}
}

View File

@ -4,9 +4,12 @@ import { IpAddress } from '../../decorator/ip.address.decorator'
import { User } from '../../decorator/user.decorator'
import { SubmissionProgressModel } from '../../dto/submission/submission.progress.model'
import { SubmissionStartInput } from '../../dto/submission/submission.start.input'
import { FormEntity } from '../../entity/form.entity'
import { SubmissionEntity } from '../../entity/submission.entity'
import { UserEntity } from '../../entity/user.entity'
import { FormByIdPipe } from '../../pipe/form/form.by.id.pipe'
import { FormService } from '../../service/form/form.service'
import { IdService } from '../../service/id.service'
import { SubmissionStartService } from '../../service/submission/submission.start.service'
import { ContextCache } from '../context.cache'
@ -15,19 +18,18 @@ export class SubmissionStartMutation {
constructor(
private readonly startService: SubmissionStartService,
private readonly formService: FormService,
private readonly idService: IdService,
) {
}
@Mutation(() => SubmissionProgressModel)
async submissionStart(
@User() user: UserEntity,
@Args({ name: 'form', type: () => ID }) id: string,
@Args({ name: 'form', type: () => ID }, FormByIdPipe) form: FormEntity,
@Args({ name: 'submission', type: () => SubmissionStartInput }) input: SubmissionStartInput,
@IpAddress() ipAddr: string,
@Context('cache') cache: ContextCache,
): Promise<SubmissionProgressModel> {
const form = await this.formService.findById(id)
if (!form.isLive && !this.formService.isAdmin(form, user)) {
throw new Error('invalid form')
}
@ -36,6 +38,6 @@ export class SubmissionStartMutation {
cache.add(cache.getCacheKey(SubmissionEntity.name, submission.id), submission)
return new SubmissionProgressModel(submission)
return new SubmissionProgressModel(this.idService.encode(submission.id), submission)
}
}

View File

@ -4,12 +4,15 @@ import { Roles } from '../../decorator/roles.decorator'
import { User } from '../../decorator/user.decorator'
import { DeletedModel } from '../../dto/deleted.model'
import { UserEntity } from '../../entity/user.entity'
import { UserByIdPipe } from '../../pipe/user/user.by.id.pipe'
import { IdService } from '../../service/id.service'
import { UserDeleteService } from '../../service/user/user.delete.service'
@Injectable()
export class UserDeleteMutation {
constructor(
private readonly deleteService: UserDeleteService,
private readonly idService: IdService,
) {
}
@ -17,14 +20,14 @@ export class UserDeleteMutation {
@Roles('admin')
async deleteUser(
@User() auth: UserEntity,
@Args({ name: 'id', type: () => ID}) id: string,
@Args({ name: 'id', type: () => ID}, UserByIdPipe) user: UserEntity,
): Promise<DeletedModel> {
if (auth.id.toString() === id) {
if (auth.id === user.id) {
throw new Error('cannot delete your own user')
}
await this.deleteService.delete(id)
await this.deleteService.delete(user.id)
return new DeletedModel(id)
return new DeletedModel(this.idService.encode(user.id))
}
}

View File

@ -3,6 +3,7 @@ import { Roles } from '../../decorator/roles.decorator'
import { UserModel } from '../../dto/user/user.model'
import { UserPagerModel } from '../../dto/user/user.pager.model'
import { UserEntity } from '../../entity/user.entity'
import { IdService } from '../../service/id.service'
import { UserService } from '../../service/user/user.service'
import { ContextCache } from '../context.cache'
@ -10,6 +11,7 @@ import { ContextCache } from '../context.cache'
export class UserListQuery {
constructor(
private readonly userService: UserService,
private readonly idService: IdService,
) {
}
@ -25,7 +27,7 @@ export class UserListQuery {
return new UserPagerModel(
entities.map(entity => {
cache.add(cache.getCacheKey(UserEntity.name, entity.id), entity)
return new UserModel(entity)
return new UserModel(this.idService.encode(entity.id), entity)
}),
total,
limit,

View File

@ -3,26 +3,25 @@ import { Args, Context, ID, Query } from '@nestjs/graphql'
import { Roles } from '../../decorator/roles.decorator'
import { UserModel } from '../../dto/user/user.model'
import { UserEntity } from '../../entity/user.entity'
import { UserService } from '../../service/user/user.service'
import { UserByIdPipe } from '../../pipe/user/user.by.id.pipe'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'
@Injectable()
export class UserQuery {
constructor(
private readonly userService: UserService,
private readonly idService: IdService,
) {
}
@Query(() => UserModel)
@Roles('admin')
public async getUserById(
@Args('id', {type: () => ID}) id: string,
public getUserById(
@Args('id', {type: () => ID}, UserByIdPipe) user: UserEntity,
@Context('cache') cache: ContextCache,
): Promise<UserModel> {
const user = await this.userService.findById(id)
): UserModel {
cache.add(cache.getCacheKey(UserEntity.name, user.id), user)
return new UserModel(user)
return new UserModel(this.idService.encode(user.id), user)
}
}

View File

@ -21,7 +21,7 @@ export class UserResolver {
@Context('cache') cache: ContextCache,
): Promise<string[]> {
return this.returnFieldForSuperuser(
await cache.get<UserEntity>(cache.getCacheKey(UserEntity.name, parent.id)),
await cache.get<UserEntity>(cache.getCacheKey(UserEntity.name, parent._id)),
user,
c => c.roles
)

View File

@ -5,6 +5,7 @@ import { User } from '../../decorator/user.decorator'
import { UserModel } from '../../dto/user/user.model'
import { UserUpdateInput } from '../../dto/user/user.update.input'
import { UserEntity } from '../../entity/user.entity'
import { IdService } from '../../service/id.service'
import { UserService } from '../../service/user/user.service'
import { UserUpdateService } from '../../service/user/user.update.service'
import { ContextCache } from '../context.cache'
@ -14,6 +15,7 @@ export class UserUpdateMutation {
constructor(
private readonly updateService: UserUpdateService,
private readonly userService: UserService,
private readonly idService: IdService,
) {
}
@ -28,12 +30,12 @@ export class UserUpdateMutation {
throw new Error('cannot update your own user')
}
const user = await this.userService.findById(input.id)
const user = await this.userService.findById(this.idService.decode(input.id))
await this.updateService.update(user, input)
cache.add(cache.getCacheKey(UserEntity.name, user.id), user)
return new UserModel(user)
return new UserModel(this.idService.encode(user.id), user)
}
}

View File

@ -3,13 +3,16 @@ import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { FormCreateInput } from '../../dto/form/form.create.input'
import { FormEntity } from '../../entity/form.entity'
import { PageEntity } from '../../entity/page.entity'
import { UserEntity } from '../../entity/user.entity'
import { FormPageCreateService } from './form.page.create.service'
@Injectable()
export class FormCreateService {
constructor(
@InjectRepository(FormEntity)
private readonly formRepository: Repository<FormEntity>
private readonly formRepository: Repository<FormEntity>,
private readonly formPageCreateService: FormPageCreateService,
) {
}
@ -23,9 +26,14 @@ export class FormCreateService {
form.language = input.language || 'en'
form.design.layout = input.layout
form.endPage = this.formPageCreateService.create(input.endPage)
form.startPage = this.formPageCreateService.create(input.startPage)
form.admin = admin
return await this.formRepository.save(form)
}
}

View File

@ -14,15 +14,13 @@ export class FormDeleteService {
) {
}
async delete(id: string): Promise<void> {
await this.submissionRepository.createQueryBuilder('s')
.delete()
.where('s.form = :form', { form: id })
.execute()
async delete(id: number): Promise<void> {
await this.submissionRepository.delete({
form: new FormEntity({ id }),
})
await this.formRepository.createQueryBuilder('f')
.delete()
.where('f.id = :form', { form: id })
.execute()
await this.formRepository.delete({
id,
})
}
}

View File

@ -0,0 +1,44 @@
import { Injectable } from '@nestjs/common'
import { PageInput } from '../../dto/form/page.input'
import { PageButtonEntity } from '../../entity/page.button.entity'
import { PageEntity } from '../../entity/page.entity'
import { IdService } from '../id.service'
@Injectable()
export class FormPageCreateService {
constructor(
private readonly idService: IdService,
) {
}
public create(input: PageInput): PageEntity {
const page = new PageEntity()
page.show = Boolean(input?.show)
page.buttons = []
if (!input) {
return page
}
page.title = input.title
page.buttonText = input.buttonText
page.paragraph = input.paragraph
if (input.buttons !== undefined) {
page.buttons = input.buttons.map(buttonInput => {
const button = new PageButtonEntity()
button.page = page
button.url = buttonInput.url
button.action = buttonInput.action
button.text = buttonInput.text
button.color = buttonInput.color
button.bgColor = buttonInput.bgColor
button.activeColor = buttonInput.activeColor
return button
})
}
return page
}
}

View File

@ -0,0 +1,73 @@
import { Injectable } from '@nestjs/common'
import { PageInput } from '../../dto/form/page.input'
import { PageButtonEntity } from '../../entity/page.button.entity'
import { PageEntity } from '../../entity/page.entity'
import { IdService } from '../id.service'
@Injectable()
export class FormPageUpdateService {
constructor(
private readonly idService: IdService,
) {
}
public update(page: PageEntity, input: PageInput): PageEntity {
if (!page) {
page = new PageEntity()
page.show = false
}
if (input.show !== undefined) {
page.show = input.show
}
if (input.title !== undefined) {
page.title = input.title
}
if (input.paragraph !== undefined) {
page.paragraph = input.paragraph
}
if (input.buttonText !== undefined) {
page.buttonText = input.buttonText
}
if (input.buttons !== undefined) {
page.buttons = input.buttons.map(buttonInput => {
const entity = this.findByIdInList(
page?.buttons,
buttonInput.id,
new PageButtonEntity()
)
entity.page = page
entity.url = buttonInput.url
entity.action = buttonInput.action
entity.text = buttonInput.text
entity.color = buttonInput.color
entity.bgColor = buttonInput.bgColor
entity.activeColor = buttonInput.activeColor
return entity
})
}
return page
}
private findByIdInList<T extends { id: number }>(list: T[], id: string, fallback: T): T {
if (!list || /^NEW-/.test(id)) {
return fallback
}
const found = list.find((value) => value.id === this.idService.decode(id))
if (found) {
return found
}
return fallback
}
}

View File

@ -10,6 +10,8 @@ import { FormHookEntity } from '../../entity/form.hook.entity'
import { FormNotificationEntity } from '../../entity/form.notification.entity'
import { PageButtonEntity } from '../../entity/page.button.entity'
import { PageEntity } from '../../entity/page.entity'
import { IdService } from '../id.service'
import { FormPageUpdateService } from './form.page.update.service'
@Injectable()
export class FormUpdateService {
@ -20,6 +22,8 @@ export class FormUpdateService {
private readonly formFieldRepository: Repository<FormFieldEntity>,
@InjectRepository(FormHookEntity)
private readonly formHookRepository: Repository<FormHookEntity>,
private readonly idService: IdService,
private readonly pageService: FormPageUpdateService,
) {
}
@ -259,87 +263,11 @@ export class FormUpdateService {
*/
if (input.startPage !== undefined) {
if (!form.startPage) {
form.startPage = new PageEntity()
form.startPage.show = false
}
if (input.startPage.show !== undefined) {
form.startPage.show = input.startPage.show
}
if (input.startPage.title !== undefined) {
form.startPage.title = input.startPage.title
}
if (input.startPage.paragraph !== undefined) {
form.startPage.paragraph = input.startPage.paragraph
}
if (input.startPage.buttonText !== undefined) {
form.startPage.buttonText = input.startPage.buttonText
}
if (input.startPage.buttons !== undefined) {
form.startPage.buttons = input.startPage.buttons.map(buttonInput => {
const entity = this.findByIdInList(
form.startPage?.buttons,
buttonInput.id,
new PageButtonEntity()
)
entity.page = form.startPage
entity.url = buttonInput.url
entity.action = buttonInput.action
entity.text = buttonInput.text
entity.color = buttonInput.color
entity.bgColor = buttonInput.bgColor
entity.activeColor = buttonInput.activeColor
return entity
})
}
form.startPage = this.pageService.update(form.startPage, input.startPage)
}
if (input.endPage !== undefined) {
if (!form.endPage) {
form.endPage = new PageEntity()
form.endPage.show = false
}
if (input.endPage.show !== undefined) {
form.endPage.show = input.endPage.show
}
if (input.endPage.title !== undefined) {
form.endPage.title = input.endPage.title
}
if (input.endPage.paragraph !== undefined) {
form.endPage.paragraph = input.endPage.paragraph
}
if (input.endPage.buttonText !== undefined) {
form.endPage.buttonText = input.endPage.buttonText
}
if (input.endPage.buttons !== undefined) {
form.endPage.buttons = input.endPage.buttons.map(buttonInput => {
const entity = this.findByIdInList(
form.endPage?.buttons,
buttonInput.id,
new PageButtonEntity()
)
entity.page = form.endPage
entity.url = buttonInput.url
entity.action = buttonInput.action
entity.text = buttonInput.text
entity.color = buttonInput.color
entity.bgColor = buttonInput.bgColor
entity.activeColor = buttonInput.activeColor
return entity
})
}
form.endPage = this.pageService.update(form.endPage, input.endPage)
}
await this.formRepository.save(form)
@ -347,12 +275,12 @@ export class FormUpdateService {
return form
}
private findByIdInList<T>(list: T[], id: string, fallback: T): T {
if (!list) {
private findByIdInList<T extends { id: number }>(list: T[], id: string, fallback: T): T {
if (!list || /^NEW-/.test(id) || !id) {
return fallback
}
const found = list.find((value: any) => String(value.id) === String(id))
const found = list.find((value) => value.id === this.idService.decode(id))
if (found) {
return found

View File

@ -1,6 +1,8 @@
import { FormCreateService } from './form.create.service'
import { FormDeleteService } from './form.delete.service'
import { FormFieldService } from './form.field.service'
import { FormPageCreateService } from './form.page.create.service'
import { FormPageUpdateService } from './form.page.update.service'
import { FormService } from './form.service'
import { FormStatisticService } from './form.statistic.service'
import { FormUpdateService } from './form.update.service'
@ -9,6 +11,8 @@ export const formServices = [
FormCreateService,
FormDeleteService,
FormFieldService,
FormPageCreateService,
FormPageUpdateService,
FormService,
FormStatisticService,
FormUpdateService,

32
src/service/id.service.ts Normal file
View File

@ -0,0 +1,32 @@
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import Hashids from 'hashids'
@Injectable()
export class IdService {
private readonly hashids: Hashids
constructor(
readonly config: ConfigService
) {
this.hashids = new Hashids(config.get('SECRET_KEY'), 6)
}
public encode(id: number): string {
return this.hashids.encode([ id ])
}
public decode(raw: string): number {
if (!this.hashids.isValidId(raw)) {
throw new Error('invalid id passed')
}
const results: number[] = this.hashids.decode(raw) as number[]
if (results[0] === undefined) {
throw new Error('invalid id passed')
}
return results[0]
}
}

View File

@ -5,6 +5,7 @@ import Redis from 'ioredis'
import { PinoLogger } from 'nestjs-pino'
import { authServices } from './auth'
import { formServices } from './form'
import { IdService } from './id.service'
import { InstallationMetricsService } from './installation.metrics.service'
import { MailService } from './mail.service'
import { profileServices } from './profile'
@ -44,4 +45,5 @@ export const services = [
})
},
},
IdService,
]

View File

@ -25,12 +25,12 @@ export class SubmissionNotificationService {
try {
const to = this.getEmail(
submission,
notification.toField.id,
notification.toField?.id,
notification.toEmail
)
const from = this.getEmail(
submission,
notification.fromField.id,
notification.fromField?.id,
notification.fromEmail
)
@ -73,14 +73,18 @@ export class SubmissionNotificationService {
}
private getEmail(submission: SubmissionEntity, fieldId: number, fallback: string): string {
if (!fieldId) {
return fallback
}
const data = submission.fields.find(field => field.fieldId === fieldId)?.content
if (!data) {
return fallback
}
if (typeof data.value === 'string') {
return data.value
if (typeof data === 'string') {
return data
}
return fallback

View File

@ -58,7 +58,7 @@ export class SubmissionService {
return await qb.getManyAndCount()
}
async findById(id: string): Promise<SubmissionEntity> {
async findById(id: number): Promise<SubmissionEntity> {
const submission = await this.submissionRepository.findOne(id);
if (!submission) {

View File

@ -7,6 +7,7 @@ import { Repository } from 'typeorm'
import { SubmissionSetFieldInput } from '../../dto/submission/submission.set.field.input'
import { SubmissionEntity } from '../../entity/submission.entity'
import { SubmissionFieldContent, SubmissionFieldEntity } from '../../entity/submission.field.entity'
import { IdService } from '../id.service'
import { SubmissionHookService } from './submission.hook.service'
import { SubmissionNotificationService } from './submission.notification.service'
@ -19,13 +20,16 @@ export class SubmissionSetFieldService {
private readonly submissionFieldRepository: Repository<SubmissionFieldEntity>,
private readonly webHook: SubmissionHookService,
private readonly notifications: SubmissionNotificationService,
private readonly idService: IdService,
private readonly logger: PinoLogger,
) {
logger.setContext(this.constructor.name)
}
async saveField(submission: SubmissionEntity, input: SubmissionSetFieldInput): Promise<void> {
let field = submission.fields.find(field => field.field.id.toString() === input.field)
const formFieldId = this.idService.decode(input.field)
let field = submission.fields.find(field => field.field.id === formFieldId)
submission.timeElapsed = dayjs().diff(dayjs(submission.created), 'second')
@ -38,7 +42,7 @@ export class SubmissionSetFieldService {
field = new SubmissionFieldEntity()
field.submission = submission
field.field = submission.form.fields.find(field => field.id.toString() === input.field)
field.field = submission.form.fields.find(field => field.id === formFieldId)
field.type = field.field.type
field.content = this.parseData(field, input.data)
@ -84,7 +88,7 @@ export class SubmissionSetFieldService {
field: SubmissionFieldEntity,
data: string
): SubmissionFieldContent {
let raw: { [key: string]: unknown }
let raw: SubmissionFieldContent
const context = {
field: field.fieldId,
@ -92,21 +96,49 @@ export class SubmissionSetFieldService {
}
try {
raw = JSON.parse(data) as { [key: string]: unknown }
raw = JSON.parse(data) as SubmissionFieldContent
} catch (e) {
this.logger.warn(context, 'received invalid data for field')
return { value: null }
}
if (typeof raw !== 'object' || Array.isArray(raw)) {
this.logger.warn(context, 'only object supported for data')
return { value: null }
if (Array.isArray(raw)) {
return raw.map((row: unknown, index) => {
switch (typeof row) {
case 'number':
case 'string':
case 'boolean':
case 'undefined':
return row
}
if (row === null) {
return row
}
this.logger.warn({
...context,
path: `${index}`,
}, 'invalid data in array')
valid = false
return null
})
}
if (
[
'number',
'string',
'boolean',
'undefined',
].includes(typeof raw)
) {
return raw
}
// now ensure data structure
const result = {
value: null,
}
const result = {}
let valid = true
@ -147,6 +179,48 @@ export class SubmissionSetFieldService {
return
}
if (typeof value === 'object') {
result[String(key)] = {}
for (const subKey of Object.keys(value)) {
const subValue = raw[String(key)][String(subKey)]
switch (typeof subValue) {
case 'number':
case 'string':
case 'boolean':
result[String(key)][String(subKey)] = subValue
return
}
if (Array.isArray(subValue)) {
result[String(key)][String(subKey)] = subValue.map((row: unknown, index) => {
switch (typeof row) {
case 'number':
case 'string':
case 'boolean':
case 'undefined':
return row
}
if (row === null) {
return row
}
this.logger.warn({
...context,
path: `${key}/${subKey}/${index}`,
}, 'invalid data in array')
valid = false
return null
})
return
}
}
}
this.logger.warn({
...context,
path: String(key),

View File

@ -11,7 +11,7 @@ export class UserDeleteService {
) {
}
async delete(id: string): Promise<void> {
async delete(id: number): Promise<void> {
await this.userRepository.delete(id)
}
}

View File

@ -26,7 +26,7 @@ export class UserService {
return await qb.getManyAndCount()
}
async findById(id: string): Promise<UserEntity> {
async findById(id: number): Promise<UserEntity> {
const user = await this.userRepository.findOne(id);
if (!user) {

View File

@ -16,15 +16,15 @@ export class UserUpdateService {
}
async update(user: UserEntity, input: UserUpdateInput): Promise<UserEntity> {
if (input.firstName !== undefined) {
if (this.shouldUpdate(input, user, 'firstName')) {
user.firstName = input.firstName
}
if (input.lastName !== undefined) {
if (this.shouldUpdate(input, user, 'lastName')) {
user.lastName = input.lastName
}
if (input.email !== undefined) {
if (this.shouldUpdate(input, user, 'email')) {
user.email = input.email
user.emailVerified = false
// TODO request email verification
@ -34,15 +34,15 @@ export class UserUpdateService {
}
}
if (input.username !== undefined) {
if (this.shouldUpdate(input, user, 'username')) {
user.username = input.username
}
if (input.roles !== undefined) {
if (this.shouldUpdate(input, user, 'roles')) {
user.roles = input.roles as rolesType
}
if (input.language !== undefined) {
if (this.shouldUpdate(input, user, 'language')) {
user.language = input.language
}
@ -54,4 +54,16 @@ export class UserUpdateService {
return user
}
private shouldUpdate(
input: UserUpdateInput,
user: UserEntity,
property: keyof UserUpdateInput
): boolean {
if (input[property] === undefined) {
return false
}
return input[property] == user[property]
}
}

345
yarn.lock
View File

@ -532,6 +532,11 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@gar/promisify@^1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
"@graphql-tools/merge@8.2.2":
version "8.2.2"
resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.2.2.tgz#433566c662a33f5a9c3cc5f3ce3753fb0019477a"
@ -1070,6 +1075,22 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@npmcli/fs@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.0.tgz#f2a21c28386e299d1a9fae8051d35ad180e33109"
integrity sha512-DmfBvNXGaetMxj9LTp8NAN9vEidXURrf5ZTslQzEAi/6GbW+4yjaLFQc6Tue5cpZ9Frlk4OBo/Snf1Bh/S7qTQ==
dependencies:
"@gar/promisify" "^1.1.3"
semver "^7.3.5"
"@npmcli/move-file@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674"
integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==
dependencies:
mkdirp "^1.0.4"
rimraf "^3.0.2"
"@nuxtjs/opencollective@0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz#620ce1044f7ac77185e825e1936115bb38e2681c"
@ -1164,6 +1185,11 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
"@tsconfig/node10@^1.0.7":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
@ -1356,7 +1382,7 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/jest@27.4.1":
"@types/jest@^27.4.1":
version "27.4.1"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.1.tgz#185cbe2926eaaf9662d340cc02e548ce9e11ab6d"
integrity sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw==
@ -1837,6 +1863,23 @@ agent-base@6, agent-base@^6.0.0, agent-base@^6.0.2:
dependencies:
debug "4"
agentkeepalive@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717"
integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==
dependencies:
debug "^4.1.0"
depd "^1.1.2"
humanize-ms "^1.2.1"
aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
dependencies:
clean-stack "^2.0.0"
indent-string "^4.0.0"
ajv-formats@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
@ -2064,6 +2107,14 @@ are-we-there-yet@^2.0.0:
delegates "^1.0.0"
readable-stream "^3.6.0"
are-we-there-yet@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz#ba20bd6b553e31d62fc8c31bd23d22b95734390d"
integrity sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==
dependencies:
delegates "^1.0.0"
readable-stream "^3.6.0"
are-we-there-yet@~1.1.2:
version "1.1.7"
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146"
@ -2436,6 +2487,30 @@ bytes@3.1.1:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a"
integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==
cacache@^16.0.2:
version "16.0.3"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.0.3.tgz#0b6314bde969bd4098b03a5f90a351e8a1483f48"
integrity sha512-eC7wYodNCVb97kuHGk5P+xZsvUJHkhSEOyNwkenqQPAsOtrTjvWOE5vSPNBpz9d8X3acIf6w2Ub5s4rvOCTs4g==
dependencies:
"@npmcli/fs" "^2.1.0"
"@npmcli/move-file" "^1.1.2"
chownr "^2.0.0"
fs-minipass "^2.1.0"
glob "^7.2.0"
infer-owner "^1.0.4"
lru-cache "^7.7.1"
minipass "^3.1.6"
minipass-collect "^1.0.2"
minipass-flush "^1.0.5"
minipass-pipeline "^1.2.4"
mkdirp "^1.0.4"
p-map "^4.0.0"
promise-inflight "^1.0.1"
rimraf "^3.0.2"
ssri "^8.0.1"
tar "^6.1.11"
unique-filename "^1.1.1"
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
@ -2645,6 +2720,11 @@ clean-css@^4.2.1:
dependencies:
source-map "~0.6.0"
clean-stack@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
@ -2741,7 +2821,7 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-support@^1.1.2:
color-support@^1.1.2, color-support@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
@ -3093,7 +3173,7 @@ denque@^2.0.1:
resolved "https://registry.yarnpkg.com/denque/-/denque-2.0.1.tgz#bcef4c1b80dc32efe97515744f21a4229ab8934a"
integrity sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==
depd@~1.1.2:
depd@^1.1.2, depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
@ -3350,6 +3430,13 @@ encoding-japanese@1.0.30:
resolved "https://registry.yarnpkg.com/encoding-japanese/-/encoding-japanese-1.0.30.tgz#537c4d62881767925d601acb4c79fb14db81703a"
integrity sha512-bd/DFLAoJetvv7ar/KIpE3CNO8wEuyrt9Xuw6nSMiZ+Vrz/Q21BPsMHvARL2Wz6IKHKXgb+DWZqtRg1vql9cBg==
encoding@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
dependencies:
iconv-lite "^0.6.2"
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@ -3375,6 +3462,16 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
env-paths@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
err-code@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@ -4059,7 +4156,7 @@ fs-minipass@^1.2.7:
dependencies:
minipass "^2.6.0"
fs-minipass@^2.0.0:
fs-minipass@^2.0.0, fs-minipass@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
@ -4124,6 +4221,20 @@ gauge@^3.0.0:
strip-ansi "^6.0.1"
wide-align "^1.1.2"
gauge@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.3.tgz#286cf105c1962c659f0963058fb05116c1b82d3f"
integrity sha512-ICw1DhAwMtb22rYFwEHgJcx1JCwJGv3x6G0OQUq56Nge+H4Q8JEwr8iveS0XFlsUNSI67F5ffMGK25bK4Pmskw==
dependencies:
aproba "^1.0.3 || ^2.0.0"
color-support "^1.1.3"
console-control-strings "^1.1.0"
has-unicode "^2.0.1"
signal-exit "^3.0.7"
string-width "^4.2.3"
strip-ansi "^6.0.1"
wide-align "^1.1.5"
gauge@~2.7.3:
version "2.7.4"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
@ -4239,7 +4350,7 @@ glob@7.1.7:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
@ -4280,7 +4391,7 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graceful-fs@^4.2.9:
graceful-fs@^4.2.6, graceful-fs@^4.2.9:
version "4.2.9"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
@ -4392,6 +4503,11 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
hashids@^2.2.10:
version "2.2.10"
resolved "https://registry.yarnpkg.com/hashids/-/hashids-2.2.10.tgz#82f45538cf03ce73e31b78d1abe78d287cf760c4"
integrity sha512-nXnYums7F8B5Y+GSThutLPlKMaamW1yjWNZVt0WModiJfdjaDZHnhYTWblS+h1OoBx3yjwiBwxldPP3nIbFSSA==
he@1.2.0, he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@ -4502,6 +4618,11 @@ htmlparser2@^6.1.0:
domutils "^2.5.2"
entities "^2.0.0"
http-cache-semantics@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
http-errors@1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
@ -4522,6 +4643,15 @@ http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1:
agent-base "6"
debug "4"
http-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
dependencies:
"@tootallnate/once" "2"
agent-base "6"
debug "4"
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@ -4549,6 +4679,13 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=
dependencies:
ms "^2.0.0"
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -4563,7 +4700,7 @@ iconv-lite@0.6.2:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
iconv-lite@0.6.3, iconv-lite@^0.6.3:
iconv-lite@0.6.3, iconv-lite@^0.6.2, iconv-lite@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
@ -4613,6 +4750,16 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
indent-string@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
infer-owner@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@ -4839,6 +4986,11 @@ is-interactive@^1.0.0:
resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
is-lambda@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=
is-negative-zero@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"
@ -5923,6 +6075,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
lru-cache@^7.7.1:
version "7.7.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.7.1.tgz#03d2846b1ad2dcc7931a9340b8711d9798fcb0c6"
integrity sha512-cRffBiTW8s73eH4aTXqBcTLU0xQnwGV3/imttRHGWCrbergmnK4D6JXQd8qin5z43HnDwRI+o7mVW0LEB+tpAw==
macos-release@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.0.tgz#067c2c88b5f3fb3c56a375b2ec93826220fa1ff2"
@ -5971,6 +6128,28 @@ make-error@1.x, make-error@^1.1.1:
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
make-fetch-happen@^10.0.3:
version "10.1.0"
resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.1.0.tgz#7232ef7002a635c04d4deac325df144f21871408"
integrity sha512-HeP4QlkadP/Op+hE+Une1070kcyN85FshQObku3/rmzRh4zDcKXA19d2L3AQR6UoaX3uZmhSOpTLH15b1vOFvQ==
dependencies:
agentkeepalive "^4.2.1"
cacache "^16.0.2"
http-cache-semantics "^4.1.0"
http-proxy-agent "^5.0.0"
https-proxy-agent "^5.0.0"
is-lambda "^1.0.1"
lru-cache "^7.7.1"
minipass "^3.1.6"
minipass-collect "^1.0.2"
minipass-fetch "^2.0.3"
minipass-flush "^1.0.5"
minipass-pipeline "^1.2.4"
negotiator "^0.6.3"
promise-retry "^2.0.1"
socks-proxy-agent "^6.1.1"
ssri "^8.0.1"
makeerror@1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
@ -6074,6 +6253,45 @@ minimist@1.2.5, minimist@^1.2.0, minimist@^1.2.5:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
minipass-collect@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==
dependencies:
minipass "^3.0.0"
minipass-fetch@^2.0.3:
version "2.1.0"
resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.0.tgz#ca1754a5f857a3be99a9271277246ac0b44c3ff8"
integrity sha512-H9U4UVBGXEyyWJnqYDCLp1PwD8XIkJ4akNHp1aGVI+2Ym7wQMlxDKi4IB4JbmyU+pl9pEs/cVrK6cOuvmbK4Sg==
dependencies:
minipass "^3.1.6"
minipass-sized "^1.0.3"
minizlib "^2.1.2"
optionalDependencies:
encoding "^0.1.13"
minipass-flush@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373"
integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==
dependencies:
minipass "^3.0.0"
minipass-pipeline@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c"
integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==
dependencies:
minipass "^3.0.0"
minipass-sized@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70"
integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==
dependencies:
minipass "^3.0.0"
minipass@^2.6.0, minipass@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
@ -6082,7 +6300,7 @@ minipass@^2.6.0, minipass@^2.9.0:
safe-buffer "^5.1.2"
yallist "^3.0.0"
minipass@^3.0.0:
minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee"
integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==
@ -6096,7 +6314,7 @@ minizlib@^1.3.3:
dependencies:
minipass "^2.9.0"
minizlib@^2.1.1:
minizlib@^2.1.1, minizlib@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
@ -6468,7 +6686,7 @@ ms@2.1.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@2.1.3, ms@^2.1.1:
ms@2.1.3, ms@^2.0.0, ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@ -6551,6 +6769,11 @@ negotiator@0.6.2:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
negotiator@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
neo-async@^2.6.0, neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
@ -6625,6 +6848,22 @@ node-gyp@3.x:
tar "^2.0.0"
which "1"
node-gyp@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.0.0.tgz#e1da2067427f3eb5bb56820cb62bc6b1e4bd2089"
integrity sha512-Ma6p4s+XCTPxCuAMrOA/IJRmVy16R8Sdhtwl4PrCr7IBlj4cPawF0vg/l7nOT1jPbuNS7lIRJpBSvVsXwEZuzw==
dependencies:
env-paths "^2.2.0"
glob "^7.1.4"
graceful-fs "^4.2.6"
make-fetch-happen "^10.0.3"
nopt "^5.0.0"
npmlog "^6.0.0"
rimraf "^3.0.2"
semver "^7.3.5"
tar "^6.1.2"
which "^2.0.2"
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@ -6736,6 +6975,16 @@ npmlog@^5.0.1:
gauge "^3.0.0"
set-blocking "^2.0.0"
npmlog@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.1.tgz#06f1344a174c06e8de9c6c70834cfba2964bba17"
integrity sha512-BTHDvY6nrRHuRfyjt1MAufLxYdVXZfd099H4+i1f0lPywNQyI4foeNXJRObB/uy+TYqUW0vAD9gbdSOXPst7Eg==
dependencies:
are-we-there-yet "^3.0.0"
console-control-strings "^1.1.0"
gauge "^4.0.0"
set-blocking "^2.0.0"
nth-check@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2"
@ -6949,6 +7198,13 @@ p-map@^2.1.0:
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
p-map@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
dependencies:
aggregate-error "^3.0.0"
p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
@ -7336,6 +7592,19 @@ process-warning@^1.0.0:
resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616"
integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==
promise-inflight@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
promise-retry@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22"
integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==
dependencies:
err-code "^2.0.2"
retry "^0.12.0"
promise@^7.0.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
@ -7805,6 +8074,11 @@ retry@0.13.1:
resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==
retry@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
@ -8052,6 +8326,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af"
integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==
signal-exit@^3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
sisteransi@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
@ -8067,7 +8346,7 @@ slick@^1.12.2:
resolved "https://registry.yarnpkg.com/slick/-/slick-1.12.2.tgz#bd048ddb74de7d1ca6915faa4a57570b3550c2d7"
integrity sha1-vQSN23TefRymkV+qSldXCzVQwtc=
smart-buffer@^4.1.0:
smart-buffer@^4.1.0, smart-buffer@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
@ -8081,6 +8360,15 @@ socks-proxy-agent@5, socks-proxy-agent@^5.0.0:
debug "4"
socks "^2.3.3"
socks-proxy-agent@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87"
integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==
dependencies:
agent-base "^6.0.2"
debug "^4.3.1"
socks "^2.6.1"
socks@^2.3.3:
version "2.6.1"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e"
@ -8089,6 +8377,14 @@ socks@^2.3.3:
ip "^1.1.5"
smart-buffer "^4.1.0"
socks@^2.6.1:
version "2.6.2"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.2.tgz#ec042d7960073d40d94268ff3bb727dc685f111a"
integrity sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==
dependencies:
ip "^1.1.5"
smart-buffer "^4.2.0"
sonic-boom@^2.2.0, sonic-boom@^2.2.1:
version "2.4.2"
resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-2.4.2.tgz#34c0965b1a498abedaaca794c752d190f74b5e8f"
@ -8169,6 +8465,13 @@ sshpk@^1.7.0:
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
ssri@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af"
integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==
dependencies:
minipass "^3.1.1"
stack-utils@^2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"
@ -8442,7 +8745,7 @@ tar@^4:
safe-buffer "^5.2.1"
yallist "^3.1.1"
tar@^6.1.11:
tar@^6.1.11, tar@^6.1.2:
version "6.1.11"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
@ -8800,6 +9103,20 @@ unbox-primitive@^1.0.1:
has-symbols "^1.0.2"
which-boxed-primitive "^1.0.2"
unique-filename@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==
dependencies:
unique-slug "^2.0.0"
unique-slug@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==
dependencies:
imurmurhash "^0.1.4"
universalify@^0.1.0, universalify@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
@ -9050,14 +9367,14 @@ which@1:
dependencies:
isexe "^2.0.0"
which@^2.0.1:
which@^2.0.1, which@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
isexe "^2.0.0"
wide-align@^1.1.0, wide-align@^1.1.2:
wide-align@^1.1.0, wide-align@^1.1.2, wide-align@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==