Skip to content

Commit 6768d49

Browse files
committed
feat(gate): add cache to store used urls and add file upload at tg_deploy
1 parent 93d6632 commit 6768d49

File tree

4 files changed

+111
-23
lines changed

4 files changed

+111
-23
lines changed

typegate/src/engine/query_engine.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ export interface EndpointToSchemaMap {
114114
[index: string]: { fnName: string; outputSchema: unknown };
115115
}
116116

117+
export interface UploadUrlMeta {
118+
fileName: string;
119+
fileHash: string;
120+
fileSizeInBytes: number;
121+
urlUsed: boolean;
122+
}
123+
117124
export class QueryEngine {
118125
name: string;
119126
queryCache: QueryCache;
@@ -131,6 +138,7 @@ export class QueryEngine {
131138
}
132139
>
133140
>;
141+
fileUploadUrlCache: Map<string, UploadUrlMeta> = new Map();
134142

135143
get rawName(): string {
136144
return this.tg.rawName;
Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,94 @@
11
// Copyright Metatype OÜ, licensed under the Elastic License 2.0.
22
// SPDX-License-Identifier: Elastic-2.0
33

4+
import { UploadUrlMeta } from "../engine/query_engine.ts";
5+
import { QueryEngine } from "../engine/query_engine.ts";
6+
7+
interface TypegraphFile {
8+
name: string;
9+
fileHash: string;
10+
fileSizeInBytes: number;
11+
}
12+
413
function createUploadPath(origin: string, typegraphName: string) {
514
const rand_path = crypto.randomUUID();
6-
return `${origin}/${typegraphName}/files/${rand_path}`;
15+
return `${origin}/upload-files/${typegraphName}/files/${rand_path}`;
716
}
817

9-
export function handleUploadUrl(request: Request, typegraphName: string) {
18+
export async function handleUploadUrl(request: Request, engine: QueryEngine) {
1019
const url = new URL(request.url);
1120
const origin = url.origin;
21+
const { name, fileHash, fileSizeInBytes }: TypegraphFile = await request
22+
.json();
1223

13-
const uploadUrl = createUploadPath(origin, typegraphName);
14-
return new Response(uploadUrl);
15-
}
24+
const uploadUrlMeta: UploadUrlMeta = {
25+
fileName: name,
26+
fileHash: fileHash,
27+
fileSizeInBytes: fileSizeInBytes,
28+
urlUsed: false,
29+
};
1630

17-
interface TypegraphFile {
18-
name: string;
19-
fileHash: string;
20-
fileSizeInBytes: number;
21-
file: string;
31+
const uploadUrl = createUploadPath(origin, engine.name);
32+
33+
engine.fileUploadUrlCache.set(uploadUrl, uploadUrlMeta);
34+
35+
return new Response(JSON.stringify({ uploadUrl: [uploadUrl] }));
2236
}
2337

2438
export async function handleFileUpload(
2539
request: Request,
26-
typegraphName: string,
40+
engine: QueryEngine,
2741
) {
2842
const url = new URL(request.url);
29-
if (request.method !== "POST") {
43+
if (request.method !== "PUT") {
3044
throw new Error(
3145
`${url.pathname} does not support ${request.method} method`,
3246
);
3347
}
3448

35-
const fileStorageDir = `metatype_artifacts/${typegraphName}/files`;
36-
const { name, fileHash, fileSizeInBytes, file }: TypegraphFile = await request
37-
.json();
49+
const uploadMeta = engine.fileUploadUrlCache.get(url.toString());
50+
51+
if (!uploadMeta) {
52+
throw new Error(`Endpoint ${url.toString()} does not exist`);
53+
}
3854

39-
const fileData = Uint8Array.from(atob(file), (c) => c.charCodeAt(0));
55+
const { fileName, fileHash, fileSizeInBytes, urlUsed }: UploadUrlMeta =
56+
uploadMeta!;
4057

41-
if (fileData.length !== fileSizeInBytes) {
42-
throw new Error();
58+
if (urlUsed) {
59+
throw new Error(`Endpoint ${url.toString()} is disabled`);
4360
}
4461

62+
const reader = request.body?.getReader()!;
63+
64+
let fileData = new Uint8Array();
65+
let bytesRead = 0;
66+
while (true) {
67+
const { done, value } = await reader.read();
68+
if (done) {
69+
break;
70+
}
71+
if (value) {
72+
bytesRead += value.length;
73+
const temp = new Uint8Array(fileData.length + value.length);
74+
temp.set(fileData);
75+
temp.set(value, fileData.length);
76+
fileData = temp;
77+
}
78+
}
79+
80+
if (bytesRead !== fileSizeInBytes) {
81+
throw new Error("File size does not match");
82+
}
83+
84+
const fileStorageDir = `metatype_artifacts/${engine.name}/files`;
4585
await Deno.mkdir(fileStorageDir, { recursive: true });
46-
const filePath = `${fileStorageDir}/${name}.${fileHash}`;
86+
const filePath = `${fileStorageDir}/${fileName}.${fileHash}`;
4787
await Deno.writeFile(filePath, fileData);
4888

89+
// mark as the url used once the request completes.
90+
uploadMeta.urlUsed = true;
91+
engine.fileUploadUrlCache.set(url.toString(), uploadMeta);
92+
4993
return new Response();
5094
}

typegate/src/typegate/mod.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,11 @@ export class Typegate {
134134
}
135135

136136
if (serviceName === "get-upload-url") {
137-
return handleUploadUrl(request, engineName);
137+
return handleUploadUrl(request, engine);
138138
}
139139

140140
if (serviceName === "upload-files") {
141-
return handleFileUpload(request, engineName);
141+
return handleFileUpload(request, engine);
142142
}
143143

144144
if (serviceName === "auth") {

typegraph/python/typegraph/graph/tg_deploy.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0.
22
# SPDX-License-Identifier: MPL-2.0
33

4+
import json
5+
from base64 import b64encode
46
from dataclasses import dataclass
57
from typing import Dict, Optional, Union
68
from urllib import request
7-
import json
89
from typegraph.graph.shared_types import BasicAuth
910
from typegraph.wit import ArtifactResolutionConfig
1011
from typegraph.gen.types import Err
11-
1212
from typegraph.graph.typegraph import TypegraphOutput
1313
from typegraph.gen.exports.utils import QueryDeployParams
1414
from typegraph.wit import store, wit_utils
@@ -39,6 +39,13 @@ class RemoveResult:
3939
typegate: Union[Dict[str, any], str]
4040

4141

42+
@dataclass
43+
class UploadArtifactMeta:
44+
name: str
45+
file_hash: str
46+
file_size_in_bytes: int
47+
48+
4249
def tg_deploy(tg: TypegraphOutput, params: TypegraphDeployParams) -> DeployResult:
4350
sep = "/" if not params.base_url.endswith("/") else ""
4451
url = params.base_url + sep + "typegate"
@@ -47,6 +54,35 @@ def tg_deploy(tg: TypegraphOutput, params: TypegraphDeployParams) -> DeployResul
4754
if params.auth is not None:
4855
headers["Authorization"] = params.auth.as_header_value()
4956
serialized = tg.serialize(params.artifacts_config)
57+
58+
# upload the referred files
59+
ref_files = core.get_ref_files(store)
60+
if isinstance(ref_files, Err):
61+
raise Exception(ref_files.value)
62+
63+
get_upload_url = params.base_url + sep + "get-upload-url"
64+
for file_hash, file_path in ref_files.value:
65+
with open(file_path, "rb") as file:
66+
file_content = file.read()
67+
artifact = UploadArtifactMeta(
68+
name=file.name,
69+
file_hash=file_hash,
70+
file_size_in_bytes=len(file_content),
71+
)
72+
req = request.Request(
73+
url=get_upload_url, method="GET", headers=headers, data=artifact
74+
)
75+
76+
response = handle_response(request.urlopen(req).read().decode())
77+
file_upload_url = response["uploadUrl"]
78+
upload_req = request.Request(
79+
url=file_upload_url,
80+
method="PUT",
81+
data=file_content,
82+
headers={"Content-Type": "application/octet-stream"},
83+
)
84+
response = request.urlopen(upload_req)
85+
5086
res = wit_utils.gql_deploy_query(
5187
store,
5288
params=QueryDeployParams(

0 commit comments

Comments
 (0)