HM-113. Добавление хуков для чтения/редактирования хранилищ (#1)
This commit is contained in:
19
http/hooks.http
Normal file
19
http/hooks.http
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
GET http://localhost:4001/hooks?key=rgfdfff HTTP/1.1
|
||||||
|
|
||||||
|
###
|
||||||
|
POST http://localhost:4001/hooks?key=rgfdfff HTTP/1.1
|
||||||
|
content-type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"holder": "john",
|
||||||
|
"description": "test token",
|
||||||
|
"author": "john smith",
|
||||||
|
"token": "sdf6sdfhs99-sdf",
|
||||||
|
"rights": {
|
||||||
|
"write": true,
|
||||||
|
"read": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
DELETE http://localhost:4001/hooks?key=rgfdfff&id=5f4a0347c3a82d5a571f058a HTTP/1.1
|
||||||
@ -6,6 +6,9 @@ Api-Name: store-service-test
|
|||||||
GET http://localhost:4001/store/testApi-4345345345 HTTP/1.1
|
GET http://localhost:4001/store/testApi-4345345345 HTTP/1.1
|
||||||
Api-Name: store-service-test
|
Api-Name: store-service-test
|
||||||
|
|
||||||
|
###
|
||||||
|
GET http://localhost:4001/store/rgfdfff?hook=sdf6sdfhs99-sdf HTTP/1.1
|
||||||
|
|
||||||
###
|
###
|
||||||
POST http://localhost:4001/store HTTP/1.1
|
POST http://localhost:4001/store HTTP/1.1
|
||||||
content-type: application/json
|
content-type: application/json
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {LogsService} from './logs/logs.service';
|
|||||||
import {LogsController} from './logs/logs.controller';
|
import {LogsController} from './logs/logs.controller';
|
||||||
import {ClientLog, ClientLogSchema, ServerLog, ServerLogSchema} from './logs/logs.schema';
|
import {ClientLog, ClientLogSchema, ServerLog, ServerLogSchema} from './logs/logs.schema';
|
||||||
import {AuthService} from './services/auth.service';
|
import {AuthService} from './services/auth.service';
|
||||||
|
import {HookTonesController} from './store/hookTokens.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -36,6 +37,7 @@ import {AuthService} from './services/auth.service';
|
|||||||
controllers: [
|
controllers: [
|
||||||
StoreController,
|
StoreController,
|
||||||
LogsController,
|
LogsController,
|
||||||
|
HookTonesController,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
StoreService,
|
StoreService,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ export const DB_TEST_NAME = 'store-service-test';
|
|||||||
export const DB_LOGGER = 'logger';
|
export const DB_LOGGER = 'logger';
|
||||||
export const MONGO_URL = 'mongodb://localhost:27017';
|
export const MONGO_URL = 'mongodb://localhost:27017';
|
||||||
export const COLLECTION_STORE = 'store';
|
export const COLLECTION_STORE = 'store';
|
||||||
|
export const COLLECTION_HOOKS = 'hooks';
|
||||||
export const COLLECTION_LOGS = 'logs';
|
export const COLLECTION_LOGS = 'logs';
|
||||||
export const LOG_TYPE = {
|
export const LOG_TYPE = {
|
||||||
CLIENT: 'client-logs',
|
CLIENT: 'client-logs',
|
||||||
|
|||||||
86
src/store/hookTokens.controller.ts
Normal file
86
src/store/hookTokens.controller.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import {Controller, Get, Header, Req, Post, Delete, HttpCode, Options, UseInterceptors} from '@nestjs/common';
|
||||||
|
import {ApiTags, ApiQuery, ApiBody, ApiResponse, ApiSecurity} from '@nestjs/swagger';
|
||||||
|
import {StoreService} from './store.service';
|
||||||
|
import {AuthService} from 'src/services/auth.service';
|
||||||
|
import {COLLECTION_HOOKS, ALLOW_ORIGIN_ALL, ALLOW_METHOD, ALLOW_CREDENTIALS, CONTENT_LENGTH, ALLOW_HEADERS} from 'src/consts';
|
||||||
|
import {Request} from 'express';
|
||||||
|
import {makeApiHeader} from './utils';
|
||||||
|
import {HookTokenMap, HookTokenResponse} from './store.schema';
|
||||||
|
import {FIND_ALL_SUCCESS, FIND_ALL_NOT_FOUND, CREATE_TOKEN_SUCCESS, CREATE_TOKEN_NOT_VALID, CREATE_TOKEN_CONFLICT, DELETE_TOKEN_SUCCESS, DELETE_TOKEN_NOT_FOUND} from './hookTokens.responses';
|
||||||
|
import {LoggingInterceptor} from 'src/logs/logging.interceptor';
|
||||||
|
|
||||||
|
@ApiSecurity('apiKey')
|
||||||
|
@UseInterceptors(LoggingInterceptor)
|
||||||
|
@Controller(COLLECTION_HOOKS)
|
||||||
|
@ApiTags(COLLECTION_HOOKS)
|
||||||
|
export class HookTonesController {
|
||||||
|
constructor(
|
||||||
|
private readonly storeService: StoreService,
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@Header(...ALLOW_ORIGIN_ALL)
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'key',
|
||||||
|
description: 'Ключ хранилища'
|
||||||
|
})
|
||||||
|
@ApiResponse(FIND_ALL_SUCCESS)
|
||||||
|
@ApiResponse(FIND_ALL_NOT_FOUND)
|
||||||
|
async findAll(@Req() request: Request<null, null, null, {key: string}>): Promise<HookTokenResponse[]> {
|
||||||
|
await this.authService.checkRequest(request);
|
||||||
|
const api = makeApiHeader(request);
|
||||||
|
const {key} = request?.query ?? {};
|
||||||
|
return await this.storeService.findApiTokens(api, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@Header(...ALLOW_ORIGIN_ALL)
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'key',
|
||||||
|
description: 'Ключ хранилища'
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
type: HookTokenMap,
|
||||||
|
description: 'Объект создания токена'
|
||||||
|
})
|
||||||
|
@ApiResponse(CREATE_TOKEN_SUCCESS)
|
||||||
|
@ApiResponse(CREATE_TOKEN_NOT_VALID)
|
||||||
|
@ApiResponse(CREATE_TOKEN_CONFLICT)
|
||||||
|
async createToken(@Req() request: Request<null, HookTokenMap, null, {key: string}>): Promise<HookTokenResponse> {
|
||||||
|
await this.authService.checkRequest(request);
|
||||||
|
const api = makeApiHeader(request);
|
||||||
|
const {key} = request?.query ?? {};
|
||||||
|
return await this.storeService.createApiToken(api, key, request?.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete()
|
||||||
|
@Header(...ALLOW_ORIGIN_ALL)
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'key',
|
||||||
|
description: 'Ключ хранилища'
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'id',
|
||||||
|
description: 'Id токена'
|
||||||
|
})
|
||||||
|
@ApiResponse(DELETE_TOKEN_SUCCESS)
|
||||||
|
@ApiResponse(DELETE_TOKEN_NOT_FOUND)
|
||||||
|
async deleteToken(@Req() request: Request<null, null, null, {key: string, id: string}>): Promise<HookTokenResponse> {
|
||||||
|
await this.authService.checkRequest(request);
|
||||||
|
const api = makeApiHeader(request);
|
||||||
|
const {key, id} = request?.query ?? {};
|
||||||
|
return await this.storeService.deleteApiToken(api, key, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Options('')
|
||||||
|
@Header(...ALLOW_ORIGIN_ALL)
|
||||||
|
@Header(...ALLOW_METHOD)
|
||||||
|
@Header(...ALLOW_CREDENTIALS)
|
||||||
|
@Header(...CONTENT_LENGTH)
|
||||||
|
@Header(...ALLOW_HEADERS)
|
||||||
|
@HttpCode(204)
|
||||||
|
async optionsKey(): Promise<string> {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/store/hookTokens.responses.ts
Normal file
56
src/store/hookTokens.responses.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import {ApiResponseOptions, ApiProperty} from '@nestjs/swagger';
|
||||||
|
import {HookTokenResponse} from './store.schema';
|
||||||
|
|
||||||
|
class Error {
|
||||||
|
@ApiProperty()
|
||||||
|
statusCode: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIND_ALL_SUCCESS: ApiResponseOptions = {
|
||||||
|
status: 200,
|
||||||
|
description: 'Список всех хуков хранилища',
|
||||||
|
type: HookTokenResponse,
|
||||||
|
isArray: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FIND_ALL_NOT_FOUND: ApiResponseOptions = {
|
||||||
|
status: 404,
|
||||||
|
description: 'Ошибка при попытке получить хуки для несуществующего хранилища',
|
||||||
|
type: Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CREATE_TOKEN_SUCCESS: ApiResponseOptions = {
|
||||||
|
status: 201,
|
||||||
|
description: 'Объект созданного хука',
|
||||||
|
type: HookTokenResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CREATE_TOKEN_NOT_VALID: ApiResponseOptions = {
|
||||||
|
status: 400,
|
||||||
|
description: 'Ошибка при попытке отправить хук с невалидными полями',
|
||||||
|
type: Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CREATE_TOKEN_CONFLICT: ApiResponseOptions = {
|
||||||
|
status: 409,
|
||||||
|
description: 'Ошибка при попытке создать хук с уже имеющимся именем в базе',
|
||||||
|
type: Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DELETE_TOKEN_SUCCESS: ApiResponseOptions = {
|
||||||
|
status: 200,
|
||||||
|
description: 'Удаляет хук из хранилища',
|
||||||
|
type: HookTokenResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DELETE_TOKEN_NOT_FOUND: ApiResponseOptions = {
|
||||||
|
status: 404,
|
||||||
|
description: 'Ошибка при попытке удалить несуществующий хук или обратится к несуществующем хранилищу',
|
||||||
|
type: Error,
|
||||||
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import {Controller, Get, Req, Post, Options, Header, Delete, HttpCode, Put, UseInterceptors} from '@nestjs/common';
|
import {Controller, Get, Req, Post, Options, Header, Delete, HttpCode, Put, UseInterceptors} from '@nestjs/common';
|
||||||
import {StoreService} from './store.service';
|
import {StoreService} from './store.service';
|
||||||
import {Store, StoreRequest} from './store.schema';
|
import {Store, StoreRequest} from './store.schema';
|
||||||
import {ApiResponse, ApiTags, ApiParam, ApiBody, ApiBearerAuth, ApiSecurity} from '@nestjs/swagger';
|
import {ApiResponse, ApiTags, ApiParam, ApiBody, ApiBearerAuth, ApiSecurity, ApiQuery} from '@nestjs/swagger';
|
||||||
import {ALLOW_ORIGIN_ALL, ALLOW_METHOD, ALLOW_CREDENTIALS, CONTENT_LENGTH, ALLOW_HEADERS, COLLECTION_STORE} from 'src/consts';
|
import {ALLOW_ORIGIN_ALL, ALLOW_METHOD, ALLOW_CREDENTIALS, CONTENT_LENGTH, ALLOW_HEADERS, COLLECTION_STORE} from 'src/consts';
|
||||||
import {Request} from 'express';
|
import {Request} from 'express';
|
||||||
import {LoggingInterceptor} from 'src/logs/logging.interceptor';
|
import {LoggingInterceptor} from 'src/logs/logging.interceptor';
|
||||||
@ -19,6 +19,7 @@ import {
|
|||||||
REMOVE_NOT_FOUND,
|
REMOVE_NOT_FOUND,
|
||||||
} from './store.responses';
|
} from './store.responses';
|
||||||
import {AuthService} from 'src/services/auth.service';
|
import {AuthService} from 'src/services/auth.service';
|
||||||
|
import {makeApiHeader} from './utils';
|
||||||
|
|
||||||
const prepareStoreToStoreRequest = ({
|
const prepareStoreToStoreRequest = ({
|
||||||
key, value, description, service_name, author
|
key, value, description, service_name, author
|
||||||
@ -26,11 +27,6 @@ const prepareStoreToStoreRequest = ({
|
|||||||
key, value, description, service_name, author,
|
key, value, description, service_name, author,
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeApiHeader = (request: Request): string => {
|
|
||||||
const apiHeader = request.headers?.['api-name'];
|
|
||||||
return typeof apiHeader === 'string' ? apiHeader : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
@ApiSecurity('apiKey')
|
@ApiSecurity('apiKey')
|
||||||
@UseInterceptors(LoggingInterceptor)
|
@UseInterceptors(LoggingInterceptor)
|
||||||
@Controller(COLLECTION_STORE)
|
@Controller(COLLECTION_STORE)
|
||||||
@ -61,11 +57,20 @@ export class StoreController {
|
|||||||
name: 'key',
|
name: 'key',
|
||||||
description: 'Ключ для поиска хранилища',
|
description: 'Ключ для поиска хранилища',
|
||||||
})
|
})
|
||||||
async findOne(@Req() request: Request<{key: string}>): Promise<StoreRequest> {
|
@ApiQuery({
|
||||||
await this.authService.checkRequest(request);
|
name: 'hook',
|
||||||
|
description: 'Хук для получения доступа без авторизации',
|
||||||
const {key} = request.params;
|
required: false,
|
||||||
|
})
|
||||||
|
async findOne(@Req() request: Request<{key: string}, null, null, {hook: string}>): Promise<StoreRequest> {
|
||||||
const api = makeApiHeader(request);
|
const api = makeApiHeader(request);
|
||||||
|
const {key} = request?.params ?? {};
|
||||||
|
const {hook} = request?.query ?? {};
|
||||||
|
const isActualHook = await this.storeService.checkHook(api, key, hook, ['read']);
|
||||||
|
if (!isActualHook) {
|
||||||
|
await this.authService.checkRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
const store = await this.storeService.findOne(api, key);
|
const store = await this.storeService.findOne(api, key);
|
||||||
return prepareStoreToStoreRequest(store);
|
return prepareStoreToStoreRequest(store);
|
||||||
}
|
}
|
||||||
@ -79,7 +84,7 @@ export class StoreController {
|
|||||||
type: StoreRequest,
|
type: StoreRequest,
|
||||||
description: 'Объект для создания хранилища'
|
description: 'Объект для создания хранилища'
|
||||||
})
|
})
|
||||||
async create(@Req() request: Request<null, StoreRequest>): Promise<StoreRequest> {
|
async create(@Req() request: Request<null, null, StoreRequest>): Promise<StoreRequest> {
|
||||||
const api = makeApiHeader(request);
|
const api = makeApiHeader(request);
|
||||||
const store = await this.storeService.create(api, request.body, request.headers.authorization);
|
const store = await this.storeService.create(api, request.body, request.headers.authorization);
|
||||||
return prepareStoreToStoreRequest(store);
|
return prepareStoreToStoreRequest(store);
|
||||||
@ -94,8 +99,20 @@ export class StoreController {
|
|||||||
type: StoreRequest,
|
type: StoreRequest,
|
||||||
description: 'Объект для обновления хранилища'
|
description: 'Объект для обновления хранилища'
|
||||||
})
|
})
|
||||||
async update(@Req() request: Request<null, StoreRequest>): Promise<StoreRequest> {
|
@ApiQuery({
|
||||||
|
name: 'hook',
|
||||||
|
description: 'Хук для получения доступа без авторизации',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
async update(@Req() request: Request<null, null, StoreRequest, {hook: string}>): Promise<StoreRequest> {
|
||||||
const api = makeApiHeader(request);
|
const api = makeApiHeader(request);
|
||||||
|
const {hook} = request?.query ?? {};
|
||||||
|
const {key} = request?.body;
|
||||||
|
const isActualHook = await this.storeService.checkHook(api, key, hook, ['write', 'read']);
|
||||||
|
if (!isActualHook) {
|
||||||
|
await this.authService.checkRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
const store = await this.storeService.update(api, request.body);
|
const store = await this.storeService.update(api, request.body);
|
||||||
return prepareStoreToStoreRequest(store);
|
return prepareStoreToStoreRequest(store);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,50 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
|||||||
import { Document } from 'mongoose';
|
import { Document } from 'mongoose';
|
||||||
import {ApiProperty} from '@nestjs/swagger';
|
import {ApiProperty} from '@nestjs/swagger';
|
||||||
|
|
||||||
|
class HookRightMap {
|
||||||
|
@ApiProperty()
|
||||||
|
write: boolean;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
read: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RightType = keyof HookRightMap;
|
||||||
|
|
||||||
|
export class HookTokenResponse {
|
||||||
|
@ApiProperty()
|
||||||
|
holder: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
author: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
rights: HookRightMap;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HookTokenMap {
|
||||||
|
@ApiProperty()
|
||||||
|
holder: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
author: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
token: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
rights: HookRightMap;
|
||||||
|
}
|
||||||
|
|
||||||
export class StoreRequest {
|
export class StoreRequest {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
key: string;
|
key: string;
|
||||||
@ -17,8 +61,44 @@ export class StoreRequest {
|
|||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
author: string;
|
author: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
hook_tokens?: HookTokenResponse[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Schema()
|
||||||
|
export class HookToken extends Document {
|
||||||
|
@Prop({
|
||||||
|
type: String,
|
||||||
|
})
|
||||||
|
holder: string;
|
||||||
|
|
||||||
|
@Prop({
|
||||||
|
type: String,
|
||||||
|
})
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Prop({
|
||||||
|
type: String,
|
||||||
|
})
|
||||||
|
author: string;
|
||||||
|
|
||||||
|
@Prop({
|
||||||
|
type: String,
|
||||||
|
})
|
||||||
|
token: string;
|
||||||
|
|
||||||
|
@Prop({
|
||||||
|
type: {
|
||||||
|
write: Boolean,
|
||||||
|
read: Boolean,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
rights: HookRightMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HookTokenSchema = SchemaFactory.createForClass(HookToken);
|
||||||
|
|
||||||
@Schema()
|
@Schema()
|
||||||
export class Store extends Document {
|
export class Store extends Document {
|
||||||
@Prop({
|
@Prop({
|
||||||
@ -53,6 +133,11 @@ export class Store extends Document {
|
|||||||
type: String,
|
type: String,
|
||||||
})
|
})
|
||||||
author: string;
|
author: string;
|
||||||
|
|
||||||
|
@Prop({
|
||||||
|
type: [HookTokenSchema]
|
||||||
|
})
|
||||||
|
hook_tokens: HookToken[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StoreSchema = SchemaFactory.createForClass(Store);
|
export const StoreSchema = SchemaFactory.createForClass(Store);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {Model, Connection} from 'mongoose';
|
import {Model, Connection} from 'mongoose';
|
||||||
import {Injectable, NotFoundException, BadGatewayException, ConflictException, BadRequestException, HttpException, HttpService} from '@nestjs/common';
|
import {Injectable, NotFoundException, BadGatewayException, ConflictException, BadRequestException, HttpException, HttpService} from '@nestjs/common';
|
||||||
import {InjectConnection} from '@nestjs/mongoose';
|
import {InjectConnection} from '@nestjs/mongoose';
|
||||||
import {Store, StoreRequest, StoreSchema} from './store.schema';
|
import {Store, StoreRequest, StoreSchema, HookToken, HookTokenResponse, HookTokenMap, RightType} from './store.schema';
|
||||||
import {DB_TEST_NAME, DB_NAME, COLLECTION_STORE} from 'src/consts';
|
import {DB_TEST_NAME, DB_NAME, COLLECTION_STORE} from 'src/consts';
|
||||||
import * as jwt from 'jsonwebtoken';
|
import * as jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
@ -12,6 +12,23 @@ interface Token {
|
|||||||
exp: number;
|
exp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const prepareHook = (baseHook: HookToken): HookTokenResponse => ({
|
||||||
|
holder: baseHook.holder,
|
||||||
|
description: baseHook.description,
|
||||||
|
author: baseHook.author,
|
||||||
|
rights: baseHook.rights,
|
||||||
|
id: baseHook._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const prepareStore = (baseStore: Store): StoreRequest => ({
|
||||||
|
key: baseStore.key,
|
||||||
|
value: baseStore.value,
|
||||||
|
description: baseStore.description,
|
||||||
|
author: baseStore.author,
|
||||||
|
service_name: baseStore.service_name,
|
||||||
|
hook_tokens: baseStore.hook_tokens?.map(prepareHook),
|
||||||
|
})
|
||||||
|
|
||||||
const validateModel = async (store: Store) => {
|
const validateModel = async (store: Store) => {
|
||||||
try {
|
try {
|
||||||
await store.validate();
|
await store.validate();
|
||||||
@ -134,4 +151,84 @@ export class StoreService {
|
|||||||
|
|
||||||
throw new NotFoundException(`Not found api key "${key}"`);
|
throw new NotFoundException(`Not found api key "${key}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findApiTokens(api: string, key: string): Promise<HookTokenResponse[]> {
|
||||||
|
const searchStore = await this.storeModel(api).findOne({key});
|
||||||
|
if (!searchStore) {
|
||||||
|
throw new NotFoundException(`Not found api key "${key}"`);
|
||||||
|
}
|
||||||
|
return searchStore.hook_tokens?.map(prepareHook);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createApiToken(api: string, key: string, hook: HookTokenMap): Promise<HookTokenResponse> {
|
||||||
|
const searchStore = await this.storeModel(api).findOne({key});
|
||||||
|
|
||||||
|
if (!searchStore) {
|
||||||
|
throw new NotFoundException(`Not found api key "${key}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = {
|
||||||
|
...prepareStore(searchStore),
|
||||||
|
hook_tokens: (searchStore.hook_tokens || []).map(baseHook => {
|
||||||
|
const {id, ...omitHook} = prepareHook(baseHook);
|
||||||
|
return omitHook;
|
||||||
|
}).concat(hook),
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateStore = new (this.storeModel(api))(store);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await validateModel(updateStore);
|
||||||
|
} catch (e) {
|
||||||
|
if (e?.message?.includes('validation failed')) {
|
||||||
|
throw new BadRequestException(e.message);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
await searchStore.updateOne(store);
|
||||||
|
|
||||||
|
const updatedStore = await this.storeModel(api).findOne({key});
|
||||||
|
const oldTokenIds = searchStore.hook_tokens.map(hook => hook._id);
|
||||||
|
const newToken = updatedStore.hook_tokens.find(token => !oldTokenIds.includes(token));
|
||||||
|
|
||||||
|
return prepareHook(newToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteApiToken(api: string, key: string, id: string): Promise<HookTokenResponse> {
|
||||||
|
const searchStore = await this.storeModel(api).findOne({key});
|
||||||
|
|
||||||
|
if (!searchStore) {
|
||||||
|
throw new NotFoundException(`Not found api key "${key}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteToken = searchStore.hook_tokens?.find(token => token._id.toString() === id);
|
||||||
|
|
||||||
|
if (!deleteToken) {
|
||||||
|
throw new NotFoundException(`Not found token id "${id}" in api key "${key}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = {
|
||||||
|
...prepareStore(searchStore),
|
||||||
|
hook_tokens: (searchStore.hook_tokens || []).filter(token => token._id.toString() !== id),
|
||||||
|
};
|
||||||
|
|
||||||
|
await searchStore.updateOne(store);
|
||||||
|
|
||||||
|
return prepareHook(deleteToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkHook(api: string, key: string, hook: string, checkObject: RightType[]): Promise<boolean> {
|
||||||
|
const searchStore = await this.storeModel(api).findOne({key});
|
||||||
|
|
||||||
|
if (!searchStore) {
|
||||||
|
throw new NotFoundException(`Not found api key "${key}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = searchStore.hook_tokens?.find(baseToken => baseToken.token === hook);
|
||||||
|
|
||||||
|
const available = checkObject.every(right => token?.rights[right]);
|
||||||
|
|
||||||
|
return !!token && available;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
8
src/store/utils.ts
Normal file
8
src/store/utils.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import {Request} from 'express';
|
||||||
|
|
||||||
|
const apiName = 'api-name' as const;
|
||||||
|
|
||||||
|
export const makeApiHeader = (request: Request): string => {
|
||||||
|
const apiHeader = request.headers?.[apiName];
|
||||||
|
return typeof apiHeader === 'string' ? apiHeader : '';
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user