@ainias42/typeorm-helper 0.0.1
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/.ctirc +112 -0
- package/.eslintrc.cjs +3 -0
- package/.prettierrc +1 -0
- package/bin/release.sh +3 -0
- package/bin/updateCopies.js +87 -0
- package/package.json +30 -0
- package/src/BaseModel.ts +24 -0
- package/src/DbNamingStrategy.ts +63 -0
- package/src/ServerSubscriber.ts +95 -0
- package/src/dataSource.ts +17 -0
- package/src/decorators/FileColumn/FileColumn.ts +58 -0
- package/src/decorators/FileColumn/FileTransformer.ts +8 -0
- package/src/decorators/FileColumn/FileType.ts +5 -0
- package/src/decorators/FileColumn/FileWriter.ts +47 -0
- package/src/index.ts +9 -0
- package/src/migration/getCreateTableColumns.ts +27 -0
- package/tsconfig.json +35 -0
package/.ctirc
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Stream of cli spinner, you can pass stdout or stderr
|
|
3
|
+
//
|
|
4
|
+
// @mode all
|
|
5
|
+
// @default stdout
|
|
6
|
+
"spinnerStream": "stdout",
|
|
7
|
+
// Stream of cli progress, you can pass stdout or stderr
|
|
8
|
+
//
|
|
9
|
+
// @mode all
|
|
10
|
+
// @default stdout
|
|
11
|
+
"progressStream": "stdout",
|
|
12
|
+
// Stream of cli reasoner. Reasoner show name conflict error and already exist index.ts file error.
|
|
13
|
+
// You can pass stdout or stderr
|
|
14
|
+
//
|
|
15
|
+
// @mode all
|
|
16
|
+
// @default stderr
|
|
17
|
+
"reasonerStream": "stderr",
|
|
18
|
+
"options": [
|
|
19
|
+
{
|
|
20
|
+
// build mode
|
|
21
|
+
// - create: create an `index.ts` file in each directory
|
|
22
|
+
// - bundle: bundle all export information in one `index.ts` file
|
|
23
|
+
"mode": "bundle",
|
|
24
|
+
// tsconfig.json path: you must pass path with filename, like this "./tsconfig.json"
|
|
25
|
+
// only work root directory or cli parameter
|
|
26
|
+
//
|
|
27
|
+
// @mode all
|
|
28
|
+
"project": "tsconfig.json",
|
|
29
|
+
// Export filename, if you not pass this field that use "index.ts" or "index.d.ts"
|
|
30
|
+
//
|
|
31
|
+
// @mode create, bundle, remove
|
|
32
|
+
// @default index.ts
|
|
33
|
+
"exportFilename": "index.ts",
|
|
34
|
+
// add ctix comment at first line of creted index.ts file, that remark created from ctix
|
|
35
|
+
//
|
|
36
|
+
// @mode create, bundle
|
|
37
|
+
// @default false
|
|
38
|
+
"useSemicolon": true,
|
|
39
|
+
// add ctix comment at first line of creted index.ts file, that remark created from ctix
|
|
40
|
+
//
|
|
41
|
+
// @mode create, bundle
|
|
42
|
+
// @default false
|
|
43
|
+
"useBanner": false,
|
|
44
|
+
// If specified as true, adds the created date to the top of the `index.ts` file,
|
|
45
|
+
// this option only works if the `useBanner` option is enabled
|
|
46
|
+
//
|
|
47
|
+
// @mode create, bundle
|
|
48
|
+
// @default false
|
|
49
|
+
"useTimestamp": false,
|
|
50
|
+
// quote mark " or '
|
|
51
|
+
// @mode create, bundle
|
|
52
|
+
//
|
|
53
|
+
// @default '
|
|
54
|
+
"quote": "'",
|
|
55
|
+
// Use to add a literal like `"use strict"` to the top. It will be added before the banner.
|
|
56
|
+
//
|
|
57
|
+
// @mode create, bundle
|
|
58
|
+
"directive": "",
|
|
59
|
+
// keep file extension in export statement path
|
|
60
|
+
//
|
|
61
|
+
// if this option set true that see below
|
|
62
|
+
// export * from './test.ts'
|
|
63
|
+
//
|
|
64
|
+
// @mode create, bundle
|
|
65
|
+
// @default none
|
|
66
|
+
"fileExt": "none",
|
|
67
|
+
// overwrite each index.ts file
|
|
68
|
+
// @mode create, bundle
|
|
69
|
+
// @default false
|
|
70
|
+
"overwrite": true,
|
|
71
|
+
// Create a backup file if the `index.ts` file already exists.
|
|
72
|
+
// This option only works if the `overwrite` option is enabled.
|
|
73
|
+
//
|
|
74
|
+
// @mode create, bundle
|
|
75
|
+
// @default true
|
|
76
|
+
"backup": false,
|
|
77
|
+
// When generating the `index.ts` file, decide how you want to generate it
|
|
78
|
+
//
|
|
79
|
+
// @mode create, bundle
|
|
80
|
+
// @default auto
|
|
81
|
+
"generationStyle": "auto",
|
|
82
|
+
// A list of files to use when generating the index.ts file. If no value is set,
|
|
83
|
+
// the value of the include setting set in the tsconfig.json file will be used
|
|
84
|
+
//
|
|
85
|
+
// @mode create, bundle
|
|
86
|
+
"include": [
|
|
87
|
+
"src/**/*.tsx",
|
|
88
|
+
"src/**/*.ts"
|
|
89
|
+
],
|
|
90
|
+
// A list of files to exclude when generating the index.ts file. If no value is set,
|
|
91
|
+
// the value of the exclude setting set in the tsconfig.json file is used
|
|
92
|
+
//
|
|
93
|
+
// @mode create, bundle
|
|
94
|
+
"exclude": [
|
|
95
|
+
"../../node_modules",
|
|
96
|
+
"dist/**/*",
|
|
97
|
+
"dist/src/**/*"
|
|
98
|
+
],
|
|
99
|
+
// Output directory. Default value is same project directory
|
|
100
|
+
// @mode bundle
|
|
101
|
+
"output": "./src",
|
|
102
|
+
// remove with backup file
|
|
103
|
+
// @mode remove
|
|
104
|
+
// @default false
|
|
105
|
+
"removeBackup": false,
|
|
106
|
+
// answer `yes` to all questions
|
|
107
|
+
// @mode remove
|
|
108
|
+
// @default false
|
|
109
|
+
"forceYes": false
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
}
|
package/.eslintrc.cjs
ADDED
package/.prettierrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"@ainia42/prettier-config"
|
package/bin/release.sh
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import {exec} from "child_process";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const packageName = JSON.parse(fs.readFileSync(__dirname+"/../package.json", {encoding: "utf8"})).name;
|
|
9
|
+
|
|
10
|
+
let pathsToProjects = [
|
|
11
|
+
// '/home/silas/Projekte/web/dnd',
|
|
12
|
+
// '/home/silas/Projekte/web/smd-mail',
|
|
13
|
+
// '/home/silas/Projekte/web/games',
|
|
14
|
+
// '/home/silas/Projekte/web/react-windows',
|
|
15
|
+
// '/home/silas/Projekte/web/cordova-sites',
|
|
16
|
+
"/Users/sguenter/Projekte/Privat/dnd",
|
|
17
|
+
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const deleteFolderRecursive = function (path) {
|
|
21
|
+
if (fs.existsSync(path)) {
|
|
22
|
+
fs.readdirSync(path).forEach(function (file) {
|
|
23
|
+
let curPath = path + '/' + file;
|
|
24
|
+
if (fs.lstatSync(curPath).isDirectory()) {
|
|
25
|
+
// recurse
|
|
26
|
+
deleteFolderRecursive(curPath);
|
|
27
|
+
} else {
|
|
28
|
+
// delete file
|
|
29
|
+
fs.unlinkSync(curPath);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
fs.rmdirSync(path);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
async function execPromise(command) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
console.log('executing ' + command + '...');
|
|
39
|
+
exec(command, (err, stdout, stderr) => {
|
|
40
|
+
console.log(stdout);
|
|
41
|
+
console.log(stderr);
|
|
42
|
+
if (err) {
|
|
43
|
+
reject([err, stdout, stderr]);
|
|
44
|
+
} else {
|
|
45
|
+
resolve([stdout, stderr]);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
execPromise('npm pack')
|
|
52
|
+
.then(async (std) => {
|
|
53
|
+
let thisPath = process.cwd();
|
|
54
|
+
let name = std[0].trim();
|
|
55
|
+
let pathToTar = path.resolve(thisPath, name);
|
|
56
|
+
|
|
57
|
+
if (!fs.existsSync('tmp')) {
|
|
58
|
+
fs.mkdirSync('tmp');
|
|
59
|
+
}
|
|
60
|
+
process.chdir('tmp');
|
|
61
|
+
await execPromise('tar -xvzf ' + pathToTar + ' -C ./');
|
|
62
|
+
process.chdir('package');
|
|
63
|
+
// fs.unlinkSync('package.json');
|
|
64
|
+
|
|
65
|
+
let promise = Promise.resolve();
|
|
66
|
+
pathsToProjects.forEach((project) => {
|
|
67
|
+
promise = promise.then(async () => {
|
|
68
|
+
let resultDir = path.resolve(project, 'node_modules', packageName);
|
|
69
|
+
console.log(resultDir, fs.existsSync(resultDir));
|
|
70
|
+
if (!fs.existsSync(resultDir)) {
|
|
71
|
+
fs.mkdirSync(resultDir);
|
|
72
|
+
}
|
|
73
|
+
return execPromise('cp -r ./* ' + resultDir);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
await promise;
|
|
77
|
+
|
|
78
|
+
process.chdir(thisPath);
|
|
79
|
+
fs.unlinkSync(name);
|
|
80
|
+
deleteFolderRecursive('tmp');
|
|
81
|
+
// fs.unlinkSync("tmp");
|
|
82
|
+
|
|
83
|
+
console.log('done!');
|
|
84
|
+
})
|
|
85
|
+
.catch((e) => {
|
|
86
|
+
console.error(e);
|
|
87
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ainias42/typeorm-helper",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
+
"build": "ctix build && tsc && tsc-alias",
|
|
12
|
+
"lint:noCache": "eslint \"src/{**/*,*}.{js,jsx,tsx,ts}\" --max-warnings 0",
|
|
13
|
+
"lint": "npm run lint:noCache -- --cache",
|
|
14
|
+
"lint:fix": "npm run lint -- --fix",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "gitea@git.silas.link:Ainias/libraries.git"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@ainias42/js-helper": ">=0.8.15",
|
|
23
|
+
"typeorm": "^0.3.20",
|
|
24
|
+
"reflect-metadata": "^0.2.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"ctix": "^2.6.4",
|
|
28
|
+
"prettier": "^3.3.3"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/BaseModel.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateDateColumn,
|
|
3
|
+
DeleteDateColumn,
|
|
4
|
+
PrimaryGeneratedColumn,
|
|
5
|
+
UpdateDateColumn,
|
|
6
|
+
VersionColumn
|
|
7
|
+
} from "typeorm";
|
|
8
|
+
|
|
9
|
+
export class BaseModel {
|
|
10
|
+
@PrimaryGeneratedColumn()
|
|
11
|
+
id?: number;
|
|
12
|
+
|
|
13
|
+
@CreateDateColumn()
|
|
14
|
+
createdAt?: Date;
|
|
15
|
+
|
|
16
|
+
@UpdateDateColumn()
|
|
17
|
+
updatedAt?: Date;
|
|
18
|
+
|
|
19
|
+
@DeleteDateColumn()
|
|
20
|
+
deletedAt?: Date;
|
|
21
|
+
|
|
22
|
+
@VersionColumn()
|
|
23
|
+
version?: number;
|
|
24
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DefaultNamingStrategy } from "typeorm";
|
|
2
|
+
import type { NamingStrategyInterface, Table } from "typeorm";
|
|
3
|
+
|
|
4
|
+
export class DbNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
|
|
5
|
+
|
|
6
|
+
private createKey(prefix: string, tableOrName: Table | string, columnNames: string[], suffix?: string): string {
|
|
7
|
+
const clonedColumnNames = [...columnNames];
|
|
8
|
+
clonedColumnNames.sort();
|
|
9
|
+
const tableName = this.getTableName(tableOrName);
|
|
10
|
+
const replacedTableName = tableName.replace(".", "_");
|
|
11
|
+
let key = `${prefix}_${replacedTableName}_${clonedColumnNames.join("_")}`;
|
|
12
|
+
if (suffix){
|
|
13
|
+
key += `_${suffix}`;
|
|
14
|
+
}
|
|
15
|
+
return key;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
indexName(tableOrName: Table | string, columnNames: string[], where?: string): string {
|
|
19
|
+
return this.createKey("IDX", tableOrName, columnNames, where);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
foreignKeyName(
|
|
23
|
+
tableOrName: Table | string,
|
|
24
|
+
columnNames: string[],
|
|
25
|
+
): string {
|
|
26
|
+
return this.createKey("FK", tableOrName, columnNames);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
uniqueConstraintName(
|
|
30
|
+
tableOrName: Table | string,
|
|
31
|
+
columnNames: string[],
|
|
32
|
+
): string {
|
|
33
|
+
// There is a bug in the cli. They use the indexName method to generate unique constraint names
|
|
34
|
+
return this.createKey("IDX", tableOrName, columnNames);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
primaryKeyName(tableOrName: Table | string, columnNames: string[]): string {
|
|
38
|
+
return this.createKey("PK", tableOrName, columnNames);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
relationConstraintName(
|
|
42
|
+
tableOrName: Table | string,
|
|
43
|
+
columnNames: string[],
|
|
44
|
+
where?: string,
|
|
45
|
+
): string {
|
|
46
|
+
return this.createKey("REL", tableOrName, columnNames, where);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
checkConstraintName(
|
|
50
|
+
tableOrName: Table | string,
|
|
51
|
+
expression: string,
|
|
52
|
+
isEnum?: boolean,
|
|
53
|
+
): string {
|
|
54
|
+
return this.createKey("CHK", tableOrName, [expression], isEnum ? "ENUM" : undefined);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
exclusionConstraintName(
|
|
58
|
+
tableOrName: Table | string,
|
|
59
|
+
expression: string,
|
|
60
|
+
): string {
|
|
61
|
+
return this.createKey("XCL", tableOrName, [expression]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
import { ColumnMetadata } from "typeorm/metadata/ColumnMetadata";
|
|
3
|
+
import {
|
|
4
|
+
EntitySubscriberInterface,
|
|
5
|
+
EventSubscriber, InsertEvent,
|
|
6
|
+
ObjectLiteral, OptimisticLockVersionMismatchError, UpdateEvent
|
|
7
|
+
} from "typeorm";
|
|
8
|
+
import { FileTransformer } from "./decorators/FileColumn/FileTransformer";
|
|
9
|
+
import { FileType } from "./decorators/FileColumn/FileType";
|
|
10
|
+
import { FileWriter } from "./decorators/FileColumn/FileWriter";
|
|
11
|
+
|
|
12
|
+
@EventSubscriber()
|
|
13
|
+
export class ServerSubscriber implements EntitySubscriberInterface {
|
|
14
|
+
|
|
15
|
+
async saveFiles(entity: ObjectLiteral, columns: ColumnMetadata[]) {
|
|
16
|
+
const promises = [];
|
|
17
|
+
for (const column of columns) {
|
|
18
|
+
const transformer = column.transformer as FileTransformer | undefined;
|
|
19
|
+
if (transformer?.isFile) {
|
|
20
|
+
let values: FileType | FileType[] | undefined = Reflect.get(entity, column.propertyName);
|
|
21
|
+
if (values) {
|
|
22
|
+
let single = false;
|
|
23
|
+
if (!Array.isArray(values)) {
|
|
24
|
+
values = [values];
|
|
25
|
+
single = true;
|
|
26
|
+
}
|
|
27
|
+
promises.push(Promise.all(values.map(value => FileWriter.writeToFile(value.src, transformer.fileOptions.saveDirectory).then(newUrl => {
|
|
28
|
+
return {...value, src: newUrl};
|
|
29
|
+
}))).then(newValues => {
|
|
30
|
+
if (single) {
|
|
31
|
+
Reflect.set(entity, column.propertyName, newValues[0]);
|
|
32
|
+
} else {
|
|
33
|
+
Reflect.set(entity, column.propertyName, newValues);
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
await Promise.all(promises);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Called before post insertion.
|
|
44
|
+
*/
|
|
45
|
+
async beforeInsert({entity, metadata: {columns}}: InsertEvent<any>) {
|
|
46
|
+
if (entity) {
|
|
47
|
+
// TODO check if this is necessary
|
|
48
|
+
// Reflect.set(entity, "updatedAt", new Date());
|
|
49
|
+
// Reflect.set(entity, "createdAt", new Date());
|
|
50
|
+
|
|
51
|
+
await this.saveFiles(entity, columns);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Called before entity update.
|
|
57
|
+
*/
|
|
58
|
+
async beforeUpdate(event: UpdateEvent<any>) {
|
|
59
|
+
// To know if an entity has a version number, we check if versionColumn
|
|
60
|
+
// is defined in the metadatas of that entity.
|
|
61
|
+
if (event.metadata.versionColumn && event.entity && event.databaseEntity) {
|
|
62
|
+
// Getting the current version of the requested entity update
|
|
63
|
+
const versionFromUpdate = Reflect.get(
|
|
64
|
+
event.entity,
|
|
65
|
+
event.metadata.versionColumn.propertyName
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Getting the entity's version from the database
|
|
69
|
+
const versionFromDatabase = event.databaseEntity[event.metadata.versionColumn.propertyName];
|
|
70
|
+
|
|
71
|
+
// they should match otherwise someone has changed it underneath us
|
|
72
|
+
if (versionFromDatabase !== versionFromUpdate) {
|
|
73
|
+
throw new OptimisticLockVersionMismatchError(
|
|
74
|
+
event.metadata.name,
|
|
75
|
+
versionFromDatabase,
|
|
76
|
+
versionFromUpdate
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (event.entity) {
|
|
82
|
+
Reflect.set(event.entity, "updatedAt", new Date());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const {columns} = event.metadata;
|
|
86
|
+
const {entity} = event;
|
|
87
|
+
if (entity) {
|
|
88
|
+
await this.saveFiles(entity, columns);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// beforeRemove({metadata, entity, ...other}: RemoveEvent<any>): Promise<any> | void {
|
|
93
|
+
// TODO Remove files from server (?)
|
|
94
|
+
// }
|
|
95
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DataSource } from "typeorm";
|
|
2
|
+
import type { DataSourceOptions } from "typeorm/data-source/DataSourceOptions";
|
|
3
|
+
|
|
4
|
+
let dataSource: DataSource | undefined;
|
|
5
|
+
|
|
6
|
+
export async function initDataSource(options: DataSourceOptions) {
|
|
7
|
+
dataSource = new DataSource(options);
|
|
8
|
+
await dataSource.initialize();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getDataSource() {
|
|
12
|
+
if (!dataSource || !dataSource.isInitialized) {
|
|
13
|
+
throw new Error("Data source is not initialized");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return dataSource;
|
|
17
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getMetadataArgsStorage } from "typeorm";
|
|
2
|
+
import type { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
|
|
3
|
+
import type { FileType } from "./FileType";
|
|
4
|
+
|
|
5
|
+
export function FileColumn(options: { saveDirectory: string, publicPath: string }) {
|
|
6
|
+
return function decorator(object: any, propertyName: string) {
|
|
7
|
+
getMetadataArgsStorage().columns.push({
|
|
8
|
+
target: object.constructor,
|
|
9
|
+
propertyName,
|
|
10
|
+
mode: "regular",
|
|
11
|
+
options: {
|
|
12
|
+
type: "json",
|
|
13
|
+
isFile: true,
|
|
14
|
+
nullable: true,
|
|
15
|
+
transformer: {
|
|
16
|
+
isFile: true,
|
|
17
|
+
fileOptions: options,
|
|
18
|
+
to: (values: FileType | FileType[] | undefined) => {
|
|
19
|
+
if (values) {
|
|
20
|
+
let single = false;
|
|
21
|
+
if (!Array.isArray(values)) {
|
|
22
|
+
values = [values];
|
|
23
|
+
single = true;
|
|
24
|
+
}
|
|
25
|
+
for (const value of values) {
|
|
26
|
+
if (value.src.startsWith(options.publicPath)) {
|
|
27
|
+
value.src = value.src.slice(options.publicPath.length);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (single) {
|
|
31
|
+
return values[0];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return values;
|
|
35
|
+
},
|
|
36
|
+
from: (values: FileType | FileType[] | undefined) => {
|
|
37
|
+
if (values) {
|
|
38
|
+
let single = false;
|
|
39
|
+
if (!Array.isArray(values)) {
|
|
40
|
+
values = [values];
|
|
41
|
+
single = true;
|
|
42
|
+
}
|
|
43
|
+
for (const value of values) {
|
|
44
|
+
if (!value.src.startsWith("data:")) {
|
|
45
|
+
value.src = options.publicPath + value.src;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (single) {
|
|
49
|
+
return values[0];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return values;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
} as ColumnMetadataArgs);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { PassThrough, Readable } from 'stream';
|
|
2
|
+
import { createHash, randomBytes } from 'crypto';
|
|
3
|
+
import { createWriteStream, existsSync, mkdirSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const FileWriter = {
|
|
7
|
+
async writeToFile(src: string, saveDirectory: string) {
|
|
8
|
+
const base64SearchText = ';base64,';
|
|
9
|
+
const indexBase64SearchText = src.indexOf(base64SearchText);
|
|
10
|
+
const indexSlash = src.indexOf('/');
|
|
11
|
+
|
|
12
|
+
// file is already a url
|
|
13
|
+
if (indexBase64SearchText === -1 || indexSlash === -1 || !src.startsWith('data:')) {
|
|
14
|
+
return src;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const fileType = src.slice('data:'.length, indexSlash);
|
|
18
|
+
const fileEnding = src.slice(indexSlash + 1, indexBase64SearchText);
|
|
19
|
+
const data = src.slice(Math.max(0, indexBase64SearchText + base64SearchText.length));
|
|
20
|
+
|
|
21
|
+
const seed = randomBytes(20);
|
|
22
|
+
const now = new Date();
|
|
23
|
+
|
|
24
|
+
// Month is 0-based. Add 1 to get 1-12
|
|
25
|
+
const name = `${now.getUTCFullYear()}-${now.getUTCMonth()+1}-${now.getUTCDate()}-${fileType}-${createHash('sha1').update(seed).digest('hex')}.${fileEnding}`;
|
|
26
|
+
|
|
27
|
+
const dataBuffer = Buffer.from(data, 'base64');
|
|
28
|
+
const inputStream = new Readable();
|
|
29
|
+
const dataStream = new PassThrough();
|
|
30
|
+
|
|
31
|
+
if(!existsSync(saveDirectory)) {
|
|
32
|
+
mkdirSync(saveDirectory, {recursive: true});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const writeStream = createWriteStream(saveDirectory + name);
|
|
36
|
+
inputStream.pipe(dataStream);
|
|
37
|
+
|
|
38
|
+
inputStream.push(dataBuffer);
|
|
39
|
+
// eslint-disable-next-line unicorn/no-array-push-push
|
|
40
|
+
inputStream.push(null);
|
|
41
|
+
|
|
42
|
+
const resultPromise = new Promise((r) => writeStream.addListener('finish', r));
|
|
43
|
+
dataStream.pipe(writeStream);
|
|
44
|
+
await resultPromise;
|
|
45
|
+
return name;
|
|
46
|
+
},
|
|
47
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './BaseModel';
|
|
2
|
+
export * from './dataSource';
|
|
3
|
+
export * from './DbNamingStrategy';
|
|
4
|
+
export * from './ServerSubscriber';
|
|
5
|
+
export * from './migration/getCreateTableColumns';
|
|
6
|
+
export * from './decorators/FileColumn/FileColumn';
|
|
7
|
+
export * from './decorators/FileColumn/FileTransformer';
|
|
8
|
+
export * from './decorators/FileColumn/FileType';
|
|
9
|
+
export * from './decorators/FileColumn/FileWriter';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Do not change the columns here. It is needed for migration, aka creation of the database
|
|
2
|
+
export function getCreateTableColumns() {
|
|
3
|
+
return [{
|
|
4
|
+
name: 'id',
|
|
5
|
+
type: 'int',
|
|
6
|
+
isPrimary: true,
|
|
7
|
+
isGenerated: true,
|
|
8
|
+
generationStrategy: 'increment',
|
|
9
|
+
isNullable: false,
|
|
10
|
+
}, {
|
|
11
|
+
name: 'createdAt',
|
|
12
|
+
type: 'datetime',
|
|
13
|
+
isNullable: false,
|
|
14
|
+
}, {
|
|
15
|
+
name: 'updatedAt',
|
|
16
|
+
type: 'datetime',
|
|
17
|
+
isNullable: false,
|
|
18
|
+
}, {
|
|
19
|
+
name: 'deletedAt',
|
|
20
|
+
type: 'datetime(6)',
|
|
21
|
+
isNullable: true,
|
|
22
|
+
}, {
|
|
23
|
+
name: 'version',
|
|
24
|
+
type: 'int',
|
|
25
|
+
isNullable: false,
|
|
26
|
+
}] as const;
|
|
27
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@ainias42/typescript-config",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "react",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"baseUrl": "./",
|
|
8
|
+
"paths": {
|
|
9
|
+
"@/*": [
|
|
10
|
+
"src/*"
|
|
11
|
+
],
|
|
12
|
+
"@cli/*": [
|
|
13
|
+
"cli/*"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"exclude": [
|
|
18
|
+
"../../node_modules",
|
|
19
|
+
"dist/**/*",
|
|
20
|
+
"dist/src/**/*"
|
|
21
|
+
],
|
|
22
|
+
"include": [
|
|
23
|
+
"src/**/*.tsx",
|
|
24
|
+
"src/**/*.ts",
|
|
25
|
+
],
|
|
26
|
+
"tsc-alias": {
|
|
27
|
+
"resolveFullPaths": true,
|
|
28
|
+
"replacers": {
|
|
29
|
+
"scssReplacer": {
|
|
30
|
+
"enabled": true,
|
|
31
|
+
"file": "../tscReplacers/scssReplacer.cjs"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|