stop exposing internal ids and switch over to hashids
This commit is contained in:
parent
2d3589e13a
commit
fe3821ad42
@ -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
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/pipe/form/form.by.id.pipe.ts
Normal file
24
src/pipe/form/form.by.id.pipe.ts
Normal 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
3
src/pipe/form/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { FormByIdPipe } from './form.by.id.pipe'
|
||||||
|
|
||||||
|
export const formPipes = [FormByIdPipe]
|
||||||
9
src/pipe/index.ts
Normal file
9
src/pipe/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { formPipes } from './form'
|
||||||
|
import { submissionPipes } from './submission'
|
||||||
|
import { userPipes } from './user'
|
||||||
|
|
||||||
|
export const pipes = [
|
||||||
|
...formPipes,
|
||||||
|
...submissionPipes,
|
||||||
|
...userPipes,
|
||||||
|
]
|
||||||
3
src/pipe/submission/index.ts
Normal file
3
src/pipe/submission/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { SubmissionByIdPipe } from './submission.by.id.pipe'
|
||||||
|
|
||||||
|
export const submissionPipes = [SubmissionByIdPipe]
|
||||||
19
src/pipe/submission/submission.by.id.pipe.ts
Normal file
19
src/pipe/submission/submission.by.id.pipe.ts
Normal 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
3
src/pipe/user/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { UserByIdPipe } from './user.by.id.pipe'
|
||||||
|
|
||||||
|
export const userPipes = [UserByIdPipe]
|
||||||
19
src/pipe/user/user.by.id.pipe.ts
Normal file
19
src/pipe/user/user.by.id.pipe.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>(
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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
32
src/service/id.service.ts
Normal 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]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user