stop exposing internal ids and switch over to hashids

This commit is contained in:
Michael Schramm 2022-02-28 22:50:20 +01:00
parent 2d3589e13a
commit fe3821ad42
45 changed files with 274 additions and 97 deletions

View File

@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Security ### Security
- start using hashids to prevent insights into form ids
## [1.0.0] - 2022-02-28 ## [1.0.0] - 2022-02-28
### Added ### Added

View File

@ -51,6 +51,7 @@
"graphql-subscriptions": "^2.0.0", "graphql-subscriptions": "^2.0.0",
"graphql-tools": "^8.2.0", "graphql-tools": "^8.2.0",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"hashids": "^2.2.10",
"html-to-text": "^8.1.0", "html-to-text": "^8.1.0",
"inquirer": "^8.2.0", "inquirer": "^8.2.0",
"ioredis": "^4.28.5", "ioredis": "^4.28.5",

View File

@ -1,11 +1,13 @@
import { commands } from './command' import { commands } from './command'
import { guards } from './guard' import { guards } from './guard'
import { pipes } from './pipe'
import { resolvers } from './resolver' import { resolvers } from './resolver'
import { services } from './service' import { services } from './service'
export const providers = [ export const providers = [
...resolvers,
...commands, ...commands,
...services,
...guards, ...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') @ObjectType('Deleted')
export class DeletedModel { export class DeletedModel {
@Field() @Field(() => ID)
id: string id: string
constructor(id: string) { constructor(id: string) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,24 @@
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)
console.log({
id,
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> { export class ContextCache<A = any> {
private cache: { private cache: {
[key: string]: any [key: string]: any
} = {} } = {}
public getCacheKey(type: string, id: ID): string { public getCacheKey(type: string, id: number): string {
return `${type}:${id}` return `${type}:${id}`
} }

View File

@ -7,12 +7,14 @@ import { FormModel } from '../../dto/form/form.model'
import { FormEntity } from '../../entity/form.entity' import { FormEntity } from '../../entity/form.entity'
import { UserEntity } from '../../entity/user.entity' import { UserEntity } from '../../entity/user.entity'
import { FormCreateService } from '../../service/form/form.create.service' import { FormCreateService } from '../../service/form/form.create.service'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@Injectable() @Injectable()
export class FormCreateMutation { export class FormCreateMutation {
constructor( 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) 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 { Roles } from '../../decorator/roles.decorator'
import { User } from '../../decorator/user.decorator' import { User } from '../../decorator/user.decorator'
import { DeletedModel } from '../../dto/deleted.model' import { DeletedModel } from '../../dto/deleted.model'
import { FormEntity } from '../../entity/form.entity'
import { UserEntity } from '../../entity/user.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 { FormDeleteService } from '../../service/form/form.delete.service'
import { FormService } from '../../service/form/form.service' import { FormService } from '../../service/form/form.service'
import { IdService } from '../../service/id.service'
@Injectable() @Injectable()
export class FormDeleteMutation { export class FormDeleteMutation {
constructor( constructor(
private readonly deleteService: FormDeleteService, private readonly deleteService: FormDeleteService,
private readonly formService: FormService, private readonly formService: FormService,
private readonly idService: IdService,
) { ) {
} }
@ -19,16 +23,14 @@ export class FormDeleteMutation {
@Roles('admin') @Roles('admin')
async deleteForm( async deleteForm(
@User() user: UserEntity, @User() user: UserEntity,
@Args({ name: 'id', type: () => ID}) id: string, @Args('id', {type: () => ID}, FormByIdPipe) form: FormEntity,
): Promise<DeletedModel> { ): Promise<DeletedModel> {
const form = await this.formService.findById(id)
if (!form.isLive && !this.formService.isAdmin(form, user)) { if (!form.isLive && !this.formService.isAdmin(form, user)) {
throw new Error('invalid form') 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

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

View File

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

@ -11,12 +11,14 @@ import { UserModel } from '../../dto/user/user.model'
import { FormEntity } from '../../entity/form.entity' import { FormEntity } from '../../entity/form.entity'
import { UserEntity } from '../../entity/user.entity' import { UserEntity } from '../../entity/user.entity'
import { FormService } from '../../service/form/form.service' import { FormService } from '../../service/form/form.service'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@Resolver(() => FormModel) @Resolver(() => FormModel)
export class FormResolver { export class FormResolver {
constructor( constructor(
private readonly formService: FormService, private readonly formService: FormService,
private readonly idService: IdService,
) { ) {
} }
@ -26,7 +28,7 @@ export class FormResolver {
@Parent() parent: FormModel, @Parent() parent: FormModel,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<FormFieldModel[]> { ): 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) || [] return form.fields?.map(field => new FormFieldModel(field)).sort((a,b) => a.idx - b.idx) || []
} }
@ -37,7 +39,7 @@ export class FormResolver {
@Parent() parent: FormModel, @Parent() parent: FormModel,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<FormHookModel[]> { ): 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)) || [] return form.hooks?.map(hook => new FormHookModel(hook)) || []
} }
@ -49,7 +51,7 @@ export class FormResolver {
@Parent() parent: FormModel, @Parent() parent: FormModel,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<boolean> { ): 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)) { if (!this.formService.isAdmin(form, user)) {
throw new Error('no access to field') throw new Error('no access to field')
@ -65,7 +67,7 @@ export class FormResolver {
@Parent() parent: FormModel, @Parent() parent: FormModel,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<FormNotificationModel[]> { ): 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)) { if (!this.formService.isAdmin(form, user)) {
throw new Error('no access to field') throw new Error('no access to field')
@ -80,7 +82,7 @@ export class FormResolver {
@Parent() parent: FormModel, @Parent() parent: FormModel,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<DesignModel> { ): 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) return new DesignModel(form.design)
} }
@ -90,7 +92,7 @@ export class FormResolver {
@Parent() parent: FormModel, @Parent() parent: FormModel,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<PageModel> { ): Promise<PageModel> {
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 PageModel(form.startPage) return new PageModel(form.startPage)
} }
@ -100,7 +102,7 @@ export class FormResolver {
@Parent() parent: FormModel, @Parent() parent: FormModel,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<PageModel> { ): Promise<PageModel> {
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 PageModel(form.endPage) return new PageModel(form.endPage)
} }
@ -111,12 +113,12 @@ export class FormResolver {
@Parent() parent: FormModel, @Parent() parent: FormModel,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<UserModel> { ): 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) { if (!form.admin) {
return null 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 { UserEntity } from '../../entity/user.entity'
import { FormService } from '../../service/form/form.service' import { FormService } from '../../service/form/form.service'
import { FormUpdateService } from '../../service/form/form.update.service' import { FormUpdateService } from '../../service/form/form.update.service'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@Injectable() @Injectable()
@ -15,6 +16,7 @@ export class FormUpdateMutation {
constructor( constructor(
private readonly updateService: FormUpdateService, private readonly updateService: FormUpdateService,
private readonly formService: FormService, private readonly formService: FormService,
private readonly idService: IdService,
) { ) {
} }
@ -25,7 +27,7 @@ export class FormUpdateMutation {
@Args({ name: 'form', type: () => FormUpdateInput }) input: FormUpdateInput, @Args({ name: 'form', type: () => FormUpdateInput }) input: FormUpdateInput,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<FormModel> { ): 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)) { if (!form.isLive && !this.formService.isAdmin(form, user)) {
throw new Error('invalid form') throw new Error('invalid form')
@ -35,6 +37,6 @@ export class FormUpdateMutation {
cache.add(cache.getCacheKey(FormEntity.name, form.id), 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

@ -4,10 +4,16 @@ import { Roles } from '../../decorator/roles.decorator'
import { User } from '../../decorator/user.decorator' import { User } from '../../decorator/user.decorator'
import { ProfileModel } from '../../dto/profile/profile.model' import { ProfileModel } from '../../dto/profile/profile.model'
import { UserEntity } from '../../entity/user.entity' import { UserEntity } from '../../entity/user.entity'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@Injectable() @Injectable()
export class ProfileQuery { export class ProfileQuery {
constructor(
private readonly idService: IdService,
) {
}
@Query(() => ProfileModel) @Query(() => ProfileModel)
@Roles('user') @Roles('user')
public me( public me(
@ -16,6 +22,6 @@ export class ProfileQuery {
): ProfileModel { ): ProfileModel {
cache.add(cache.getCacheKey(UserEntity.name, user.id), user) 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 { ProfileModel } from '../../dto/profile/profile.model'
import { ProfileUpdateInput } from '../../dto/profile/profile.update.input' import { ProfileUpdateInput } from '../../dto/profile/profile.update.input'
import { UserEntity } from '../../entity/user.entity' import { UserEntity } from '../../entity/user.entity'
import { IdService } from '../../service/id.service'
import { ProfileUpdateService } from '../../service/profile/profile.update.service' import { ProfileUpdateService } from '../../service/profile/profile.update.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@ -12,6 +13,7 @@ import { ContextCache } from '../context.cache'
export class ProfileUpdateMutation { export class ProfileUpdateMutation {
constructor( constructor(
private readonly updateService: ProfileUpdateService, private readonly updateService: ProfileUpdateService,
private readonly idService: IdService,
) { ) {
} }
@ -26,7 +28,7 @@ export class ProfileUpdateMutation {
cache.add(cache.getCacheKey(UserEntity.name, user.id), user) cache.add(cache.getCacheKey(UserEntity.name, user.id), user)
return new ProfileModel(user) return new ProfileModel(this.idService.encode(user.id), user)
} }
@Mutation(() => ProfileModel) @Mutation(() => ProfileModel)
@ -40,6 +42,6 @@ export class ProfileUpdateMutation {
cache.add(cache.getCacheKey(UserEntity.name, user.id), user) cache.add(cache.getCacheKey(UserEntity.name, user.id), user)
return new ProfileModel(user) return new ProfileModel(this.idService.encode(user.id), user)
} }
} }

View File

@ -19,7 +19,7 @@ export class SubmissionFieldResolver {
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<FormFieldModel> { ): Promise<FormFieldModel> {
const submissionField = await cache.get<SubmissionFieldEntity>( const submissionField = await cache.get<SubmissionFieldEntity>(
cache.getCacheKey(SubmissionFieldEntity.name, parent.id) cache.getCacheKey(SubmissionFieldEntity.name, parent._id)
) )
const field = await cache.get<FormFieldEntity>( const field = await cache.get<FormFieldEntity>(

View File

@ -2,33 +2,31 @@ import { Injectable } from '@nestjs/common'
import { Args, Context, ID, Mutation } from '@nestjs/graphql' import { Args, Context, ID, Mutation } from '@nestjs/graphql'
import { User } from '../../decorator/user.decorator' import { User } from '../../decorator/user.decorator'
import { SubmissionProgressModel } from '../../dto/submission/submission.progress.model' import { SubmissionProgressModel } from '../../dto/submission/submission.progress.model'
import { SubmissionSetFieldInput } from '../../dto/submission/submission.set.field.input'
import { SubmissionEntity } from '../../entity/submission.entity' import { SubmissionEntity } from '../../entity/submission.entity'
import { UserEntity } from '../../entity/user.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 { SubmissionSetFieldService } from '../../service/submission/submission.set.field.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@Injectable() @Injectable()
export class SubmissionFinishMutation { export class SubmissionFinishMutation {
constructor( constructor(
private readonly submissionService: SubmissionService,
private readonly setFieldService: SubmissionSetFieldService, private readonly setFieldService: SubmissionSetFieldService,
private readonly idService: IdService,
) { ) {
} }
@Mutation(() => SubmissionProgressModel) @Mutation(() => SubmissionProgressModel)
async submissionFinish( async submissionFinish(
@User() user: UserEntity, @User() user: UserEntity,
@Args({ name: 'submission', type: () => ID }) id: string, @Args({ name: 'submission', type: () => ID }, SubmissionByIdPipe) submission: SubmissionEntity,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<SubmissionProgressModel> { ): Promise<SubmissionProgressModel> {
const submission = await this.submissionService.findById(id)
await this.setFieldService.finishSubmission(submission) await this.setFieldService.finishSubmission(submission)
cache.add(cache.getCacheKey(SubmissionEntity.name, submission.id), 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 { SubmissionModel } from '../../dto/submission/submission.model'
import { SubmissionPagerFilterInput } from '../../dto/submission/submission.pager.filter.input' import { SubmissionPagerFilterInput } from '../../dto/submission/submission.pager.filter.input'
import { SubmissionPagerModel } from '../../dto/submission/submission.pager.model' import { SubmissionPagerModel } from '../../dto/submission/submission.pager.model'
import { FormEntity } from '../../entity/form.entity'
import { SubmissionEntity } from '../../entity/submission.entity' import { SubmissionEntity } from '../../entity/submission.entity'
import { UserEntity } from '../../entity/user.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 { SubmissionService } from '../../service/submission/submission.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@Injectable() @Injectable()
export class SubmissionListQuery { export class SubmissionListQuery {
constructor( constructor(
private readonly formService: FormService,
private readonly submissionService: SubmissionService, private readonly submissionService: SubmissionService,
private readonly idService: IdService,
) { ) {
} }
@Query(() => SubmissionPagerModel) @Query(() => SubmissionPagerModel)
async listSubmissions( async listSubmissions(
@User() user: UserEntity, @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('start', {type: () => Int, defaultValue: 0, nullable: true}) start: number,
@Args('limit', {type: () => Int, defaultValue: 50, nullable: true}) limit: number, @Args('limit', {type: () => Int, defaultValue: 50, nullable: true}) limit: number,
@Args('filter', {type: () => SubmissionPagerFilterInput, defaultValue: new SubmissionPagerFilterInput()}) filter: SubmissionPagerFilterInput, @Args('filter', {type: () => SubmissionPagerFilterInput, defaultValue: new SubmissionPagerFilterInput()}) filter: SubmissionPagerFilterInput,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<SubmissionPagerModel> { ): Promise<SubmissionPagerModel> {
const form = await this.formService.findById(id)
const [submissions, total] = await this.submissionService.find( const [submissions, total] = await this.submissionService.find(
form, form,
start, start,
@ -42,7 +42,10 @@ export class SubmissionListQuery {
}) })
return new SubmissionPagerModel( return new SubmissionPagerModel(
submissions.map(submission => new SubmissionModel(submission)), submissions.map(submission => new SubmissionModel(
this.idService.encode(submission.id),
submission
)),
total, total,
limit, limit,
start, start,

View File

@ -4,8 +4,9 @@ import { User } from '../../decorator/user.decorator'
import { SubmissionModel } from '../../dto/submission/submission.model' import { SubmissionModel } from '../../dto/submission/submission.model'
import { SubmissionEntity } from '../../entity/submission.entity' import { SubmissionEntity } from '../../entity/submission.entity'
import { UserEntity } from '../../entity/user.entity' import { UserEntity } from '../../entity/user.entity'
import { SubmissionByIdPipe } from '../../pipe/submission/submission.by.id.pipe'
import { FormService } from '../../service/form/form.service' 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 { SubmissionTokenService } from '../../service/submission/submission.token.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@ -13,20 +14,18 @@ import { ContextCache } from '../context.cache'
export class SubmissionQuery { export class SubmissionQuery {
constructor( constructor(
private readonly formService: FormService, private readonly formService: FormService,
private readonly submissionService: SubmissionService,
private readonly tokenService: SubmissionTokenService, private readonly tokenService: SubmissionTokenService,
private readonly idService: IdService,
) { ) {
} }
@Query(() => SubmissionModel) @Query(() => SubmissionModel)
async getSubmissionById( async getSubmissionById(
@User() user: UserEntity, @User() user: UserEntity,
@Args('id', {type: () => ID}) id: string, @Args('id', {type: () => ID}, SubmissionByIdPipe) submission: SubmissionEntity,
@Args('token', {nullable: true}) token: string, @Args('token', {nullable: true}) token: string,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<SubmissionModel> { ): Promise<SubmissionModel> {
const submission = await this.submissionService.findById(id)
if ( if (
!await this.tokenService.verify(token, submission.tokenHash) !await this.tokenService.verify(token, submission.tokenHash)
&& !this.formService.isAdmin(submission.form, user) && !this.formService.isAdmin(submission.form, user)
@ -36,6 +35,6 @@ export class SubmissionQuery {
cache.add(cache.getCacheKey(SubmissionEntity.name, submission.id), submission) 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 { SubmissionEntity } from '../../entity/submission.entity'
import { SubmissionFieldEntity } from '../../entity/submission.field.entity' import { SubmissionFieldEntity } from '../../entity/submission.field.entity'
import { UserEntity } from '../../entity/user.entity' import { UserEntity } from '../../entity/user.entity'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@Resolver(() => SubmissionModel) @Resolver(() => SubmissionModel)
export class SubmissionResolver { export class SubmissionResolver {
constructor(
private readonly idService: IdService,
) {
}
@ResolveField(() => [SubmissionFieldModel]) @ResolveField(() => [SubmissionFieldModel])
async fields( async fields(
@User() user: UserEntity, @User() user: UserEntity,
@ -16,12 +22,12 @@ export class SubmissionResolver {
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<SubmissionFieldModel[]> { ): Promise<SubmissionFieldModel[]> {
const submission = await cache.get<SubmissionEntity>( const submission = await cache.get<SubmissionEntity>(
cache.getCacheKey(SubmissionEntity.name, parent.id) cache.getCacheKey(SubmissionEntity.name, parent._id)
) )
return submission.fields.map(field => { return submission.fields.map(field => {
cache.add(cache.getCacheKey(SubmissionFieldEntity.name, field.id), 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 { SubmissionSetFieldInput } from '../../dto/submission/submission.set.field.input'
import { SubmissionEntity } from '../../entity/submission.entity' import { SubmissionEntity } from '../../entity/submission.entity'
import { UserEntity } from '../../entity/user.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 { SubmissionService } from '../../service/submission/submission.service'
import { SubmissionSetFieldService } from '../../service/submission/submission.set.field.service' import { SubmissionSetFieldService } from '../../service/submission/submission.set.field.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@ -14,18 +16,17 @@ export class SubmissionSetFieldMutation {
constructor( constructor(
private readonly submissionService: SubmissionService, private readonly submissionService: SubmissionService,
private readonly setFieldService: SubmissionSetFieldService, private readonly setFieldService: SubmissionSetFieldService,
private readonly idService: IdService,
) { ) {
} }
@Mutation(() => SubmissionProgressModel) @Mutation(() => SubmissionProgressModel)
async submissionSetField( async submissionSetField(
@User() user: UserEntity, @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, @Args({ name: 'field', type: () => SubmissionSetFieldInput }) input: SubmissionSetFieldInput,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<SubmissionProgressModel> { ): Promise<SubmissionProgressModel> {
const submission = await this.submissionService.findById(id)
if (!await this.submissionService.isOwner(submission, input.token)) { if (!await this.submissionService.isOwner(submission, input.token)) {
throw new Error('no access to submission') throw new Error('no access to submission')
} }
@ -34,6 +35,6 @@ export class SubmissionSetFieldMutation {
cache.add(cache.getCacheKey(SubmissionEntity.name, submission.id), 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,9 +4,12 @@ import { IpAddress } from '../../decorator/ip.address.decorator'
import { User } from '../../decorator/user.decorator' import { User } from '../../decorator/user.decorator'
import { SubmissionProgressModel } from '../../dto/submission/submission.progress.model' import { SubmissionProgressModel } from '../../dto/submission/submission.progress.model'
import { SubmissionStartInput } from '../../dto/submission/submission.start.input' import { SubmissionStartInput } from '../../dto/submission/submission.start.input'
import { FormEntity } from '../../entity/form.entity'
import { SubmissionEntity } from '../../entity/submission.entity' import { SubmissionEntity } from '../../entity/submission.entity'
import { UserEntity } from '../../entity/user.entity' import { UserEntity } from '../../entity/user.entity'
import { FormByIdPipe } from '../../pipe/form/form.by.id.pipe'
import { FormService } from '../../service/form/form.service' import { FormService } from '../../service/form/form.service'
import { IdService } from '../../service/id.service'
import { SubmissionStartService } from '../../service/submission/submission.start.service' import { SubmissionStartService } from '../../service/submission/submission.start.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@ -15,19 +18,18 @@ export class SubmissionStartMutation {
constructor( constructor(
private readonly startService: SubmissionStartService, private readonly startService: SubmissionStartService,
private readonly formService: FormService, private readonly formService: FormService,
private readonly idService: IdService,
) { ) {
} }
@Mutation(() => SubmissionProgressModel) @Mutation(() => SubmissionProgressModel)
async submissionStart( async submissionStart(
@User() user: UserEntity, @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, @Args({ name: 'submission', type: () => SubmissionStartInput }) input: SubmissionStartInput,
@IpAddress() ipAddr: string, @IpAddress() ipAddr: string,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<SubmissionProgressModel> { ): Promise<SubmissionProgressModel> {
const form = await this.formService.findById(id)
if (!form.isLive && !this.formService.isAdmin(form, user)) { if (!form.isLive && !this.formService.isAdmin(form, user)) {
throw new Error('invalid form') throw new Error('invalid form')
} }
@ -36,6 +38,6 @@ export class SubmissionStartMutation {
cache.add(cache.getCacheKey(SubmissionEntity.name, submission.id), 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,12 +4,15 @@ import { Roles } from '../../decorator/roles.decorator'
import { User } from '../../decorator/user.decorator' import { User } from '../../decorator/user.decorator'
import { DeletedModel } from '../../dto/deleted.model' import { DeletedModel } from '../../dto/deleted.model'
import { UserEntity } from '../../entity/user.entity' 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' import { UserDeleteService } from '../../service/user/user.delete.service'
@Injectable() @Injectable()
export class UserDeleteMutation { export class UserDeleteMutation {
constructor( constructor(
private readonly deleteService: UserDeleteService, private readonly deleteService: UserDeleteService,
private readonly idService: IdService,
) { ) {
} }
@ -17,14 +20,14 @@ export class UserDeleteMutation {
@Roles('admin') @Roles('admin')
async deleteUser( async deleteUser(
@User() auth: UserEntity, @User() auth: UserEntity,
@Args({ name: 'id', type: () => ID}) id: string, @Args({ name: 'id', type: () => ID}, UserByIdPipe) user: UserEntity,
): Promise<DeletedModel> { ): Promise<DeletedModel> {
if (auth.id.toString() === id) { if (auth.id === user.id) {
throw new Error('cannot delete your own user') 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 { UserModel } from '../../dto/user/user.model'
import { UserPagerModel } from '../../dto/user/user.pager.model' import { UserPagerModel } from '../../dto/user/user.pager.model'
import { UserEntity } from '../../entity/user.entity' import { UserEntity } from '../../entity/user.entity'
import { IdService } from '../../service/id.service'
import { UserService } from '../../service/user/user.service' import { UserService } from '../../service/user/user.service'
import { ContextCache } from '../context.cache' import { ContextCache } from '../context.cache'
@ -10,6 +11,7 @@ import { ContextCache } from '../context.cache'
export class UserListQuery { export class UserListQuery {
constructor( constructor(
private readonly userService: UserService, private readonly userService: UserService,
private readonly idService: IdService,
) { ) {
} }
@ -25,7 +27,7 @@ export class UserListQuery {
return new UserPagerModel( return new UserPagerModel(
entities.map(entity => { entities.map(entity => {
cache.add(cache.getCacheKey(UserEntity.name, entity.id), entity) cache.add(cache.getCacheKey(UserEntity.name, entity.id), entity)
return new UserModel(entity) return new UserModel(this.idService.encode(entity.id), entity)
}), }),
total, total,
limit, limit,

View File

@ -3,26 +3,25 @@ import { Args, Context, ID, Query } from '@nestjs/graphql'
import { Roles } from '../../decorator/roles.decorator' import { Roles } from '../../decorator/roles.decorator'
import { UserModel } from '../../dto/user/user.model' import { UserModel } from '../../dto/user/user.model'
import { UserEntity } from '../../entity/user.entity' 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' import { ContextCache } from '../context.cache'
@Injectable() @Injectable()
export class UserQuery { export class UserQuery {
constructor( constructor(
private readonly userService: UserService, private readonly idService: IdService,
) { ) {
} }
@Query(() => UserModel) @Query(() => UserModel)
@Roles('admin') @Roles('admin')
public async getUserById( public getUserById(
@Args('id', {type: () => ID}) id: string, @Args('id', {type: () => ID}, UserByIdPipe) user: UserEntity,
@Context('cache') cache: ContextCache, @Context('cache') cache: ContextCache,
): Promise<UserModel> { ): UserModel {
const user = await this.userService.findById(id)
cache.add(cache.getCacheKey(UserEntity.name, user.id), user) 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, @Context('cache') cache: ContextCache,
): Promise<string[]> { ): Promise<string[]> {
return this.returnFieldForSuperuser( return this.returnFieldForSuperuser(
await cache.get<UserEntity>(cache.getCacheKey(UserEntity.name, parent.id)), await cache.get<UserEntity>(cache.getCacheKey(UserEntity.name, parent._id)),
user, user,
c => c.roles c => c.roles
) )

View File

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

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

View File

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

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) await this.userRepository.delete(id)
} }
} }

View File

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

View File

@ -4392,6 +4392,11 @@ has@^1.0.3:
dependencies: dependencies:
function-bind "^1.1.1" 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: he@1.2.0, he@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"