From e0b61196c92e1745d95a5740130a443771dae9dc Mon Sep 17 00:00:00 2001 From: Jason Park <93040528+Autogod@users.noreply.github.com> Date: Thu, 29 Dec 2022 20:40:43 +0900 Subject: [PATCH] feat: creating only database and not API (#225) * f:added Put method into default Headers for new api function put-timetable * feat: adding table and api * feat: for backup * feat: updated pnpm-lock * feat: initiating database creation * feat: redoing databases * feat: building database thread --- lib/configs/common/registry.ts | 6 +- lib/constructs/business/rest-api-service.ts | 561 +++++++++++++------- lib/constructs/common/lambda-functions.ts | 405 ++++++++++---- lib/constructs/persistence/database.ts | 149 ++++-- lib/stacks/persistence.ts | 43 +- pnpm-lock.yaml | 100 ++-- src/lambda/get-single-thread/index.py | 29 + src/lambda/get-single-thread/utils.py | 66 +++ src/lambda/get-threads/index.py | 20 + src/lambda/get-threads/utils.py | 66 +++ 10 files changed, 1064 insertions(+), 381 deletions(-) create mode 100644 src/lambda/get-single-thread/index.py create mode 100644 src/lambda/get-single-thread/utils.py create mode 100644 src/lambda/get-threads/index.py create mode 100644 src/lambda/get-threads/utils.py diff --git a/lib/configs/common/registry.ts b/lib/configs/common/registry.ts index ed03051e3..936047c89 100644 --- a/lib/configs/common/registry.ts +++ b/lib/configs/common/registry.ts @@ -1,7 +1,7 @@ export enum ServiceEndpoint { API_REST, AUTH, - API_GRAPHQL + API_GRAPHQL, } export const enum DataEndpoint { @@ -10,9 +10,11 @@ export const enum DataEndpoint { CAREER, TIMETABLE, COURSE, + THREAD, + COMMENT, } export enum OperationEndpoint { APP, - SYLLABUS + SYLLABUS, } diff --git a/lib/constructs/business/rest-api-service.ts b/lib/constructs/business/rest-api-service.ts index 6c6c62a3e..9274b6f8b 100644 --- a/lib/constructs/business/rest-api-service.ts +++ b/lib/constructs/business/rest-api-service.ts @@ -4,7 +4,11 @@ import * as iam from 'aws-cdk-lib/aws-iam'; import { Construct } from 'constructs'; import { allowHeaders, allowOrigins } from '../../configs/api-gateway/cors'; -import { lambdaRespParams, s3RespMapping, syllabusRespParams } from '../../configs/api-gateway/mapping'; +import { + lambdaRespParams, + s3RespMapping, + syllabusRespParams, +} from '../../configs/api-gateway/mapping'; import { courseReviewGetRespSchema, courseReviewPatchReqSchema, @@ -12,7 +16,11 @@ import { syllabusSchema, } from '../../configs/api-gateway/schema'; import { AwsServicePrincipal } from '../../configs/common/aws'; -import { CourseReviewsFunctions, SyllabusFunctions, TimetableFunctions } from '../common/lambda-functions'; +import { + CourseReviewsFunctions, + SyllabusFunctions, + TimetableFunctions, +} from '../common/lambda-functions'; import { AbstractRestApiEndpoint } from './api-endpoint'; export interface RestApiServiceProps { @@ -22,17 +30,29 @@ export interface RestApiServiceProps { } export class RestApiService extends Construct { - readonly resourceMapping: { [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method } }; - - constructor(scope: AbstractRestApiEndpoint, id: string, props: RestApiServiceProps) { + readonly resourceMapping: { + [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method }; + }; + + constructor( + scope: AbstractRestApiEndpoint, + id: string, + props: RestApiServiceProps, + ) { super(scope, id); } } export class SyllabusApiService extends RestApiService { - readonly resourceMapping: { [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method } }; - - constructor(scope: AbstractRestApiEndpoint, id: string, props: RestApiServiceProps) { + readonly resourceMapping: { + [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method }; + }; + + constructor( + scope: AbstractRestApiEndpoint, + id: string, + props: RestApiServiceProps, + ) { super(scope, id, props); const root = scope.apiEndpoint.root.addResource('syllabus'); @@ -49,108 +69,147 @@ export class SyllabusApiService extends RestApiService { const apiGatewayRole = new iam.Role(this, 'rest-api-s3', { assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.API_GATEWAY), description: 'Allow API Gateway to fetch objects from s3 buckets.', - path: `/service-role/${ AwsServicePrincipal.API_GATEWAY }/`, + path: `/service-role/${AwsServicePrincipal.API_GATEWAY}/`, roleName: 's3-apigateway-read', - managedPolicies: [iam.ManagedPolicy.fromManagedPolicyArn(this, 's3-read-only', - 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess')], + managedPolicies: [ + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 's3-read-only', + 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess', + ), + ], }); - const getIntegration = new apigw.AwsIntegration( - { - service: 's3', - integrationHttpMethod: apigw2.HttpMethod.GET, - path: 'syllabus/{school}.json', - subdomain: props.dataSource, - options: { - credentialsRole: apiGatewayRole, - requestParameters: { ['integration.request.path.school']: 'method.request.path.school' }, - integrationResponses: [{ + const getIntegration = new apigw.AwsIntegration({ + service: 's3', + integrationHttpMethod: apigw2.HttpMethod.GET, + path: 'syllabus/{school}.json', + subdomain: props.dataSource, + options: { + credentialsRole: apiGatewayRole, + requestParameters: { + ['integration.request.path.school']: 'method.request.path.school', + }, + integrationResponses: [ + { statusCode: '200', responseParameters: s3RespMapping, - }], - }, + }, + ], }, - ); + }); - const headIntegration = new apigw.AwsIntegration( - { - service: 's3', - integrationHttpMethod: apigw2.HttpMethod.HEAD, - path: 'syllabus/{school}.json', - subdomain: props.dataSource, - options: { - credentialsRole: apiGatewayRole, - requestParameters: { ['integration.request.path.school']: 'method.request.path.school' }, - integrationResponses: [{ + const headIntegration = new apigw.AwsIntegration({ + service: 's3', + integrationHttpMethod: apigw2.HttpMethod.HEAD, + path: 'syllabus/{school}.json', + subdomain: props.dataSource, + options: { + credentialsRole: apiGatewayRole, + requestParameters: { + ['integration.request.path.school']: 'method.request.path.school', + }, + integrationResponses: [ + { statusCode: '200', responseParameters: s3RespMapping, - }], - }, + }, + ], }, - ); + }); const syllabusFunctions = new SyllabusFunctions(this, 'syllabus-function'); const courseGetIntegration = new apigw.LambdaIntegration( - syllabusFunctions.getFunction, { proxy: true }, + syllabusFunctions.getFunction, + { proxy: true }, ); const bookPostIntegration = new apigw.LambdaIntegration( - syllabusFunctions.postFunction, { proxy: true }, + syllabusFunctions.postFunction, + { proxy: true }, ); const optionsSyllabusSchools = syllabusSchools.addCorsPreflight({ allowOrigins: allowOrigins, allowHeaders: allowHeaders, - allowMethods: [apigw2.HttpMethod.GET, apigw2.HttpMethod.OPTIONS, apigw2.HttpMethod.HEAD], - }); - const getSyllabusSchools = syllabusSchools.addMethod(apigw2.HttpMethod.GET, getIntegration, { - requestParameters: { ['method.request.path.school']: true }, - operationName: 'GetSyllabusBySchool', - methodResponses: [{ - statusCode: '200', - responseModels: { ['application/json']: getRespModel }, - responseParameters: syllabusRespParams, - }], - requestValidator: props.validator, - }); - const headSyllabusSchools = syllabusSchools.addMethod(apigw2.HttpMethod.HEAD, headIntegration, { - requestParameters: { ['method.request.path.school']: true }, - operationName: 'GetSyllabusMetadataBySchool', - methodResponses: [{ - statusCode: '200', - responseParameters: syllabusRespParams, - }], - requestValidator: props.validator, + allowMethods: [ + apigw2.HttpMethod.GET, + apigw2.HttpMethod.OPTIONS, + apigw2.HttpMethod.HEAD, + ], }); + const getSyllabusSchools = syllabusSchools.addMethod( + apigw2.HttpMethod.GET, + getIntegration, + { + requestParameters: { ['method.request.path.school']: true }, + operationName: 'GetSyllabusBySchool', + methodResponses: [ + { + statusCode: '200', + responseModels: { ['application/json']: getRespModel }, + responseParameters: syllabusRespParams, + }, + ], + requestValidator: props.validator, + }, + ); + const headSyllabusSchools = syllabusSchools.addMethod( + apigw2.HttpMethod.HEAD, + headIntegration, + { + requestParameters: { ['method.request.path.school']: true }, + operationName: 'GetSyllabusMetadataBySchool', + methodResponses: [ + { + statusCode: '200', + responseParameters: syllabusRespParams, + }, + ], + requestValidator: props.validator, + }, + ); const optionsSyllabusCourse = root.addCorsPreflight({ allowOrigins: allowOrigins, allowHeaders: allowHeaders, allowMethods: [apigw2.HttpMethod.GET, apigw2.HttpMethod.OPTIONS], }); - const getSyllabusCourse = root.addMethod(apigw2.HttpMethod.GET, courseGetIntegration, { - operationName: 'GetCourse', - requestParameters: { - 'method.request.querystring.id': true, + const getSyllabusCourse = root.addMethod( + apigw2.HttpMethod.GET, + courseGetIntegration, + { + operationName: 'GetCourse', + requestParameters: { + 'method.request.querystring.id': true, + }, + methodResponses: [ + { + statusCode: '200', + responseParameters: lambdaRespParams, + }, + ], + requestValidator: props.validator, }, - methodResponses: [{ - statusCode: '200', - responseParameters: lambdaRespParams, - }], - requestValidator: props.validator, - }); + ); const optionsBookInfo = bookInfo.addCorsPreflight({ allowOrigins: allowOrigins, allowHeaders: allowHeaders, allowMethods: [apigw2.HttpMethod.POST, apigw2.HttpMethod.OPTIONS], }); - const postBookInfo = bookInfo.addMethod(apigw2.HttpMethod.POST, bookPostIntegration, { - operationName: 'GetBookInfo', - methodResponses: [{ - statusCode: '200', - responseParameters: lambdaRespParams, - }], - requestValidator: props.validator, - }); + const postBookInfo = bookInfo.addMethod( + apigw2.HttpMethod.POST, + bookPostIntegration, + { + operationName: 'GetBookInfo', + methodResponses: [ + { + statusCode: '200', + responseParameters: lambdaRespParams, + }, + ], + requestValidator: props.validator, + }, + ); this.resourceMapping = { '/syllabus': { @@ -171,12 +230,20 @@ export class SyllabusApiService extends RestApiService { } export class CourseReviewsApiService extends RestApiService { - readonly resourceMapping: { [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method } }; - - constructor(scope: AbstractRestApiEndpoint, id: string, props: RestApiServiceProps) { + readonly resourceMapping: { + [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method }; + }; + + constructor( + scope: AbstractRestApiEndpoint, + id: string, + props: RestApiServiceProps, + ) { super(scope, id, props); - const root = scope.apiEndpoint.root.addResource('course-reviews').addResource('{key}'); + const root = scope.apiEndpoint.root + .addResource('course-reviews') + .addResource('{key}'); const getRespModel = scope.apiEndpoint.addModel('review-get-resp-model', { schema: courseReviewGetRespSchema, @@ -197,80 +264,110 @@ export class CourseReviewsApiService extends RestApiService { modelName: 'PatchReviewReq', }); - const courseReviewsFunctions = new CourseReviewsFunctions(this, 'crud-functions', { - envVars: { - TABLE_NAME: props.dataSource!, + const courseReviewsFunctions = new CourseReviewsFunctions( + this, + 'crud-functions', + { + envVars: { + TABLE_NAME: props.dataSource!, + }, }, - }); + ); const getIntegration = new apigw.LambdaIntegration( - courseReviewsFunctions.getFunction, { proxy: true }, + courseReviewsFunctions.getFunction, + { proxy: true }, ); const postIntegration = new apigw.LambdaIntegration( - courseReviewsFunctions.postFunction, { proxy: true }, + courseReviewsFunctions.postFunction, + { proxy: true }, ); const patchIntegration = new apigw.LambdaIntegration( - courseReviewsFunctions.patchFunction, { proxy: true }, + courseReviewsFunctions.patchFunction, + { proxy: true }, ); const deleteIntegration = new apigw.LambdaIntegration( - courseReviewsFunctions.deleteFunction, { proxy: true }, + courseReviewsFunctions.deleteFunction, + { proxy: true }, ); const optionsCourseReviews = root.addCorsPreflight({ allowOrigins: allowOrigins, allowHeaders: allowHeaders, - allowMethods: [apigw2.HttpMethod.GET, apigw2.HttpMethod.POST, apigw2.HttpMethod.PATCH, apigw2.HttpMethod.DELETE, apigw2.HttpMethod.OPTIONS], + allowMethods: [ + apigw2.HttpMethod.GET, + apigw2.HttpMethod.POST, + apigw2.HttpMethod.PATCH, + apigw2.HttpMethod.DELETE, + apigw2.HttpMethod.OPTIONS, + ], }); - const getCourseReviews = root.addMethod(apigw2.HttpMethod.GET, getIntegration, + const getCourseReviews = root.addMethod( + apigw2.HttpMethod.GET, + getIntegration, { requestParameters: { 'method.request.querystring.uid': false, }, operationName: 'GetReviews', - methodResponses: [{ - statusCode: '200', - responseModels: { ['application/json']: getRespModel }, - responseParameters: lambdaRespParams, - }], + methodResponses: [ + { + statusCode: '200', + responseModels: { ['application/json']: getRespModel }, + responseParameters: lambdaRespParams, + }, + ], requestValidator: props.validator, }, ); - const postCourseReviews = root.addMethod(apigw2.HttpMethod.POST, postIntegration, + const postCourseReviews = root.addMethod( + apigw2.HttpMethod.POST, + postIntegration, { operationName: 'PostReview', requestModels: { ['application/json']: postReqModel }, - methodResponses: [{ - statusCode: '200', - responseParameters: lambdaRespParams, - }], + methodResponses: [ + { + statusCode: '200', + responseParameters: lambdaRespParams, + }, + ], authorizer: props.authorizer, requestValidator: props.validator, }, ); - const patchCourseReviews = root.addMethod(apigw2.HttpMethod.PATCH, patchIntegration, + const patchCourseReviews = root.addMethod( + apigw2.HttpMethod.PATCH, + patchIntegration, { operationName: 'UpdateReview', requestParameters: { 'method.request.querystring.ts': true, }, requestModels: { ['application/json']: patchReqModel }, - methodResponses: [{ - statusCode: '200', - responseParameters: lambdaRespParams, - }], + methodResponses: [ + { + statusCode: '200', + responseParameters: lambdaRespParams, + }, + ], authorizer: props.authorizer, requestValidator: props.validator, }, ); - const deleteCourseReviews = root.addMethod(apigw2.HttpMethod.DELETE, deleteIntegration, + const deleteCourseReviews = root.addMethod( + apigw2.HttpMethod.DELETE, + deleteIntegration, { operationName: 'DeleteReview', requestParameters: { 'method.request.querystring.ts': true, }, - methodResponses: [{ - statusCode: '200', - responseParameters: lambdaRespParams, - }], + methodResponses: [ + { + statusCode: '200', + responseParameters: lambdaRespParams, + }, + ], authorizer: props.authorizer, requestValidator: props.validator, }, @@ -289,9 +386,15 @@ export class CourseReviewsApiService extends RestApiService { } export class CareerApiService extends RestApiService { - readonly resourceMapping: { [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method } }; - - constructor(scope: AbstractRestApiEndpoint, id: string, props: RestApiServiceProps) { + readonly resourceMapping: { + [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method }; + }; + + constructor( + scope: AbstractRestApiEndpoint, + id: string, + props: RestApiServiceProps, + ) { super(scope, id, props); const root = scope.apiEndpoint.root.addResource('career'); @@ -302,33 +405,41 @@ export class CareerApiService extends RestApiService { const internGetIntegration = new apigw.MockIntegration({ requestTemplates: { ['application/json']: '{"statusCode": 200}' }, passthroughBehavior: apigw.PassthroughBehavior.WHEN_NO_TEMPLATES, - integrationResponses: [{ - statusCode: '200', - responseTemplates: { ['application/json']: '{}' }, - }], + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { ['application/json']: '{}' }, + }, + ], }); const partGetIntegration = new apigw.MockIntegration({ requestTemplates: { ['application/json']: '{"statusCode": 200}' }, passthroughBehavior: apigw.PassthroughBehavior.WHEN_NO_TEMPLATES, - integrationResponses: [{ - statusCode: '200', - responseTemplates: { ['application/json']: '{}' }, - }], + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { ['application/json']: '{}' }, + }, + ], }); const seminarGetIntegration = new apigw.MockIntegration({ requestTemplates: { ['application/json']: '{"statusCode": 200}' }, passthroughBehavior: apigw.PassthroughBehavior.WHEN_NO_TEMPLATES, - integrationResponses: [{ - statusCode: '200', - responseTemplates: { ['application/json']: '{}' }, - }], + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { ['application/json']: '{}' }, + }, + ], }); - [intern, part, seminar].forEach((value => value.addCorsPreflight({ - allowOrigins: allowOrigins, - allowHeaders: allowHeaders, - allowMethods: [apigw2.HttpMethod.GET, apigw2.HttpMethod.OPTIONS], - }))); + [intern, part, seminar].forEach((value) => + value.addCorsPreflight({ + allowOrigins: allowOrigins, + allowHeaders: allowHeaders, + allowMethods: [apigw2.HttpMethod.GET, apigw2.HttpMethod.OPTIONS], + }), + ); intern.addMethod(apigw2.HttpMethod.GET, internGetIntegration, { requestParameters: { 'method.request.querystring.offset': true, @@ -338,11 +449,13 @@ export class CareerApiService extends RestApiService { 'method.request.querystring.lang': false, }, operationName: 'GetInternInfo', - methodResponses: [{ - statusCode: '200', - responseModels: { ['application/json']: apigw.Model.EMPTY_MODEL }, - responseParameters: lambdaRespParams, - }], + methodResponses: [ + { + statusCode: '200', + responseModels: { ['application/json']: apigw.Model.EMPTY_MODEL }, + responseParameters: lambdaRespParams, + }, + ], requestValidator: props.validator, }); part.addMethod(apigw2.HttpMethod.GET, partGetIntegration, { @@ -356,11 +469,13 @@ export class CareerApiService extends RestApiService { 'method.request.querystring.freq': false, }, operationName: 'GetParttimeInfo', - methodResponses: [{ - statusCode: '200', - responseModels: { ['application/json']: apigw.Model.EMPTY_MODEL }, - responseParameters: lambdaRespParams, - }], + methodResponses: [ + { + statusCode: '200', + responseModels: { ['application/json']: apigw.Model.EMPTY_MODEL }, + responseParameters: lambdaRespParams, + }, + ], requestValidator: props.validator, }); seminar.addMethod(apigw2.HttpMethod.GET, seminarGetIntegration, { @@ -374,20 +489,28 @@ export class CareerApiService extends RestApiService { 'method.request.querystring.major': false, }, operationName: 'GetSeminarInfo', - methodResponses: [{ - statusCode: '200', - responseModels: { ['application/json']: apigw.Model.EMPTY_MODEL }, - responseParameters: lambdaRespParams, - }], + methodResponses: [ + { + statusCode: '200', + responseModels: { ['application/json']: apigw.Model.EMPTY_MODEL }, + responseParameters: lambdaRespParams, + }, + ], requestValidator: props.validator, }); } } export class TimetableApiService extends RestApiService { - readonly resourceMapping: { [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method } }; - - constructor(scope: AbstractRestApiEndpoint, id: string, props: RestApiServiceProps) { + readonly resourceMapping: { + [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method }; + }; + + constructor( + scope: AbstractRestApiEndpoint, + id: string, + props: RestApiServiceProps, + ) { super(scope, id, props); const root = scope.apiEndpoint.root.addResource('timetable'); @@ -400,16 +523,20 @@ export class TimetableApiService extends RestApiService { }, }); const getIntegration = new apigw.LambdaIntegration( - timetableFunctions.getFunction, { proxy: true }, + timetableFunctions.getFunction, + { proxy: true }, ); const postIntegration = new apigw.LambdaIntegration( - timetableFunctions.postFunction, { proxy: true }, + timetableFunctions.postFunction, + { proxy: true }, ); const patchIntegration = new apigw.LambdaIntegration( - timetableFunctions.patchFunction, { proxy: true }, + timetableFunctions.patchFunction, + { proxy: true }, ); const putIntergation = new apigw.LambdaIntegration( - timetableFunctions.putFunction, { proxy: true }, + timetableFunctions.putFunction, + { proxy: true }, ); // const importIntegration = new apigw.LambdaIntegration( // timetableFunctions.importFunction, {proxy: true}, @@ -423,41 +550,63 @@ export class TimetableApiService extends RestApiService { const optionsTimetable = root.addCorsPreflight({ allowOrigins: allowOrigins, allowHeaders: allowHeaders, - allowMethods: [apigw2.HttpMethod.GET, apigw2.HttpMethod.PUT, apigw2.HttpMethod.POST, apigw2.HttpMethod.PATCH, apigw2.HttpMethod.OPTIONS], + allowMethods: [ + apigw2.HttpMethod.GET, + apigw2.HttpMethod.PUT, + apigw2.HttpMethod.POST, + apigw2.HttpMethod.PATCH, + apigw2.HttpMethod.OPTIONS, + ], }); const getTimetable = root.addMethod(apigw2.HttpMethod.GET, getIntegration, { operationName: 'GetTimetable', - methodResponses: [{ - statusCode: '200', - responseParameters: lambdaRespParams, - }], - authorizer: props.authorizer, - requestValidator: props.validator, - }); - const postTimetable = root.addMethod(apigw2.HttpMethod.POST, postIntegration, { - operationName: 'PostTimetable', - methodResponses: [{ - statusCode: '200', - responseParameters: lambdaRespParams, - }], - authorizer: props.authorizer, - requestValidator: props.validator, - }); - const patchTimetable = root.addMethod(apigw2.HttpMethod.PATCH, patchIntegration, { - operationName: 'UpdateTimetable', - methodResponses: [{ - statusCode: '200', - responseParameters: lambdaRespParams, - }], + methodResponses: [ + { + statusCode: '200', + responseParameters: lambdaRespParams, + }, + ], authorizer: props.authorizer, requestValidator: props.validator, }); + const postTimetable = root.addMethod( + apigw2.HttpMethod.POST, + postIntegration, + { + operationName: 'PostTimetable', + methodResponses: [ + { + statusCode: '200', + responseParameters: lambdaRespParams, + }, + ], + authorizer: props.authorizer, + requestValidator: props.validator, + }, + ); + const patchTimetable = root.addMethod( + apigw2.HttpMethod.PATCH, + patchIntegration, + { + operationName: 'UpdateTimetable', + methodResponses: [ + { + statusCode: '200', + responseParameters: lambdaRespParams, + }, + ], + authorizer: props.authorizer, + requestValidator: props.validator, + }, + ); const putTimetable = root.addMethod(apigw2.HttpMethod.PUT, putIntergation, { operationName: 'PutTimetable', - methodResponses: [{ - statusCode: '200', - responseParameters: lambdaRespParams, - }], + methodResponses: [ + { + statusCode: '200', + responseParameters: lambdaRespParams, + }, + ], authorizer: props.authorizer, requestValidator: props.validator, }); @@ -503,9 +652,15 @@ export class TimetableApiService extends RestApiService { } export class GraphqlApiService extends RestApiService { - readonly resourceMapping: { [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method } }; - - constructor(scope: AbstractRestApiEndpoint, id: string, props: RestApiServiceProps) { + readonly resourceMapping: { + [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method }; + }; + + constructor( + scope: AbstractRestApiEndpoint, + id: string, + props: RestApiServiceProps, + ) { super(scope, id, props); const root = scope.apiEndpoint.root.addResource('graphql'); @@ -522,9 +677,11 @@ export class GraphqlApiService extends RestApiService { }); const postGql = root.addMethod(apigw2.HttpMethod.POST, postIntegration, { operationName: 'PostGraphQL', - methodResponses: [{ - statusCode: '200', - }], + methodResponses: [ + { + statusCode: '200', + }, + ], }); this.resourceMapping = { @@ -535,3 +692,35 @@ export class GraphqlApiService extends RestApiService { }; } } + +// export class ForumApiService extends RestApiService { +// readonly resourceMapping: { [path: string]: { [method in apigw2.HttpMethod]?: apigw.Method } }; +// constructor(scope: AbstractRestApiEndpoint, id: string, props: RestApiServiceProps) { +// super(scope, id, props); + +// const root = scope.apiEndpoint.root.addResource('forum'); +// const forumFunctions = new forumFunctions(this, 'crud-functions', { +// envVars: { +// TABLE_NAME: props.dataSource!, +// }, +// }); +// const getIntegration = new apigw.LambdaIntegration( +// forumFunctions.getFunction, { proxy: true }, +// ); +// const postIntegration = new apigw.LambdaIntegration( +// forumFunctions.postFunction, { proxy: true }, +// ); +// const patchIntegration = new apigw.LambdaIntegration( +// forumFunctions.patchFunction, { proxy: true }, +// ); +// const putIntergation = new apigw.LambdaIntegration( +// forumFunctions.putFunction, { proxy: true }, +// ); +// const optionsForum = root.addCorsPreflight({ +// allowOrigins: allowOrigins, +// allowHeaders: allowHeaders, +// allowMethods: [apigw2.HttpMethod.GET, apigw2.HttpMethod.POST, apigw2.HttpMethod.PATCH, apigw2.HttpMethod.OPTIONS, apigw2.HttpMethod.DELETE], +// }); + +// } +// } diff --git a/lib/constructs/common/lambda-functions.ts b/lib/constructs/common/lambda-functions.ts index f24182f4c..eb91f3bf9 100644 --- a/lib/constructs/common/lambda-functions.ts +++ b/lib/constructs/common/lambda-functions.ts @@ -6,7 +6,10 @@ import * as lambda_js from 'aws-cdk-lib/aws-lambda-nodejs'; import * as logs from 'aws-cdk-lib/aws-logs'; import { Construct } from 'constructs'; import { AwsServicePrincipal } from '../../configs/common/aws'; -import { GOOGLE_API_SERVICE_ACCOUNT_INFO, SLACK_WEBHOOK_URL } from '../../configs/lambda/environment'; +import { + GOOGLE_API_SERVICE_ACCOUNT_INFO, + SLACK_WEBHOOK_URL, +} from '../../configs/lambda/environment'; interface FunctionsProps { envVars: { [name: string]: string }; @@ -21,31 +24,53 @@ export class CourseReviewsFunctions extends Construct { constructor(scope: Construct, id: string, props: FunctionsProps) { super(scope, id); - const dynamoDBReadRole: iam.LazyRole = new iam.LazyRole(this, 'dynamo-read-role', { - assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), - description: 'Allow lambda function to perform crud operation on dynamodb', - path: `/service-role/${ AwsServicePrincipal.LAMBDA }/`, - roleName: 'dynamodb-lambda-read', - managedPolicies: [ - iam.ManagedPolicy.fromManagedPolicyArn(this, 'basic-exec', - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'), - iam.ManagedPolicy.fromManagedPolicyArn(this, 'db-read-only', - 'arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess'), - ], - }); - - const dynamoDBPutRole: iam.LazyRole = new iam.LazyRole(this, 'dynamo-put-role', { - assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), - description: 'Allow lambda function to perform crud operation on dynamodb', - path: `/service-role/${ AwsServicePrincipal.LAMBDA }/`, - roleName: 'dynamodb-lambda-write', - managedPolicies: [ - iam.ManagedPolicy.fromManagedPolicyArn(this, 'basic-exec1', - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'), - iam.ManagedPolicy.fromManagedPolicyArn(this, 'db-full-access', - 'arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess'), - ], - }); + const dynamoDBReadRole: iam.LazyRole = new iam.LazyRole( + this, + 'dynamo-read-role', + { + assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), + description: + 'Allow lambda function to perform crud operation on dynamodb', + path: `/service-role/${AwsServicePrincipal.LAMBDA}/`, + roleName: 'dynamodb-lambda-read', + managedPolicies: [ + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'basic-exec', + 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ), + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'db-read-only', + 'arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess', + ), + ], + }, + ); + + const dynamoDBPutRole: iam.LazyRole = new iam.LazyRole( + this, + 'dynamo-put-role', + { + assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), + description: + 'Allow lambda function to perform crud operation on dynamodb', + path: `/service-role/${AwsServicePrincipal.LAMBDA}/`, + roleName: 'dynamodb-lambda-write', + managedPolicies: [ + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'basic-exec1', + 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ), + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'db-full-access', + 'arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess', + ), + ], + }, + ); this.getFunction = new lambda_py.PythonFunction(this, 'get-reviews', { entry: 'src/lambda/get-reviews', @@ -69,7 +94,10 @@ export class CourseReviewsFunctions extends Construct { runtime: lambda.Runtime.PYTHON_3_9, timeout: Duration.seconds(5), environment: props.envVars, - }).addEnvironment('GOOGLE_API_SERVICE_ACCOUNT_INFO', GOOGLE_API_SERVICE_ACCOUNT_INFO); + }).addEnvironment( + 'GOOGLE_API_SERVICE_ACCOUNT_INFO', + GOOGLE_API_SERVICE_ACCOUNT_INFO, + ); this.patchFunction = new lambda_py.PythonFunction(this, 'patch-review', { entry: 'src/lambda/patch-review', @@ -81,7 +109,10 @@ export class CourseReviewsFunctions extends Construct { runtime: lambda.Runtime.PYTHON_3_9, timeout: Duration.seconds(5), environment: props.envVars, - }).addEnvironment('GOOGLE_API_SERVICE_ACCOUNT_INFO', GOOGLE_API_SERVICE_ACCOUNT_INFO); + }).addEnvironment( + 'GOOGLE_API_SERVICE_ACCOUNT_INFO', + GOOGLE_API_SERVICE_ACCOUNT_INFO, + ); this.deleteFunction = new lambda_py.PythonFunction(this, 'delete-review', { entry: 'src/lambda/delete-review', @@ -103,22 +134,33 @@ export class SyllabusScraper extends Construct { constructor(scope: Construct, id: string, props: FunctionsProps) { super(scope, id); - const s3AccessRole: iam.LazyRole = new iam.LazyRole(this, 's3-access-role', { - assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), - description: 'Allow lambda function to access s3 buckets', - path: `/service-role/${ AwsServicePrincipal.LAMBDA }/`, - roleName: 's3-lambda-full-access', - managedPolicies: [ - iam.ManagedPolicy.fromManagedPolicyArn(this, 'basic-exec', - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'), - iam.ManagedPolicy.fromManagedPolicyArn(this, 's3-full-access', - 'arn:aws:iam::aws:policy/AmazonS3FullAccess'), - ], - }); + const s3AccessRole: iam.LazyRole = new iam.LazyRole( + this, + 's3-access-role', + { + assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), + description: 'Allow lambda function to access s3 buckets', + path: `/service-role/${AwsServicePrincipal.LAMBDA}/`, + roleName: 's3-lambda-full-access', + managedPolicies: [ + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'basic-exec', + 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ), + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 's3-full-access', + 'arn:aws:iam::aws:policy/AmazonS3FullAccess', + ), + ], + }, + ); this.baseFunction = new lambda_py.PythonFunction(this, 'base-function', { entry: 'src/lambda/syllabus-scraper', - description: 'Base function for scraping syllabus data from Waseda University.', + description: + 'Base function for scraping syllabus data from Waseda University.', functionName: 'syllabus-scraper', logRetention: logs.RetentionDays.ONE_MONTH, memorySize: 4096, @@ -138,7 +180,8 @@ export class AmplifyStatusPublisher extends Construct { this.baseFunction = new lambda_js.NodejsFunction(this, 'base-function', { entry: 'src/lambda/amplify-status-publisher/index.js', - description: 'Forwards Amplify build status message from SNS to Slack Webhook.', + description: + 'Forwards Amplify build status message from SNS to Slack Webhook.', functionName: 'amplify-status-publisher', logRetention: logs.RetentionDays.ONE_MONTH, memorySize: 128, @@ -156,7 +199,8 @@ export class ScraperStatusPublisher extends Construct { this.baseFunction = new lambda_js.NodejsFunction(this, 'base-function', { entry: 'src/lambda/sfn-status-publisher/index.js', - description: 'Forwards scraper execution status message from SNS to Slack Webhook.', + description: + 'Forwards scraper execution status message from SNS to Slack Webhook.', functionName: 'scraper-status-publisher', logRetention: logs.RetentionDays.ONE_MONTH, memorySize: 128, @@ -196,31 +240,53 @@ export class TimetableFunctions extends Construct { constructor(scope: Construct, id: string, props: FunctionsProps) { super(scope, id); - const dynamoDBReadRole: iam.LazyRole = new iam.LazyRole(this, 'dynamo-read-role', { - assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), - description: 'Allow lambda function to perform crud operation on dynamodb', - path: `/service-role/${ AwsServicePrincipal.LAMBDA }/`, - roleName: 'dynamodb-lambda-read-timetable', - managedPolicies: [ - iam.ManagedPolicy.fromManagedPolicyArn(this, 'basic-exec', - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'), - iam.ManagedPolicy.fromManagedPolicyArn(this, 'db-read-only', - 'arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess'), - ], - }); - - const dynamoDBPutRole: iam.LazyRole = new iam.LazyRole(this, 'dynamo-put-role', { - assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), - description: 'Allow lambda function to perform crud operation on dynamodb', - path: `/service-role/${ AwsServicePrincipal.LAMBDA }/`, - roleName: 'dynamodb-lambda-write-timetable', - managedPolicies: [ - iam.ManagedPolicy.fromManagedPolicyArn(this, 'basic-exec1', - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'), - iam.ManagedPolicy.fromManagedPolicyArn(this, 'db-full-access', - 'arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess'), - ], - }); + const dynamoDBReadRole: iam.LazyRole = new iam.LazyRole( + this, + 'dynamo-read-role', + { + assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), + description: + 'Allow lambda function to perform crud operation on dynamodb', + path: `/service-role/${AwsServicePrincipal.LAMBDA}/`, + roleName: 'dynamodb-lambda-read-timetable', + managedPolicies: [ + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'basic-exec', + 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ), + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'db-read-only', + 'arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess', + ), + ], + }, + ); + + const dynamoDBPutRole: iam.LazyRole = new iam.LazyRole( + this, + 'dynamo-put-role', + { + assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), + description: + 'Allow lambda function to perform crud operation on dynamodb', + path: `/service-role/${AwsServicePrincipal.LAMBDA}/`, + roleName: 'dynamodb-lambda-write-timetable', + managedPolicies: [ + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'basic-exec1', + 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ), + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'db-full-access', + 'arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess', + ), + ], + }, + ); this.getFunction = new lambda_py.PythonFunction(this, 'get-timetable', { entry: 'src/lambda/get-timetable', @@ -299,18 +365,29 @@ export class SyllabusFunctions extends Construct { constructor(scope: Construct, id: string, props?: FunctionsProps) { super(scope, id); - const dynamoDBReadRole: iam.LazyRole = new iam.LazyRole(this, 'dynamo-read-role', { - assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), - description: 'Allow lambda function to perform crud operation on dynamodb', - path: `/service-role/${ AwsServicePrincipal.LAMBDA }/`, - roleName: 'dynamodb-lambda-read-syllabus', - managedPolicies: [ - iam.ManagedPolicy.fromManagedPolicyArn(this, 'basic-exec', - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'), - iam.ManagedPolicy.fromManagedPolicyArn(this, 'db-read-only', - 'arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess'), - ], - }); + const dynamoDBReadRole: iam.LazyRole = new iam.LazyRole( + this, + 'dynamo-read-role', + { + assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), + description: + 'Allow lambda function to perform crud operation on dynamodb', + path: `/service-role/${AwsServicePrincipal.LAMBDA}/`, + roleName: 'dynamodb-lambda-read-syllabus', + managedPolicies: [ + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'basic-exec', + 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ), + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'db-read-only', + 'arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess', + ), + ], + }, + ); this.getFunction = new lambda_py.PythonFunction(this, 'get-course', { entry: 'src/lambda/get-course', @@ -322,18 +399,28 @@ export class SyllabusFunctions extends Construct { timeout: Duration.seconds(3), }); - const comprehendFullAccessRole: iam.LazyRole = new iam.LazyRole(this, 'comprehend-access-role', { - assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), - description: 'Allow lambda function to interact with AWS Comprehend', - path: `/service-role/${ AwsServicePrincipal.LAMBDA }/`, - roleName: 'lambda-comprehend-access', - managedPolicies: [ - iam.ManagedPolicy.fromManagedPolicyArn(this, 'basic-exec1', - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'), - iam.ManagedPolicy.fromManagedPolicyArn(this, 'comprehend-full-access', - 'arn:aws:iam::aws:policy/ComprehendFullAccess'), - ], - }); + const comprehendFullAccessRole: iam.LazyRole = new iam.LazyRole( + this, + 'comprehend-access-role', + { + assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), + description: 'Allow lambda function to interact with AWS Comprehend', + path: `/service-role/${AwsServicePrincipal.LAMBDA}/`, + roleName: 'lambda-comprehend-access', + managedPolicies: [ + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'basic-exec1', + 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ), + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'comprehend-full-access', + 'arn:aws:iam::aws:policy/ComprehendFullAccess', + ), + ], + }, + ); this.postFunction = new lambda_py.PythonFunction(this, 'post-book', { entry: 'src/lambda/get-book-info', @@ -357,28 +444,126 @@ export class SyllabusUpdateFunction extends Construct { const LambdaFullAccess = new iam.LazyRole(this, 'lambda-fullaccess-role', { assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), description: 'Allow lambda function to access s3 buckets and dynamodb', - path: `/service-role/${ AwsServicePrincipal.LAMBDA }/`, + path: `/service-role/${AwsServicePrincipal.LAMBDA}/`, roleName: 'lambda-full-access', managedPolicies: [ - iam.ManagedPolicy.fromManagedPolicyArn(this, 'basic-exec', - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'), - iam.ManagedPolicy.fromManagedPolicyArn(this, 'db-full-access', - 'arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess'), - iam.ManagedPolicy.fromManagedPolicyArn(this, 's3-read-only', - 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess'), + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'basic-exec', + 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ), + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 'db-full-access', + 'arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess', + ), + iam.ManagedPolicy.fromManagedPolicyArn( + this, + 's3-read-only', + 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess', + ), ], }); - this.updateFunction = new lambda_py.PythonFunction(this, 'update-syllabus', { - entry: 'src/lambda/update-syllabus', - description: 'Update syllabus when S3 bucket is updated.', - functionName: 'update-syllabus', - role: LambdaFullAccess, - logRetention: logs.RetentionDays.ONE_MONTH, - memorySize: 128, - runtime: lambda.Runtime.PYTHON_3_9, - timeout: Duration.seconds(60), - environment: props.envVars, - }); + this.updateFunction = new lambda_py.PythonFunction( + this, + 'update-syllabus', + { + entry: 'src/lambda/update-syllabus', + description: 'Update syllabus when S3 bucket is updated.', + functionName: 'update-syllabus', + role: LambdaFullAccess, + logRetention: logs.RetentionDays.ONE_MONTH, + memorySize: 128, + runtime: lambda.Runtime.PYTHON_3_9, + timeout: Duration.seconds(60), + environment: props.envVars, + }, + ); } } + +// export class ForumThreadFunctions extends Construct { +// readonly getFunction: lambda.Function; +// readonly postFunction: lambda.Function; +// readonly patchFunction: lambda.Function; +// readonly deleteFunction: lambda.Function; + +// constructor(scope: Construct, id: string, props: FunctionsProps) { +// super(scope, id); + +// const dynamoDBReadRole: iam.LazyRole = new iam.LazyRole(this, 'dynamo-read-role', { +// assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), +// description: 'Allow lambda function to perform crud operation on dynamodb', +// path: `/service-role/${ AwsServicePrincipal.LAMBDA }/`, +// roleName: 'dynamodb-lambda-read', +// managedPolicies: [ +// iam.ManagedPolicy.fromManagedPolicyArn(this, 'basic-exec', +// 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'), +// iam.ManagedPolicy.fromManagedPolicyArn(this, 'db-read-only', +// 'arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess'), +// ], +// }); + +// const dynamoDBPutRole: iam.LazyRole = new iam.LazyRole(this, 'dynamo-put-role', { +// assumedBy: new iam.ServicePrincipal(AwsServicePrincipal.LAMBDA), +// description: 'Allow lambda function to perform crud operation on dynamodb', +// path: `/service-role/${ AwsServicePrincipal.LAMBDA }/`, +// roleName: 'dynamodb-lambda-write', +// managedPolicies: [ +// iam.ManagedPolicy.fromManagedPolicyArn(this, 'basic-exec1', +// 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'), +// iam.ManagedPolicy.fromManagedPolicyArn(this, 'db-full-access', +// 'arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess'), +// ], +// }); + +// this.getFunction = new lambda_py.PythonFunction(this, 'get-threads', { +// entry: 'src/lambda/get-threads', +// description: 'Get forum threads from the database.', +// functionName: 'get-forum-threads', +// logRetention: logs.RetentionDays.ONE_MONTH, +// memorySize: 128, +// role: dynamoDBReadRole, +// runtime: lambda.Runtime.PYTHON_3_9, +// timeout: Duration.seconds(3), +// environment: props.envVars, +// }); + +// this.postFunction = new lambda_py.PythonFunction(this, 'post-thread', { +// entry: 'src/lambda/post-threads', +// description: 'Save forum thread into the database.', +// functionName: 'post-forum-thread', +// logRetention: logs.RetentionDays.ONE_MONTH, +// memorySize: 256, +// role: dynamoDBPutRole, +// runtime: lambda.Runtime.PYTHON_3_9, +// timeout: Duration.seconds(5), +// environment: props.envVars, +// }).addEnvironment('GOOGLE_API_SERVICE_ACCOUNT_INFO', GOOGLE_API_SERVICE_ACCOUNT_INFO); + +// this.patchFunction = new lambda_py.PythonFunction(this, 'patch-thread', { +// entry: 'src/lambda/patch-threads', +// description: 'Update forum thread in the database.', +// functionName: 'patch-forum-thread', +// logRetention: logs.RetentionDays.ONE_MONTH, +// memorySize: 256, +// role: dynamoDBPutRole, +// runtime: lambda.Runtime.PYTHON_3_9, +// timeout: Duration.seconds(5), +// environment: props.envVars, +// }).addEnvironment('GOOGLE_API_SERVICE_ACCOUNT_INFO', GOOGLE_API_SERVICE_ACCOUNT_INFO); + +// this.deleteFunction = new lambda_py.PythonFunction(this, 'delete-thread', { +// entry: 'src/lambda/delete-threads', +// description: 'Delete forum thread in the database.', +// functionName: 'delete-forum-thread', +// logRetention: logs.RetentionDays.ONE_MONTH, +// memorySize: 128, +// role: dynamoDBPutRole, +// runtime: lambda.Runtime.PYTHON_3_9, +// timeout: Duration.seconds(3), +// environment: props.envVars, +// }); +// } +//} diff --git a/lib/constructs/persistence/database.ts b/lib/constructs/persistence/database.ts index 73e2d6206..02cc6bdab 100644 --- a/lib/constructs/persistence/database.ts +++ b/lib/constructs/persistence/database.ts @@ -8,6 +8,8 @@ export enum Collection { FEEDS, SYLLABUS, TIMETABLE, + THREAD, + COMMENT, } export class DynamoDatabase extends Construct { @@ -16,49 +18,118 @@ export class DynamoDatabase extends Construct { constructor(scope: Construct, id: string) { super(scope, id); - this.tables[Collection.COURSE_REVIEW] = new dynamodb.Table(this, 'dynamodb-review-table', { - partitionKey: { name: 'course_key', type: dynamodb.AttributeType.STRING }, - billingMode: dynamodb.BillingMode.PROVISIONED, - encryption: dynamodb.TableEncryption.DEFAULT, - removalPolicy: RemovalPolicy.RETAIN, - sortKey: { name: 'created_at', type: dynamodb.AttributeType.STRING }, - tableName: 'course-review', - readCapacity: 10, - writeCapacity: 7, - pointInTimeRecovery: true, - }); + this.tables[Collection.COURSE_REVIEW] = new dynamodb.Table( + this, + 'dynamodb-review-table', + { + partitionKey: { + name: 'course_key', + type: dynamodb.AttributeType.STRING, + }, + billingMode: dynamodb.BillingMode.PROVISIONED, + encryption: dynamodb.TableEncryption.DEFAULT, + removalPolicy: RemovalPolicy.RETAIN, + sortKey: { name: 'created_at', type: dynamodb.AttributeType.STRING }, + tableName: 'course-review', + readCapacity: 10, + writeCapacity: 7, + pointInTimeRecovery: true, + }, + ); - this.tables[Collection.CAREER] = new dynamodb.Table(this, 'dynamodb-career-table', { - partitionKey: { name: 'type', type: dynamodb.AttributeType.STRING }, - billingMode: dynamodb.BillingMode.PROVISIONED, - encryption: dynamodb.TableEncryption.DEFAULT, - removalPolicy: RemovalPolicy.RETAIN, - sortKey: { name: 'created_at', type: dynamodb.AttributeType.STRING }, - tableName: 'career', - readCapacity: 1, - writeCapacity: 1, - }); + this.tables[Collection.CAREER] = new dynamodb.Table( + this, + 'dynamodb-career-table', + { + partitionKey: { name: 'type', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PROVISIONED, + encryption: dynamodb.TableEncryption.DEFAULT, + removalPolicy: RemovalPolicy.RETAIN, + sortKey: { name: 'created_at', type: dynamodb.AttributeType.STRING }, + tableName: 'career', + readCapacity: 1, + writeCapacity: 1, + }, + ); + + this.tables[Collection.FEEDS] = new dynamodb.Table( + this, + 'dynamodb-feeds-table', + { + partitionKey: { name: 'category', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PROVISIONED, + encryption: dynamodb.TableEncryption.DEFAULT, + removalPolicy: RemovalPolicy.RETAIN, + sortKey: { name: 'created_at', type: dynamodb.AttributeType.STRING }, + tableName: 'feeds', + readCapacity: 1, + writeCapacity: 1, + }, + ); - this.tables[Collection.FEEDS] = new dynamodb.Table(this, 'dynamodb-feeds-table', { - partitionKey: { name: 'category', type: dynamodb.AttributeType.STRING }, - billingMode: dynamodb.BillingMode.PROVISIONED, - encryption: dynamodb.TableEncryption.DEFAULT, - removalPolicy: RemovalPolicy.RETAIN, + this.tables[Collection.TIMETABLE] = new dynamodb.Table( + this, + 'dynamodb-timetable-table', + { + partitionKey: { name: 'uid', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PROVISIONED, + encryption: dynamodb.TableEncryption.DEFAULT, + removalPolicy: RemovalPolicy.RETAIN, + tableName: 'timetable', + readCapacity: 12, + writeCapacity: 15, + pointInTimeRecovery: true, + }, + ); + + this.tables[Collection.THREAD] = new dynamodb.Table( + this, + 'dynamodb-thread-table', + { + partitionKey: { + name: 'board_id', + type: dynamodb.AttributeType.STRING, + }, + billingMode: dynamodb.BillingMode.PROVISIONED, + encryption: dynamodb.TableEncryption.DEFAULT, + removalPolicy: RemovalPolicy.RETAIN, + sortKey: { name: 'created_at', type: dynamodb.AttributeType.STRING }, + tableName: 'forum-thread', + readCapacity: 10, + writeCapacity: 7, + pointInTimeRecovery: true, + }, + ); + + this.tables[Collection.THREAD].addLocalSecondaryIndex({ + indexName: 'GroupIndex', sortKey: { name: 'created_at', type: dynamodb.AttributeType.STRING }, - tableName: 'feeds', - readCapacity: 1, - writeCapacity: 1, + projectionType: dynamodb.ProjectionType.ALL, }); - this.tables[Collection.TIMETABLE] = new dynamodb.Table(this, 'dynamodb-timetable-table', { - partitionKey: { name: 'uid', type: dynamodb.AttributeType.STRING }, - billingMode: dynamodb.BillingMode.PROVISIONED, - encryption: dynamodb.TableEncryption.DEFAULT, - removalPolicy: RemovalPolicy.RETAIN, - tableName: 'timetable', - readCapacity: 12, - writeCapacity: 15, - pointInTimeRecovery: true, - }); + // this.tables[Collection.FORUM].addLocalSecondaryIndex({ + // indexName: "TagbyCreated", + // sortKey: { name: "created_at", type: dynamodb.AttributeType.STRING }, + // projectionType: dynamodb.ProjectionType.ALL, + // }); + + this.tables[Collection.COMMENT] = new dynamodb.Table( + this, + 'dynamodb-comment-table', + { + partitionKey: { + name: 'thread_id', + type: dynamodb.AttributeType.STRING, + }, + billingMode: dynamodb.BillingMode.PROVISIONED, + encryption: dynamodb.TableEncryption.DEFAULT, + removalPolicy: RemovalPolicy.RETAIN, + sortKey: { name: 'board', type: dynamodb.AttributeType.STRING }, + tableName: 'forum-comment', + readCapacity: 10, + writeCapacity: 7, + pointInTimeRecovery: true, + }, + ); } } diff --git a/lib/stacks/persistence.ts b/lib/stacks/persistence.ts index 803b2075b..e13c7091d 100644 --- a/lib/stacks/persistence.ts +++ b/lib/stacks/persistence.ts @@ -18,19 +18,31 @@ export class WasedaTimePersistenceLayer extends PersistenceLayer { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); - const syllabusDataPipeline = new SyllabusDataPipeline(this, 'syllabus-datapipeline', {}); + const syllabusDataPipeline = new SyllabusDataPipeline( + this, + 'syllabus-datapipeline', + {}, + ); this.dataPipelines[Worker.SYLLABUS] = syllabusDataPipeline; - const syllabusSyncPipeline = new SyllabusSyncPipeline(this, 'syllabus-sync', { - dataSource: syllabusDataPipeline.dataWarehouse, - }); + const syllabusSyncPipeline = new SyllabusSyncPipeline( + this, + 'syllabus-sync', + { + dataSource: syllabusDataPipeline.dataWarehouse, + }, + ); const dynamoDatabase = new DynamoDatabase(this, 'dynamo-db'); this.databases['dynamo-main'] = dynamoDatabase; - this.dataPipelines[Worker.CAREER] = new CareerDataPipeline(this, 'career-datapipeline', { - dataWarehouse: dynamoDatabase.tables[Collection.CAREER], - }); + this.dataPipelines[Worker.CAREER] = new CareerDataPipeline( + this, + 'career-datapipeline', + { + dataWarehouse: dynamoDatabase.tables[Collection.CAREER], + }, + ); this.dataInterface.setEndpoint( DataEndpoint.COURSE_REVIEWS, @@ -48,16 +60,21 @@ export class WasedaTimePersistenceLayer extends PersistenceLayer { DataEndpoint.SYLLABUS, syllabusDataPipeline.dataWarehouse.bucketName, ); + this.dataInterface.setEndpoint( + DataEndpoint.THREAD, + dynamoDatabase.tables[Collection.THREAD].tableName, + ); + this.dataInterface.setEndpoint( + DataEndpoint.COMMENT, + dynamoDatabase.tables[Collection.COMMENT], + ); // this.dataInterface.setEndpoint( // DataEndpoint.COURSE, // syllabusSyncPipeline.dataWarehouse.tableName, // ); - this.operationInterface.setEndpoint( - OperationEndpoint.SYLLABUS, - { - [syllabusDataPipeline.processor.stateMachineArn]: 'scraper', - }, - ); + this.operationInterface.setEndpoint(OperationEndpoint.SYLLABUS, { + [syllabusDataPipeline.processor.stateMachineArn]: 'scraper', + }); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9a1a8928..5113bb724 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -508,8 +508,8 @@ packages: hasBin: true dependencies: '@commitlint/format': 17.0.0 - '@commitlint/lint': 17.2.0 - '@commitlint/load': 17.2.0 + '@commitlint/lint': 17.3.0 + '@commitlint/load': 17.3.0 '@commitlint/read': 17.2.0 '@commitlint/types': 17.0.0 execa: 5.1.1 @@ -537,12 +537,16 @@ packages: ajv: 8.11.0 dev: true - /@commitlint/ensure/17.0.0: - resolution: {integrity: sha512-M2hkJnNXvEni59S0QPOnqCKIK52G1XyXBGw51mvh7OXDudCmZ9tZiIPpU882p475Mhx48Ien1MbWjCP1zlyC0A==} + /@commitlint/ensure/17.3.0: + resolution: {integrity: sha512-kWbrQHDoW5veIUQx30gXoLOCjWvwC6OOEofhPCLl5ytRPBDAQObMbxTha1Bt2aSyNE/IrJ0s0xkdZ1Gi3wJwQg==} engines: {node: '>=v14'} dependencies: '@commitlint/types': 17.0.0 - lodash: 4.17.21 + lodash.camelcase: 4.3.0 + lodash.kebabcase: 4.1.1 + lodash.snakecase: 4.1.1 + lodash.startcase: 4.4.0 + lodash.upperfirst: 4.3.1 dev: true /@commitlint/execute-rule/17.0.0: @@ -566,31 +570,33 @@ packages: semver: 7.3.7 dev: true - /@commitlint/lint/17.2.0: - resolution: {integrity: sha512-N2oLn4Dj672wKH5qJ4LGO+73UkYXGHO+NTVUusGw83SjEv7GjpqPGKU6KALW2kFQ/GsDefSvOjpSi3CzWHQBDg==} + /@commitlint/lint/17.3.0: + resolution: {integrity: sha512-VilOTPg0i9A7CCWM49E9bl5jytfTvfTxf9iwbWAWNjxJ/A5mhPKbm3sHuAdwJ87tDk1k4j8vomYfH23iaY+1Rw==} engines: {node: '>=v14'} dependencies: '@commitlint/is-ignored': 17.2.0 '@commitlint/parse': 17.2.0 - '@commitlint/rules': 17.2.0 + '@commitlint/rules': 17.3.0 '@commitlint/types': 17.0.0 dev: true - /@commitlint/load/17.2.0: - resolution: {integrity: sha512-HDD57qSqNrk399R4TIjw31AWBG8dBjNj1MrDKZKmC/wvimtnIFlqzcu1+sxfXIOHj/+M6tcMWDtvknGUd7SU+g==} + /@commitlint/load/17.3.0: + resolution: {integrity: sha512-u/pV6rCAJrCUN+HylBHLzZ4qj1Ew3+eN9GBPhNi9otGxtOfA8b+8nJSxaNbcC23Ins/kcpjGf9zPSVW7628Umw==} engines: {node: '>=v14'} dependencies: '@commitlint/config-validator': 17.1.0 '@commitlint/execute-rule': 17.0.0 - '@commitlint/resolve-extends': 17.1.0 + '@commitlint/resolve-extends': 17.3.0 '@commitlint/types': 17.0.0 - '@types/node': 14.18.33 + '@types/node': 14.18.35 chalk: 4.1.2 cosmiconfig: 7.0.1 - cosmiconfig-typescript-loader: 4.2.0_d125882cb23688d4a62106962c534e3b - lodash: 4.17.21 + cosmiconfig-typescript-loader: 4.3.0_5a58b6c7d6a49be47b6f59e5649dd553 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.8.1_cf2a114464063a7ac56422e5bb88c1db + ts-node: 10.8.1_784ac17bd52437afb2b1617dc7147d54 typescript: 4.7.3 transitivePeerDependencies: - '@swc/core' @@ -622,23 +628,23 @@ packages: minimist: 1.2.6 dev: true - /@commitlint/resolve-extends/17.1.0: - resolution: {integrity: sha512-jqKm00LJ59T0O8O4bH4oMa4XyJVEOK4GzH8Qye9XKji+Q1FxhZznxMV/bDLyYkzbTodBt9sL0WLql8wMtRTbqQ==} + /@commitlint/resolve-extends/17.3.0: + resolution: {integrity: sha512-Lf3JufJlc5yVEtJWC8o4IAZaB8FQAUaVlhlAHRACd0TTFizV2Lk2VH70et23KgvbQNf7kQzHs/2B4QZalBv6Cg==} engines: {node: '>=v14'} dependencies: '@commitlint/config-validator': 17.1.0 '@commitlint/types': 17.0.0 import-fresh: 3.3.0 - lodash: 4.17.21 + lodash.mergewith: 4.6.2 resolve-from: 5.0.0 resolve-global: 1.0.0 dev: true - /@commitlint/rules/17.2.0: - resolution: {integrity: sha512-1YynwD4Eh7HXZNpqG8mtUlL2pSX2jBy61EejYJv4ooZPcg50Ak7LPOyD3a9UZnsE76AXWFBz+yo9Hv4MIpAa0Q==} + /@commitlint/rules/17.3.0: + resolution: {integrity: sha512-s2UhDjC5yP2utx3WWqsnZRzjgzAX8BMwr1nltC0u0p8T/nzpkx4TojEfhlsOUj1t7efxzZRjUAV0NxNwdJyk+g==} engines: {node: '>=v14'} dependencies: - '@commitlint/ensure': 17.0.0 + '@commitlint/ensure': 17.3.0 '@commitlint/message': 17.2.0 '@commitlint/to-lines': 17.0.0 '@commitlint/types': 17.0.0 @@ -1119,8 +1125,8 @@ packages: resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} dev: true - /@types/node/14.18.33: - resolution: {integrity: sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==} + /@types/node/14.18.35: + resolution: {integrity: sha512-2ATO8pfhG1kDvw4Lc4C0GXIMSQFFJBCo/R1fSgTwmUlq5oy95LXyjDQinsRVgQY6gp6ghh3H91wk9ES5/5C+Tw==} dev: true /@types/node/17.0.41: @@ -1897,8 +1903,8 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /cosmiconfig-typescript-loader/4.2.0_d125882cb23688d4a62106962c534e3b: - resolution: {integrity: sha512-NkANeMnaHrlaSSlpKGyvn2R4rqUDeE/9E5YHx+b4nwo0R8dZyAqcih8/gxpCZvqWP9Vf6xuLpMSzSgdVEIM78g==} + /cosmiconfig-typescript-loader/4.3.0_5a58b6c7d6a49be47b6f59e5649dd553: + resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@types/node': '*' @@ -1906,9 +1912,9 @@ packages: ts-node: '>=10' typescript: '>=3' dependencies: - '@types/node': 14.18.33 + '@types/node': 14.18.35 cosmiconfig: 7.0.1 - ts-node: 10.8.1_cf2a114464063a7ac56422e5bb88c1db + ts-node: 10.8.1_784ac17bd52437afb2b1617dc7147d54 typescript: 4.7.3 dev: true @@ -3756,10 +3762,22 @@ packages: p-locate: 5.0.0 dev: true + /lodash.camelcase/4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + dev: true + /lodash.ismatch/4.4.0: resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} dev: true + /lodash.isplainobject/4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: true + + /lodash.kebabcase/4.1.1: + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} + dev: true + /lodash.memoize/4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} dev: true @@ -3768,10 +3786,30 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash.mergewith/4.6.2: + resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + dev: true + + /lodash.snakecase/4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + dev: true + + /lodash.startcase/4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + dev: true + /lodash.truncate/4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} dev: true + /lodash.uniq/4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + dev: true + + /lodash.upperfirst/4.3.1: + resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + dev: true + /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true @@ -4666,7 +4704,7 @@ packages: dev: true /through/2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=} dev: true /through2/2.0.5: @@ -4765,7 +4803,7 @@ packages: yn: 3.1.1 dev: true - /ts-node/10.8.1_cf2a114464063a7ac56422e5bb88c1db: + /ts-node/10.8.1_784ac17bd52437afb2b1617dc7147d54: resolution: {integrity: sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==} hasBin: true peerDependencies: @@ -4784,7 +4822,7 @@ packages: '@tsconfig/node12': 1.0.9 '@tsconfig/node14': 1.0.1 '@tsconfig/node16': 1.0.2 - '@types/node': 14.18.33 + '@types/node': 14.18.35 acorn: 8.7.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -4895,7 +4933,7 @@ packages: dev: true /util-deprecate/1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} dev: true /v8-compile-cache-lib/3.0.1: diff --git a/src/lambda/get-single-thread/index.py b/src/lambda/get-single-thread/index.py new file mode 100644 index 000000000..07a7c4561 --- /dev/null +++ b/src/lambda/get-single-thread/index.py @@ -0,0 +1,29 @@ +# from boto3.dynamodb.conditions import Key +# from datetime import datetime +# from utils import JsonPayloadBuilder, table, resp_handler + + +# @resp_handler +# def get_thread(thread_id): +# now = datetime.now() + +# items = table.query( +# KeyConditionExpression='sort_key <= :sk', +# ExpressionAttributeValues={ +# ':sk': now.strftime('%Y-%m-%d %H:%M:%S') +# }, +# ScanIndexForward=False, +# Limit=50 +# ) + +# body = JsonPayloadBuilder().add_status( +# True).add_data(items).add_message('').compile() +# return body + + +# def handler(event, context): +# params = { +# "thread_id": event["queryStringParameters"]["id"] +# } + +# return get_thread(**params) diff --git a/src/lambda/get-single-thread/utils.py b/src/lambda/get-single-thread/utils.py new file mode 100644 index 000000000..3ccbaf2f5 --- /dev/null +++ b/src/lambda/get-single-thread/utils.py @@ -0,0 +1,66 @@ +# import boto3 +# import json +# import logging +# import os +# from decimal import Decimal + +# db = boto3.resource("dynamodb", region_name="ap-northeast-1") +# table = db.Table(os.getenv('TABLE_NAME')) + + +# class DecimalEncoder(json.JSONEncoder): +# def default(self, obj): +# if isinstance(obj, Decimal): +# return float(obj) +# return json.JSONEncoder.default(self, obj) + + +# class JsonPayloadBuilder: +# payload = {} + +# def add_status(self, success): +# self.payload['success'] = success +# return self + +# def add_data(self, data): +# self.payload['data'] = data +# return self + +# def add_message(self, msg): +# self.payload['message'] = msg +# return self + +# def compile(self): +# return json.dumps(self.payload, cls=DecimalEncoder, ensure_ascii=False).encode('utf8') + + +# def api_response(code, body): +# return { +# "isBase64Encoded": False, +# "statusCode": code, +# 'headers': { +# "Access-Control-Allow-Origin": '*', +# "Content-Type": "application/json", +# "Referrer-Policy": "origin" +# }, +# "multiValueHeaders": {"Access-Control-Allow-Methods": ["POST", "OPTIONS", "GET", "PATCH", "DELETE"]}, +# "body": body +# } + + +# def resp_handler(func): +# def handle(*args, **kwargs): +# try: +# resp = func(*args, **kwargs) +# return api_response(200, resp) +# except LookupError: +# resp = JsonPayloadBuilder().add_status(False).add_data(None) \ +# .add_message("Not found").compile() +# return api_response(404, resp) +# except Exception as e: +# logging.error(str(e)) +# resp = JsonPayloadBuilder().add_status(False).add_data(None) \ +# .add_message("Internal error, please contact bugs@wasedatime.com.").compile() +# return api_response(500, resp) + +# return handle diff --git a/src/lambda/get-threads/index.py b/src/lambda/get-threads/index.py new file mode 100644 index 000000000..14e4b0e8b --- /dev/null +++ b/src/lambda/get-threads/index.py @@ -0,0 +1,20 @@ +# from boto3.dynamodb.conditions import Key +# import boto3 +# from datetime import datetime +# from utils import JsonPayloadBuilder, table, resp_handler + + +# @resp_handler +# def get_threads(): + +# response = table.scan(TableName=table) +# items = response['Items'] + +# body = JsonPayloadBuilder().add_status( +# True).add_data(items).add_message('').compile() +# return body + + +# def handler(event, context): + +# return get_threads() diff --git a/src/lambda/get-threads/utils.py b/src/lambda/get-threads/utils.py new file mode 100644 index 000000000..3ccbaf2f5 --- /dev/null +++ b/src/lambda/get-threads/utils.py @@ -0,0 +1,66 @@ +# import boto3 +# import json +# import logging +# import os +# from decimal import Decimal + +# db = boto3.resource("dynamodb", region_name="ap-northeast-1") +# table = db.Table(os.getenv('TABLE_NAME')) + + +# class DecimalEncoder(json.JSONEncoder): +# def default(self, obj): +# if isinstance(obj, Decimal): +# return float(obj) +# return json.JSONEncoder.default(self, obj) + + +# class JsonPayloadBuilder: +# payload = {} + +# def add_status(self, success): +# self.payload['success'] = success +# return self + +# def add_data(self, data): +# self.payload['data'] = data +# return self + +# def add_message(self, msg): +# self.payload['message'] = msg +# return self + +# def compile(self): +# return json.dumps(self.payload, cls=DecimalEncoder, ensure_ascii=False).encode('utf8') + + +# def api_response(code, body): +# return { +# "isBase64Encoded": False, +# "statusCode": code, +# 'headers': { +# "Access-Control-Allow-Origin": '*', +# "Content-Type": "application/json", +# "Referrer-Policy": "origin" +# }, +# "multiValueHeaders": {"Access-Control-Allow-Methods": ["POST", "OPTIONS", "GET", "PATCH", "DELETE"]}, +# "body": body +# } + + +# def resp_handler(func): +# def handle(*args, **kwargs): +# try: +# resp = func(*args, **kwargs) +# return api_response(200, resp) +# except LookupError: +# resp = JsonPayloadBuilder().add_status(False).add_data(None) \ +# .add_message("Not found").compile() +# return api_response(404, resp) +# except Exception as e: +# logging.error(str(e)) +# resp = JsonPayloadBuilder().add_status(False).add_data(None) \ +# .add_message("Internal error, please contact bugs@wasedatime.com.").compile() +# return api_response(500, resp) + +# return handle