pg-mvc-service 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/PoolManager.js +57 -0
- package/dist/Service.js +257 -0
- package/dist/clients/AwsS3Client.js +249 -0
- package/dist/clients/Base64Client.js +153 -0
- package/dist/clients/EncryptClient.js +85 -0
- package/dist/clients/StringClient.js +13 -0
- package/dist/documents/Swagger.js +94 -0
- package/dist/exceptions/Exception.js +53 -0
- package/dist/index.js +16 -0
- package/dist/models/MigrateDatabase.js +138 -0
- package/dist/models/MigrateRollback.js +146 -0
- package/dist/models/MigrateTable.js +51 -0
- package/dist/models/SqlUtils/SelectExpression.js +92 -0
- package/dist/models/SqlUtils/ValidateValueUtil.js +250 -0
- package/dist/models/SqlUtils/WhereExpression.js +256 -0
- package/dist/models/TableDoc.js +353 -0
- package/dist/models/TableModel.js +636 -0
- package/dist/models/Type.js +2 -0
- package/dist/models/Utils/DateTimeUtil.js +134 -0
- package/dist/models/Utils/NumberUtil.js +28 -0
- package/dist/models/Utils/StringUtil.js +31 -0
- package/dist/models/ValidateClient.js +164 -0
- package/dist/models/index.js +14 -0
- package/dist/reqestResponse/ReqResType.js +196 -0
- package/dist/reqestResponse/RequestType.js +742 -0
- package/dist/reqestResponse/ResponseType.js +380 -0
- package/index.d.ts +306 -0
- package/package.json +36 -0
- package/src/PoolManager.ts +48 -0
- package/src/Service.ts +251 -0
- package/src/clients/AwsS3Client.ts +229 -0
- package/src/clients/Base64Client.ts +155 -0
- package/src/clients/EncryptClient.ts +100 -0
- package/src/clients/StringClient.ts +14 -0
- package/src/documents/Swagger.ts +111 -0
- package/src/exceptions/Exception.ts +54 -0
- package/src/index.ts +7 -0
- package/src/models/MigrateDatabase.ts +135 -0
- package/src/models/MigrateRollback.ts +151 -0
- package/src/models/MigrateTable.ts +56 -0
- package/src/models/SqlUtils/SelectExpression.ts +97 -0
- package/src/models/SqlUtils/ValidateValueUtil.ts +270 -0
- package/src/models/SqlUtils/WhereExpression.ts +286 -0
- package/src/models/TableDoc.ts +360 -0
- package/src/models/TableModel.ts +713 -0
- package/src/models/Type.ts +59 -0
- package/src/models/Utils/DateTimeUtil.ts +146 -0
- package/src/models/Utils/NumberUtil.ts +23 -0
- package/src/models/Utils/StringUtil.ts +33 -0
- package/src/models/ValidateClient.ts +182 -0
- package/src/models/index.ts +7 -0
- package/src/reqestResponse/ReqResType.ts +242 -0
- package/src/reqestResponse/RequestType.ts +851 -0
- package/src/reqestResponse/ResponseType.ts +418 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { PDFDocument } from 'pdf-lib';
|
|
2
|
+
import sharp from 'sharp';
|
|
3
|
+
|
|
4
|
+
export type TPng = 'image/png';
|
|
5
|
+
export type TJpeg = 'image/jpeg';
|
|
6
|
+
export type TGif = 'image/gif';
|
|
7
|
+
export type TImage = TPng | TJpeg | TGif;
|
|
8
|
+
|
|
9
|
+
export type TPdf = 'application/pdf';
|
|
10
|
+
export type TJson = 'application/json';
|
|
11
|
+
|
|
12
|
+
export default class Base64Client {
|
|
13
|
+
constructor() { }
|
|
14
|
+
|
|
15
|
+
// public encode(text: string): string {
|
|
16
|
+
// return Buffer.from(text).toString('base64');
|
|
17
|
+
// }
|
|
18
|
+
|
|
19
|
+
public tryDecode(base64: string): Buffer<ArrayBuffer> | false {
|
|
20
|
+
try {
|
|
21
|
+
// Data URLのパターンをチェック
|
|
22
|
+
if (base64.startsWith('data:')) {
|
|
23
|
+
const matches = base64.match(/^data:([A-Za-z-+/]+);base64,(.+)$/);
|
|
24
|
+
if (!matches || matches.length !== 3) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
// base64部分のみを取得
|
|
28
|
+
base64 = matches[2];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (base64.length % 4 !== 0) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
36
|
+
if (!regex.test(base64)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Buffer.from(base64, 'base64');
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public getMimeType(data: string | Buffer<ArrayBuffer>): TImage | TPdf {
|
|
47
|
+
try {
|
|
48
|
+
let buffer;
|
|
49
|
+
if (typeof data === 'string') {
|
|
50
|
+
// Data URLのパターンをチェック
|
|
51
|
+
if (data.startsWith('data:')) {
|
|
52
|
+
const matches = data.match(/^data:([A-Za-z-+/]+);base64,(.+)$/);
|
|
53
|
+
if (!matches || matches.length !== 3) {
|
|
54
|
+
throw new Error('Invalid Data URL format');
|
|
55
|
+
}
|
|
56
|
+
// base64部分のみを取得
|
|
57
|
+
data = matches[2];
|
|
58
|
+
}
|
|
59
|
+
buffer = this.tryDecode(data);
|
|
60
|
+
} else {
|
|
61
|
+
buffer = data;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (buffer === false) {
|
|
65
|
+
throw new Error('Cannot getMineType because the input is not in base64 format.');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const header = buffer.subarray(0, 4);
|
|
69
|
+
if (header[0] === 0x25 && header[1] === 0x50 && header[2] === 0x44 && header[3] === 0x46) {
|
|
70
|
+
return 'application/pdf';
|
|
71
|
+
} else if (header[0] === 0x89 && header[1] === 0x50 && header[2] === 0x4E && header[3] === 0x47) {
|
|
72
|
+
return 'image/png';
|
|
73
|
+
} else if (header[0] === 0xFF && header[1] === 0xD8) {
|
|
74
|
+
return 'image/jpeg';
|
|
75
|
+
} else if (header[0] === 0x47 && header[1] === 0x49 && header[2] === 0x46 && header[3] === 0x38) {
|
|
76
|
+
return 'image/gif';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw new Error('Cannot getMimeType because the file type is not PDF, PNG, JPEG, or GIF.');
|
|
80
|
+
} catch {
|
|
81
|
+
throw new Error('Cannot getMineType because the input is not in base64 format.');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async mergeToPdfBase64(datas: string[]): Promise<string> {
|
|
86
|
+
const mergedPdf = await PDFDocument.create();
|
|
87
|
+
|
|
88
|
+
for (const data of datas) {
|
|
89
|
+
const buffer = this.tryDecode(data);
|
|
90
|
+
if (buffer === false) {
|
|
91
|
+
throw new Error('Cannot mergeToPdf because the input is not in base64 format.');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const fileType = this.getMimeType(buffer);
|
|
95
|
+
if (fileType === 'application/pdf') {
|
|
96
|
+
const pdfDoc = await PDFDocument.load(buffer);
|
|
97
|
+
const pages = await mergedPdf.copyPages(pdfDoc, pdfDoc.getPageIndices());
|
|
98
|
+
pages.forEach(page => mergedPdf.addPage(page));
|
|
99
|
+
} else {
|
|
100
|
+
// convert from image to pdf
|
|
101
|
+
const imagePdf = await this.convertImageToPdf(buffer);
|
|
102
|
+
const pdfDoc = await PDFDocument.load(imagePdf);
|
|
103
|
+
const pages = await mergedPdf.copyPages(pdfDoc, pdfDoc.getPageIndices());
|
|
104
|
+
pages.forEach(page => mergedPdf.addPage(page));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 結合したPDFをBase64に変換
|
|
109
|
+
const mergedPdfBytes = await mergedPdf.save();
|
|
110
|
+
return Buffer.from(mergedPdfBytes).toString('base64');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private async convertImageToPdf(imageBuffer: Buffer): Promise<Buffer> {
|
|
114
|
+
const fileType = this.getMimeType(imageBuffer);
|
|
115
|
+
|
|
116
|
+
let optimizedImage;
|
|
117
|
+
if (fileType === 'image/gif') {
|
|
118
|
+
// gifの場合はpngに変換して処理
|
|
119
|
+
optimizedImage = await sharp(imageBuffer)
|
|
120
|
+
.png()
|
|
121
|
+
.toBuffer();
|
|
122
|
+
} else {
|
|
123
|
+
optimizedImage = await sharp(imageBuffer).toBuffer();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 新しいPDFドキュメントを作成
|
|
127
|
+
const pdfDoc = await PDFDocument.create();
|
|
128
|
+
const page = pdfDoc.addPage();
|
|
129
|
+
|
|
130
|
+
// 画像をPDFに埋め込み
|
|
131
|
+
|
|
132
|
+
let image;
|
|
133
|
+
if (fileType === 'image/jpeg') {
|
|
134
|
+
image = await pdfDoc.embedJpg(optimizedImage);
|
|
135
|
+
} else {
|
|
136
|
+
image = await pdfDoc.embedPng(optimizedImage);
|
|
137
|
+
}
|
|
138
|
+
const { width, height } = image.scale(1);
|
|
139
|
+
|
|
140
|
+
// ページサイズを画像に合わせる
|
|
141
|
+
page.setSize(width, height);
|
|
142
|
+
|
|
143
|
+
// 画像を描画
|
|
144
|
+
page.drawImage(image, {
|
|
145
|
+
x: 0,
|
|
146
|
+
y: 0,
|
|
147
|
+
width,
|
|
148
|
+
height,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// PDFをバッファに変換
|
|
152
|
+
const pdfBytes = await pdfDoc.save();
|
|
153
|
+
return Buffer.from(pdfBytes);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
export default class EncryptClient {
|
|
4
|
+
private secretKeyHex?: string;
|
|
5
|
+
get SecretKey(): Buffer<ArrayBuffer> {
|
|
6
|
+
if (this.secretKeyHex === undefined) {
|
|
7
|
+
throw new Error("Please set the secret key.");
|
|
8
|
+
}
|
|
9
|
+
return Buffer.from(this.secretKeyHex, 'hex');
|
|
10
|
+
}
|
|
11
|
+
private hmacKeyBase64?: string;
|
|
12
|
+
get HmacKey(): Buffer<ArrayBuffer> {
|
|
13
|
+
if (this.hmacKeyBase64 === undefined) {
|
|
14
|
+
throw new Error("Please set the hmac key.");
|
|
15
|
+
}
|
|
16
|
+
return Buffer.from(this.hmacKeyBase64, 'base64');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor(params: {secretKeyHex?: string, hmacKeyBase64?: string}) {
|
|
20
|
+
this.secretKeyHex = params?.secretKeyHex;
|
|
21
|
+
this.hmacKeyBase64 = params?.hmacKeyBase64;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public encryptAndSign(data: string | {[key: string]: any}): string {
|
|
25
|
+
const iv = crypto.randomBytes(12);
|
|
26
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', this.SecretKey, iv);
|
|
27
|
+
|
|
28
|
+
const plaintext = typeof data === 'string' ? data : JSON.stringify(data);
|
|
29
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
30
|
+
const authTag = cipher.getAuthTag();
|
|
31
|
+
|
|
32
|
+
const combined = Buffer.concat([iv, authTag, encrypted]);
|
|
33
|
+
const payload = this.base64urlEncode(combined);
|
|
34
|
+
|
|
35
|
+
const hmac = crypto.createHmac('sha256', this.HmacKey).update(payload).digest();
|
|
36
|
+
const signature = this.base64urlEncode(hmac);
|
|
37
|
+
|
|
38
|
+
return `${payload}.${signature}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public isValidToken(token: string): boolean {
|
|
42
|
+
// 形式チェック、.で区切られているか?
|
|
43
|
+
const [payload, signature] = token.split('.');
|
|
44
|
+
if (!payload || !signature) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 改竄チェック
|
|
49
|
+
const expectedSig = crypto.createHmac('sha256', this.HmacKey).update(payload).digest();
|
|
50
|
+
const expectedSigStr = this.base64urlEncode(expectedSig);
|
|
51
|
+
|
|
52
|
+
if (signature !== expectedSigStr) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public decrypt<T>(token: string): T {
|
|
60
|
+
// 形式チェック、.で区切られているか?
|
|
61
|
+
const [payload, signature] = token.split('.');
|
|
62
|
+
if (!payload || !signature) {
|
|
63
|
+
throw new Error('Invalid token format');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 改竄チェック
|
|
67
|
+
const expectedSig = crypto.createHmac('sha256', this.HmacKey).update(payload).digest();
|
|
68
|
+
const expectedSigStr = this.base64urlEncode(expectedSig);
|
|
69
|
+
if (signature !== expectedSigStr){
|
|
70
|
+
throw new Error('The token appears to have been tampered with');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const combined = this.base64urlDecode(payload);
|
|
74
|
+
const iv = combined.subarray(0, 12);
|
|
75
|
+
const authTag = combined.subarray(12, 28);
|
|
76
|
+
const encrypted = combined.subarray(28);
|
|
77
|
+
|
|
78
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', this.SecretKey, iv);
|
|
79
|
+
decipher.setAuthTag(authTag);
|
|
80
|
+
|
|
81
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
82
|
+
return JSON.parse(decrypted.toString('utf8')) as T;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
private base64urlEncode(buffer: Buffer): string {
|
|
87
|
+
return buffer.toString('base64')
|
|
88
|
+
.replace(/\+/g, '-')
|
|
89
|
+
.replace(/\//g, '_')
|
|
90
|
+
.replace(/=+$/, '');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private base64urlDecode(str: string): Buffer {
|
|
94
|
+
str = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
95
|
+
while (str.length % 4 !== 0) {
|
|
96
|
+
str += '=';
|
|
97
|
+
}
|
|
98
|
+
return Buffer.from(str, 'base64');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
export default class StringClient {
|
|
4
|
+
constructor() { }
|
|
5
|
+
|
|
6
|
+
public generateUUIDv7(): string {
|
|
7
|
+
const timestamp = BigInt(Date.now()) * BigInt(10000) + BigInt(process.hrtime.bigint() % BigInt(10000));
|
|
8
|
+
const timeHex = timestamp.toString(16).padStart(16, '0');
|
|
9
|
+
|
|
10
|
+
const randomHex = randomBytes(8).toString('hex');
|
|
11
|
+
|
|
12
|
+
return `${timeHex.slice(0, 8)}-${timeHex.slice(8, 12)}-7${timeHex.slice(13, 16)}-${randomHex.slice(0, 4)}-${randomHex.slice(4)}`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Service } from '../Service';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export interface IParams {
|
|
5
|
+
in: 'header' | 'path',
|
|
6
|
+
name: string,
|
|
7
|
+
require?: boolean,
|
|
8
|
+
description?: string,
|
|
9
|
+
example?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const createSwagger = (services: Service[], name: string, url: string, params: Array<IParams> = []): string => {
|
|
13
|
+
// *****************************************
|
|
14
|
+
// Internal method definitions
|
|
15
|
+
// *****************************************
|
|
16
|
+
function setYmlByMethod(method: string, swaggerYmlObj: {[key: string]: string}) {
|
|
17
|
+
if (method in swaggerYmlObj) {
|
|
18
|
+
swaggerInfo += ` ${method.toLowerCase()}:\n`;
|
|
19
|
+
swaggerInfo += swaggerYmlObj[method];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// *****************************************
|
|
23
|
+
// Execution part
|
|
24
|
+
// *****************************************
|
|
25
|
+
const endpontSwaggerYml: {[keyEndpoint: string]: {[keyMethod: string]: string}} = {};
|
|
26
|
+
let tags: Array<string> = [];
|
|
27
|
+
|
|
28
|
+
for (const service of services) {
|
|
29
|
+
if (service.Endpoint in endpontSwaggerYml === false) {
|
|
30
|
+
endpontSwaggerYml[service.Endpoint] = {};
|
|
31
|
+
}
|
|
32
|
+
let yml = "";
|
|
33
|
+
|
|
34
|
+
const splitEndpont = service.Endpoint.split('/');
|
|
35
|
+
let tagName = splitEndpont[0];
|
|
36
|
+
if (tagName === '' && splitEndpont.length > 1) {
|
|
37
|
+
tagName = splitEndpont[1];;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const apiTags = service.Tags;
|
|
41
|
+
if (apiTags.length > 0) {
|
|
42
|
+
tags = [ ...tags, ...apiTags];
|
|
43
|
+
yml += ` tags:\n`;
|
|
44
|
+
for (const tag of apiTags) {
|
|
45
|
+
yml += ` - ${tag}\n`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
yml += ` summary: ${service.Summary}\n`;
|
|
49
|
+
|
|
50
|
+
const croneParams = [...params];
|
|
51
|
+
for (const path of service.Endpoint.split('/')) {
|
|
52
|
+
if (path.includes('{') && path.includes('}')) {
|
|
53
|
+
const key = path.replace('{', '').replace('}', '');
|
|
54
|
+
croneParams.push({
|
|
55
|
+
in: 'path',
|
|
56
|
+
name: key,
|
|
57
|
+
require: true,
|
|
58
|
+
description: key,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (croneParams.length > 0) {
|
|
64
|
+
yml += ` parameters:\n`;
|
|
65
|
+
for (const param of croneParams) {
|
|
66
|
+
yml += ` - in: ${param.in}\n`;
|
|
67
|
+
yml += ` name: ${param.name}\n`;
|
|
68
|
+
yml += ` required: ${param.require === true ? 'true' : 'false'}\n`;
|
|
69
|
+
if (param.description !== undefined) {
|
|
70
|
+
yml += ` description: |\n ${param.description}\n`;
|
|
71
|
+
}
|
|
72
|
+
if (param.example !== undefined) {
|
|
73
|
+
yml += ` example: ${param.example}\n`;
|
|
74
|
+
}
|
|
75
|
+
yml += ` schema:\n`;
|
|
76
|
+
yml += ` type: string\n`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
yml += service.Request.createSwagger(service.Method);
|
|
81
|
+
yml += service.Response.createSwagger();
|
|
82
|
+
|
|
83
|
+
endpontSwaggerYml[service.Endpoint][service.Method] = yml;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let swaggerInfo = `openapi: 3.0.0
|
|
87
|
+
info:
|
|
88
|
+
title: Your API Documentation
|
|
89
|
+
version: 1.0.0
|
|
90
|
+
description: API documentation for your service
|
|
91
|
+
servers:
|
|
92
|
+
- url: ${url}
|
|
93
|
+
description: ${name} API IF定義書
|
|
94
|
+
tags:\n`;
|
|
95
|
+
for (const tag of tags) {
|
|
96
|
+
swaggerInfo += ` - name: ${tag}\n`;
|
|
97
|
+
}
|
|
98
|
+
swaggerInfo += 'paths:\n'
|
|
99
|
+
|
|
100
|
+
for (const keyEndpoint in endpontSwaggerYml) {
|
|
101
|
+
swaggerInfo += ` ${keyEndpoint}:\n`;
|
|
102
|
+
|
|
103
|
+
setYmlByMethod('GET', endpontSwaggerYml[keyEndpoint]);
|
|
104
|
+
setYmlByMethod('POST', endpontSwaggerYml[keyEndpoint]);
|
|
105
|
+
setYmlByMethod('PUT', endpontSwaggerYml[keyEndpoint]);
|
|
106
|
+
setYmlByMethod('PATCH', endpontSwaggerYml[keyEndpoint]);
|
|
107
|
+
setYmlByMethod('DELETE', endpontSwaggerYml[keyEndpoint]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return swaggerInfo;
|
|
111
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export class AuthException extends Error {
|
|
2
|
+
private id: string = "";
|
|
3
|
+
get Id(): string {
|
|
4
|
+
return this.id;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
constructor(id: string, message: string = "") {
|
|
8
|
+
super(message);
|
|
9
|
+
this.id = id;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ForbiddenException extends Error {
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class InputErrorException extends Error {
|
|
17
|
+
|
|
18
|
+
private errorId: string = "";
|
|
19
|
+
get ErrorId(): string {
|
|
20
|
+
return this.errorId;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private errorLog: string = "";
|
|
24
|
+
get ErrorLog(): string {
|
|
25
|
+
return this.errorLog;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
constructor(errorId: string, message: string = "", errorLog: string = "") {
|
|
29
|
+
super(message);
|
|
30
|
+
this.errorId = errorId;
|
|
31
|
+
this.errorLog = errorLog;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class MaintenanceException extends Error {
|
|
36
|
+
|
|
37
|
+
constructor(message: string = "") {
|
|
38
|
+
super(message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class DbConflictException extends Error {
|
|
43
|
+
// for 409 Conflict
|
|
44
|
+
constructor(message: string = "") {
|
|
45
|
+
super(message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class BusinessLogicException extends Error {
|
|
50
|
+
// for 422 Unprocessable Entity
|
|
51
|
+
constructor(message: string = "") {
|
|
52
|
+
super(message);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Service } from './Service';
|
|
2
|
+
export { MaintenanceException, AuthException, InputErrorException, ForbiddenException } from './exceptions/Exception';
|
|
3
|
+
export { createSwagger } from './documents/Swagger';
|
|
4
|
+
|
|
5
|
+
export { RequestType } from './reqestResponse/RequestType';
|
|
6
|
+
export { ResponseType } from './reqestResponse/ResponseType';
|
|
7
|
+
export { PropertyType } from './reqestResponse/ReqResType';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Pool } from "pg";
|
|
2
|
+
|
|
3
|
+
export class MigrateDatabase {
|
|
4
|
+
|
|
5
|
+
private dbName: string;
|
|
6
|
+
get DbName(): string { return this.dbName; }
|
|
7
|
+
private userName: string;
|
|
8
|
+
get UserName(): string { return this.userName; }
|
|
9
|
+
private password: string | null = null;
|
|
10
|
+
get Password(): string | null {
|
|
11
|
+
return this.password;
|
|
12
|
+
}
|
|
13
|
+
private pool: Pool;
|
|
14
|
+
|
|
15
|
+
constructor (dbName: string, userName: string, pool: Pool) {
|
|
16
|
+
this.dbName = dbName;
|
|
17
|
+
this.userName = userName;
|
|
18
|
+
this.pool = pool;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public async IsExistUser(): Promise<boolean> {
|
|
22
|
+
const sql = `
|
|
23
|
+
SELECT count(*) > 0 as is_exist
|
|
24
|
+
FROM pg_roles
|
|
25
|
+
WHERE rolname = '${this.UserName}';
|
|
26
|
+
`;
|
|
27
|
+
const datas = await this.pool.query(sql);
|
|
28
|
+
return datas.rows[0].is_exist;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public async CreateUser(password: string = ''): Promise<void> {
|
|
32
|
+
if (password.trim() === '') {
|
|
33
|
+
password = '';
|
|
34
|
+
|
|
35
|
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@$%^&*_+|;:.<>?';
|
|
36
|
+
for (let i = 0; i < 36; i++) {
|
|
37
|
+
const randomIndex = Math.floor(Math.random() * characters.length);
|
|
38
|
+
password += characters[randomIndex];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
this.password = password;
|
|
42
|
+
|
|
43
|
+
const sql = `
|
|
44
|
+
DO $$
|
|
45
|
+
BEGIN
|
|
46
|
+
IF NOT EXISTS (
|
|
47
|
+
SELECT FROM pg_catalog.pg_roles WHERE rolname = '${this.UserName}'
|
|
48
|
+
) THEN
|
|
49
|
+
CREATE USER ${this.UserName} WITH PASSWORD '${password}';
|
|
50
|
+
END IF;
|
|
51
|
+
END
|
|
52
|
+
$$;
|
|
53
|
+
|
|
54
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO ${this.UserName};
|
|
55
|
+
|
|
56
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
57
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${this.UserName};
|
|
58
|
+
`;
|
|
59
|
+
await this.pool.query(sql);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public async IsExistDb(): Promise<boolean> {
|
|
63
|
+
const sql = `
|
|
64
|
+
SELECT count(*) > 0 as is_exist
|
|
65
|
+
FROM pg_database
|
|
66
|
+
WHERE datname = '${this.DbName}';
|
|
67
|
+
`;
|
|
68
|
+
const datas = await this.pool.query(sql);
|
|
69
|
+
return datas.rows[0].is_exist;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public async CreateDb(collateType: string = 'C'): Promise<void> {
|
|
73
|
+
const sql = `
|
|
74
|
+
CREATE DATABASE ${this.DbName}
|
|
75
|
+
WITH OWNER = ${this.UserName}
|
|
76
|
+
ENCODING = 'UTF8'
|
|
77
|
+
LC_COLLATE = '${collateType}'
|
|
78
|
+
LC_CTYPE = '${collateType}'
|
|
79
|
+
CONNECTION LIMIT = -1;
|
|
80
|
+
`;
|
|
81
|
+
await this.pool.query(sql);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public RollbackDbSql(): string {
|
|
85
|
+
const sql = `
|
|
86
|
+
-- ${this.DbName}データベースに接続しているすべてのセッションを強制終了
|
|
87
|
+
SELECT pg_terminate_backend(pid)
|
|
88
|
+
FROM pg_stat_activity
|
|
89
|
+
WHERE datname = '${this.DbName}';
|
|
90
|
+
|
|
91
|
+
-- DB削除
|
|
92
|
+
DROP DATABASE IF EXISTS ${this.DbName};`;
|
|
93
|
+
return this.trimSpaceLineSql(sql);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public RollbackUserSql(otherUserName: string): string {
|
|
97
|
+
const sql = `
|
|
98
|
+
-- 1. すべてのセッションを強制終了
|
|
99
|
+
SELECT pg_terminate_backend(pg_stat_activity.pid)
|
|
100
|
+
FROM pg_stat_activity
|
|
101
|
+
WHERE usename = '${this.UserName}';
|
|
102
|
+
|
|
103
|
+
-- 2. 所有オブジェクトを ${otherUserName} に移行
|
|
104
|
+
REASSIGN OWNED BY ${this.UserName} TO ${otherUserName};
|
|
105
|
+
|
|
106
|
+
-- 2. すべての権限を削除
|
|
107
|
+
DROP OWNED BY ${this.UserName} CASCADE;
|
|
108
|
+
|
|
109
|
+
-- 3. ロールを削除
|
|
110
|
+
DROP ROLE IF EXISTS ${this.UserName};`;
|
|
111
|
+
return this.trimSpaceLineSql(sql);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private trimSpaceLineSql(str: string) {
|
|
115
|
+
const splitLines = str.split('\n');
|
|
116
|
+
let sql = '';
|
|
117
|
+
for (let line of splitLines) {
|
|
118
|
+
line = line.replace(/\s+/g, ' ').trim(); // 複数のスペースを一つに置き換え
|
|
119
|
+
|
|
120
|
+
if (line.startsWith('--') && sql[sql.length - 1] != '\n') {
|
|
121
|
+
line = '\n' + line;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (line.length > 0) {
|
|
125
|
+
if (line.includes('--') === false) {
|
|
126
|
+
sql += line + ' ';
|
|
127
|
+
} else {
|
|
128
|
+
sql += line + '\n';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return sql;
|
|
134
|
+
}
|
|
135
|
+
}
|