Skip to content

Commit

Permalink
Merge pull request #38 from EATSTEAK/feat/config_delete
Browse files Browse the repository at this point in the history
 `/config/delete` 엔드포인트 추가
  • Loading branch information
Twince authored Aug 20, 2022
2 parents f39ec57 + b5845ad commit 0a7d27e
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 25 deletions.
47 changes: 47 additions & 0 deletions packages/server/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,44 @@ paths:
success: false
error: 500
errorDescription: Internal error
/config/delete:
post:
tags:
- config
summary: 설정 삭제
description: 학부의 설정을 삭제합니다.
security:
- UserSessionAuth: [ ]
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ConfigDeleteRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required:
- success
- result
properties:
success:
type: boolean
result:
$ref: '#/components/schemas/ConfigDeleteRequest'
'500':
description: 내부 오류
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
success: false
error: 500
errorDescription: Internal error
components:
securitySchemes:
UserLoginAuth:
Expand Down Expand Up @@ -782,6 +820,15 @@ components:
description: (서비스 설정 전용) 서비스에서 사용하는 건물의 목록입니다. 건물 번호를 키로, `Building` 을 값으로 하는 맵입니다.
contact:
type: string
ConfigDeleteRequest:
title: ConfigDeleteRequest
description: 설정 정보를 삭제하고자 할 때 사용합니다.
type: object
required:
- id
properties:
id:
type: string
Building:
title: Building
description: 건물을 표현합니다.
Expand Down
85 changes: 62 additions & 23 deletions packages/server/src/config/data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
DeleteItemInput,
ExpressionAttributeNameMap,
ExpressionAttributeValueMap,
GetItemInput,
Expand All @@ -25,16 +26,16 @@ function toLockerSubsectionData(subsection: LockerSubsection): LockerSubsectionD

function fromLockerSectionData(data: LockerSectionData): LockerSection {
return {
subsections: data.s.L.map(subsectionData => fromLockerSubsectionData(subsectionData.M)),
disabled: data.d.L.map(disabled => disabled.S),
subsections: data.s.L.map((subsectionData) => fromLockerSubsectionData(subsectionData.M)),
disabled: data.d.L.map((disabled) => disabled.S),
height: parseInt(data.h?.N ?? '0')
};
}

function toLockerSectionData(section: LockerSection): LockerSectionData {
return {
s: { L: section.subsections.map(ss => ({ M: toLockerSubsectionData(ss) })) },
d: { L: section.disabled.map(d => ({ S: d })) },
s: { L: section.subsections.map((ss) => ({ M: toLockerSubsectionData(ss) })) },
d: { L: section.disabled.map((d) => ({ S: d })) },
h: { N: `${section.height}` }
};
}
Expand All @@ -46,14 +47,19 @@ function toBuildingData(building: Building): BuildingData {
l: {
M: Object.fromEntries(
Object.entries(building.lockers).map(([floor, lockerSectionMap]) => {
return [floor,
return [
floor,
{
M:
Object.fromEntries(
Object.entries(lockerSectionMap).map(([lockerName, section]) => [lockerName, { M: toLockerSectionData(section) }])
)
}];
}))
M: Object.fromEntries(
Object.entries(lockerSectionMap).map(([lockerName, section]) => [
lockerName,
{ M: toLockerSectionData(section) }
])
)
}
];
})
)
}
};
}
Expand All @@ -64,10 +70,15 @@ function fromBuildingData(data: BuildingData): Building {
name: data.n.S,
lockers: Object.fromEntries(
Object.entries(data.l.M).map(([floor, lockerSectionDataMap]) => {
return [floor,
return [
floor,
Object.fromEntries(
Object.entries(lockerSectionDataMap.M).map(([lockerName, sectionData]) => [lockerName, fromLockerSectionData(sectionData.M)])
)];
Object.entries(lockerSectionDataMap.M).map(([lockerName, sectionData]) => [
lockerName,
fromLockerSectionData(sectionData.M)
])
)
];
})
)
};
Expand Down Expand Up @@ -95,14 +106,20 @@ function fromConfigDao(dao: ConfigDao): Config {
function toServiceConfigDao(data: ServiceConfig): ServiceConfigDao {
return {
...toConfigDao(data),
b: { M: Object.fromEntries(Object.entries(data.buildings).map(([s, b]) => [s, { M: toBuildingData(b) }])) }
b: {
M: Object.fromEntries(
Object.entries(data.buildings).map(([s, b]) => [s, { M: toBuildingData(b) }])
)
}
};
}

function fromServiceConfigDao(dao: ServiceConfigDao): ServiceConfig {
return {
...fromConfigDao(dao),
buildings: Object.fromEntries(Object.entries(dao.b?.M ?? {}).map(([s, bd]) => [s, fromBuildingData(bd.M)]))
buildings: Object.fromEntries(
Object.entries(dao.b?.M ?? {}).map(([s, bd]) => [s, fromBuildingData(bd.M)])
)
};
}

Expand All @@ -120,7 +137,7 @@ function fromDepartmentConfigDao(dao: DepartmentConfigDao): DepartmentConfig {
};
}

