Skip to content

Commit 30a0ac2

Browse files
committed
feat(challenge-phase): add project_phase:create interface
1 parent 19e1a6d commit 30a0ac2

28 files changed

+3021
-1417
lines changed

.prettierrc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
{}
1+
{
2+
"printWidth": 100
3+
}

bin/nosql-client.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ const PROTO_DIR = path.join(
1010
const MODEL_DIR = path.join(__dirname, "../src/grpc/models/nosql/");
1111

1212
const PROTOC_PATH = "protoc";
13-
const PLUGIN_PATH = path.join(
14-
__dirname,
15-
"../node_modules/.bin/protoc-gen-ts_proto"
16-
);
13+
const PLUGIN_PATH = path.join(__dirname, "../node_modules/.bin/protoc-gen-ts_proto");
1714

1815
rimraf.sync(`${MODEL_DIR}/*`, {
1916
glob: { ignore: `${MODEL_DIR}/tsconfig.json` },

bin/rdb-client.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ const PROTO_DIR = path.join(
1010
const MODEL_DIR = path.join(__dirname, "../src/grpc/models/rdb/");
1111

1212
const PROTOC_PATH = "protoc";
13-
const PLUGIN_PATH = path.join(
14-
__dirname,
15-
"../node_modules/.bin/protoc-gen-ts_proto"
16-
);
13+
const PLUGIN_PATH = path.join(__dirname, "../node_modules/.bin/protoc-gen-ts_proto");
1714

1815
rimraf.sync(`${MODEL_DIR}/*`, {
1916
glob: { ignore: `${MODEL_DIR}/tsconfig.json` },

bin/server.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ const PROTO_REFLECTIONS = path.join(__dirname, "../reflections/reflection.bin");
99
const MODEL_DIR = path.join(__dirname, "../src/models/");
1010

1111
const PROTOC_PATH = "protoc";
12-
const PLUGIN_PATH = path.join(
13-
__dirname,
14-
"../node_modules/.bin/protoc-gen-ts_proto"
15-
);
12+
const PLUGIN_PATH = path.join(__dirname, "../node_modules/.bin/protoc-gen-ts_proto");
1613

1714
rimraf.sync(`${MODEL_DIR}/*`, {
1815
glob: { ignore: `${MODEL_DIR}/tsconfig.json` },
@@ -26,11 +23,9 @@ const protoConfig = [
2623
`--ts_proto_opt=oneof=unions`,
2724
`--ts_proto_opt=addGrpcMetadata=true`,
2825
`--ts_proto_opt=outputClientImpl=false`,
26+
`--ts_proto_opt=useDate=string`,
2927
`--include_imports`,
3028
`--descriptor_set_out ${PROTO_REFLECTIONS}`,
31-
`--ts_proto_opt=Mcommon/common.proto=@topcoder-framework/lib-common`,
32-
`--ts_proto_opt=Mgoogle/protobuf/struct.proto=@topcoder-framework/lib-common`,
33-
`--ts_proto_opt=Mgoogle/protobuf/timestamp.proto=@topcoder-framework/lib-common`,
3429
`--proto_path ${PROTO_DIR} ${PROTO_DIR}/domain-layer/legacy/**/*.proto`,
3530
];
3631

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@
1717
"license": "ISC",
1818
"dependencies": {
1919
"@grpc/grpc-js": "^1.7.1",
20-
"@topcoder-framework/lib-common": "^0.4.18-ci.0",
2120
"dayjs": "^1.11.5",
2221
"dotenv": "^16.0.3",
2322
"grpc-server-reflection": "^0.1.5",
2423
"lodash": "^4.17.21",
2524
"source-map-support": "^0.5.21",
26-
"topcoder-interface": "github:topcoder-platform/plat-interface-definition#v0.0.9",
25+
"topcoder-interface": "github:topcoder-platform/plat-interface-definition#v0.0.11",
2726
"uuidv4": "^6.2.13"
2827
},
2928
"devDependencies": {
3029
"@types/lodash": "^4.14.186",
30+
"@types/node": "18.11.18",
3131
"ts-node-dev": "^2.0.0",
3232
"ts-proto": "^1.126.1",
3333
"typescript": "^4.9.4"

src/common/QueryRunner.ts

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import { ColumnType, Query, QueryRequest, Value } from "../grpc/models/rdb/relational";
2+
3+
import { relationalClient } from "../grpc/client/relational";
4+
5+
export interface TableOptions {
6+
dbSchema: string;
7+
tableName: string;
8+
columns: {
9+
name: string;
10+
type: ColumnType;
11+
}[];
12+
idColumn?: string;
13+
idSequence?: string;
14+
idTable?: string;
15+
}
16+
17+
interface SqlQuery {
18+
exec(): Promise<unknown>;
19+
}
20+
21+
export interface SelectQuery extends SqlQuery {
22+
query(columns: string[]): SelectQuery;
23+
where(): SelectQuery;
24+
join(): SelectQuery;
25+
}
26+
27+
export interface InsertQuery extends SqlQuery {
28+
insert(input: unknown): InsertQuery;
29+
}
30+
31+
export interface UpdateQuery extends SqlQuery {
32+
update(input: Record<string, unknown>): UpdateQuery;
33+
}
34+
35+
export interface DeleteQuery extends SqlQuery {
36+
delete(): DeleteQuery;
37+
}
38+
39+
export class QueryRunner<T, CreateInput extends object>
40+
implements SelectQuery, InsertQuery, UpdateQuery, DeleteQuery
41+
{
42+
#query: Query | null = null;
43+
44+
// TODO: Optimize this as each instantiation of this class will create a new object with the same keys and values.
45+
#attributeKeyTypeMap: Record<string, ColumnType> = this.options.columns.reduce(
46+
(acc, cur) => ({
47+
...acc,
48+
[cur.name.replace(/([-_][a-z])/gi, ($1) => {
49+
return $1.toUpperCase().replace("-", "").replace("_", "");
50+
})]: cur.type,
51+
}),
52+
{}
53+
);
54+
55+
constructor(protected options: TableOptions) {}
56+
57+
query(columns: string[]): SelectQuery {
58+
this.#query = {
59+
query: {
60+
$case: "select",
61+
select: {
62+
schema: this.options.dbSchema,
63+
table: this.options.tableName,
64+
column: columns.map((col) => ({
65+
tableName: this.options.tableName,
66+
name: col,
67+
type: this.#attributeKeyTypeMap[col],
68+
})),
69+
where: [],
70+
join: [],
71+
groupBy: [],
72+
orderBy: [],
73+
limit: 100,
74+
offset: 0,
75+
},
76+
},
77+
};
78+
return this;
79+
}
80+
81+
where(): SelectQuery {
82+
return this;
83+
}
84+
85+
join(): SelectQuery {
86+
return this;
87+
}
88+
89+
insert(input: CreateInput): InsertQuery {
90+
console.log("Create Input", input);
91+
this.#query = {
92+
query: {
93+
$case: "insert",
94+
insert: {
95+
schema: this.options.dbSchema,
96+
table: this.options.tableName,
97+
columnValue: [
98+
{
99+
column: "create_date",
100+
value: {
101+
value: {
102+
$case: "datetimeValue",
103+
datetimeValue: "CURRENT",
104+
},
105+
},
106+
},
107+
{
108+
column: "modify_date",
109+
value: {
110+
value: {
111+
$case: "datetimeValue",
112+
datetimeValue: "CURRENT",
113+
},
114+
},
115+
},
116+
...Object.entries(input)
117+
.filter(([_key, value]) => value !== undefined)
118+
.map(([key, value]) => ({
119+
column: key.replace(/([A-Z])/g, "_$1").toLowerCase(),
120+
value: this.toValue(key, value),
121+
})),
122+
],
123+
idTable: this.options.tableName,
124+
idColumn: "project_phase_id",
125+
idSequence: "project_phase_id_seq",
126+
},
127+
},
128+
};
129+
130+
return this;
131+
}
132+
133+
update(input: Record<string, unknown>): UpdateQuery {
134+
return this;
135+
}
136+
137+
delete(): DeleteQuery {
138+
return this;
139+
}
140+
141+
async exec(): Promise<number | T[]> {
142+
console.log("Execute query");
143+
console.log("Insert query", JSON.stringify(this.#query));
144+
145+
if (!this.#query) {
146+
throw new Error("No query to execute");
147+
}
148+
149+
const queryRequest: QueryRequest = {
150+
query: this.#query,
151+
};
152+
153+
const queryResponse = await relationalClient.query(queryRequest);
154+
155+
switch (this.#query.query?.$case) {
156+
case "select":
157+
if (queryResponse.result?.$case != "selectResult") {
158+
throw new Error("Unexpected result type");
159+
}
160+
return queryResponse.result.selectResult.rows.map((row) => {
161+
return row as T;
162+
});
163+
case "insert":
164+
if (queryResponse.result?.$case != "insertResult") {
165+
throw new Error("Unexpected result type");
166+
}
167+
console.log("running insert query");
168+
return queryResponse.result.insertResult.lastInsertId;
169+
case "update":
170+
if (queryResponse.result?.$case != "updateResult") {
171+
throw new Error("Unexpected result type");
172+
}
173+
return queryResponse.result.updateResult.affectedRows;
174+
case "delete":
175+
if (queryResponse.result?.$case != "deleteResult") {
176+
throw new Error("Unexpected result type");
177+
}
178+
return queryResponse.result.deleteResult.affectedRows;
179+
default:
180+
throw new Error("Unexpected query type");
181+
}
182+
}
183+
184+
private toValue(key: string, value: unknown): Value {
185+
const dataType: ColumnType = this.#attributeKeyTypeMap[key];
186+
if (dataType == null) {
187+
throw new Error(`Unknown column ${key}`);
188+
}
189+
190+
if (dataType === ColumnType.COLUMN_TYPE_INT) {
191+
return { value: { $case: "intValue", intValue: value as number } };
192+
}
193+
194+
if (dataType === ColumnType.COLUMN_TYPE_FLOAT) {
195+
return { value: { $case: "floatValue", floatValue: value as number } };
196+
}
197+
198+
if (dataType === ColumnType.COLUMN_TYPE_DATE) {
199+
return { value: { $case: "dateValue", dateValue: value as string } };
200+
}
201+
202+
if (dataType == ColumnType.COLUMN_TYPE_DATETIME) {
203+
return {
204+
value: { $case: "datetimeValue", datetimeValue: value as string },
205+
};
206+
}
207+
208+
if (dataType == ColumnType.COLUMN_TYPE_STRING) {
209+
return { value: { $case: "stringValue", stringValue: value as string } };
210+
}
211+
212+
if (dataType == ColumnType.COLUMN_TYPE_BOOLEAN) {
213+
return {
214+
value: { $case: "booleanValue", booleanValue: value as boolean },
215+
};
216+
}
217+
218+
throw new Error(`Unsupported data type ${dataType}`);
219+
}
220+
}

0 commit comments

Comments
 (0)