export const queryConfig = async function(startsWith = ''): Promise<Array<Config>> {
export const queryConfig = async function (startsWith = ''): Promise<Array<Config>> {
let composedRes: Array<Config> = [];
const req: QueryInput = {
TableName,
Expand All @@ -144,13 +161,17 @@ export const queryConfig = async function(startsWith = ''): Promise<Array<Config
.promise();
composedRes = [
...composedRes,
...res.Items.map<Config>((v) => v.id.S === 'SERVICE' ? fromServiceConfigDao(v as unknown as ServiceConfigDao) : fromDepartmentConfigDao(v as unknown as DepartmentConfigDao))
...res.Items.map<Config>((v) =>
v.id.S === 'SERVICE'
? fromServiceConfigDao(v as unknown as ServiceConfigDao)
: fromDepartmentConfigDao(v as unknown as DepartmentConfigDao)
)
];
} while (res.LastEvaluatedKey);
return composedRes;
};

export const getConfig = async function(id: string): Promise<Config> {
export const getConfig = async function (id: string): Promise<Config> {
const req: GetItemInput = {
TableName,
Key: {
Expand All @@ -163,10 +184,12 @@ export const getConfig = async function(id: string): Promise<Config> {
throw new NotFoundError(`Cannot find config of id ${id}`);
}
const dao: ConfigDao = res.Item as unknown as ConfigDao;
return dao.id.S === 'SERVICE' ? fromServiceConfigDao(dao as ServiceConfigDao) : fromDepartmentConfigDao(dao as DepartmentConfigDao);
return dao.id.S === 'SERVICE'
? fromServiceConfigDao(dao as ServiceConfigDao)
: fromDepartmentConfigDao(dao as DepartmentConfigDao);
};

export const updateConfig = async function(config: ConfigUpdateRequest) {
export const updateConfig = async function (config: ConfigUpdateRequest) {
const attributes: ExpressionAttributeValueMap = {};
const attributeNames: ExpressionAttributeNameMap = {};
let updateExp = '';
Expand All @@ -185,7 +208,11 @@ export const updateConfig = async function(config: ConfigUpdateRequest) {
}
if ((config as ServiceConfigUpdateRequest).buildings) {
const buildings = (config as ServiceConfigUpdateRequest).buildings;
attributes[':buildings'] = { M: Object.fromEntries(Object.entries(buildings).map(([s, b]) => [s, { M: toBuildingData(b) }])) };
attributes[':buildings'] = {
M: Object.fromEntries(
Object.entries(buildings).map(([s, b]) => [s, { M: toBuildingData(b) }])
)
};
updateExp += `${updateExp ? ',' : 'SET'} b = :buildings`;
}
if ((config as DepartmentConfigUpdateRequest).contact) {
Expand All @@ -205,4 +232,16 @@ export const updateConfig = async function(config: ConfigUpdateRequest) {
};
await dynamoDB.updateItem(req).promise();
return config;
};
};

export const deleteConfig = async function (id: string): Promise<string> {
const req: DeleteItemInput = {
TableName,
Key: {
type: { S: 'config' },
id: { S: id }
}
};
await dynamoDB.deleteItem(req).promise();
return id;
};
61 changes: 61 additions & 0 deletions packages/server/src/config/handler/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { APIGatewayProxyHandler } from 'aws-lambda';
import type { JwtPayload } from 'jsonwebtoken';
import * as jwt from 'jsonwebtoken';
import { JWT_SECRET } from '../../env';
import { createResponse } from '../../common';
import { assertAccessible } from '../../auth/data';
import { deleteConfig } from '../data';
import { errorResponse, isResponsibleError, ResponsibleError } from '../../util/error';

export const deleteConfigHandler: APIGatewayProxyHandler = async (event) => {
const token = (event.headers.Authorization ?? '').replace('Bearer ', '');
let data: UserDeleteRequest;
try {
data = JSON.parse(event.body) as UserDeleteRequest;
} catch {
return createResponse(500, {
success: false,
error: 500,
errorDescription: 'Data body is malformed JSON'
});
}
if (!data || !data.id) {
return createResponse(500, {
success: false,
error: 500,
errorDescription: 'Internal error'
});
}
let payload: JwtPayload;
try {
payload = jwt.verify(token, JWT_SECRET) as JwtPayload;
} catch {
console.debug('malformed token');
return createResponse(401, {
success: false,
error: 401,
errorDescription: 'Unauthorized'
});
}
try {
const id = payload.aud as string;
await assertAccessible(id, token, true);
if (data.id.toUpperCase() === 'SERVICE')
return errorResponse(
new ResponsibleError(500, 'Internal error', "Can't delete SERVICE config.")
);
const res = await deleteConfig(data.id);
return createResponse(200, { success: true, result: res });
} catch (e) {
if (isResponsibleError(e)) {
return errorResponse(e as ResponsibleError);
}
console.error(e);
const res = {
success: false,
error: 500,
errorDescription: 'Internal error'
};
return createResponse(500, res);
}
};
6 changes: 4 additions & 2 deletions packages/server/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { updateUserHandler } from './user/handler/update';
import { batchDeleteUserHandler } from './user/handler/batch/delete';
import { batchPutUserHandler } from './user/handler/batch/put';
import { unclaimLockerHandler } from './locker/handler/unclaim';
import { deleteConfigHandler } from './config/handler/delete';

export const auth = {
ssuLoginHandler,
Expand All @@ -20,7 +21,8 @@ export const auth = {

export const config = {
getConfigHandler,
updateConfigHandler
updateConfigHandler,
deleteConfigHandler
};

export const locker = {
Expand All @@ -37,4 +39,4 @@ export const user = {
updateUserHandler,
batchDeleteUserHandler,
batchPutUserHandler
};
};
25 changes: 25 additions & 0 deletions packages/server/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ Resources:
Properties:
Path: /api/v1/config/update
Method: options
ConfigDelete:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /api/v1/config/delete
Method: options
LockerClaim:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Expand Down Expand Up @@ -198,6 +203,26 @@ Resources:
Properties:
Path: /api/v1/config/update
Method: post
ConfigDeleteFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: ./dist
Handler: handler.config.deleteConfigHandler
Runtime: nodejs14.x
Layers:
- !Ref DependenciesLayer
Policies:
- AWSLambdaExecute
- DynamoDBCrudPolicy:
TableName: !Ref TableName
Architectures:
- x86_64
Events:
ConfigDelete:
Type: Api
Properties:
Path: /api/v1/config/delete
Method: post
LockerClaimFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
Expand Down

0 comments on commit 0a7d27e

Please sign in to comment.