@cepseudo/assets 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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/assets_manager.d.ts +643 -0
- package/dist/assets_manager.d.ts.map +1 -0
- package/dist/assets_manager.js +1217 -0
- package/dist/assets_manager.js.map +1 -0
- package/dist/assets_openapi.d.ts +9 -0
- package/dist/assets_openapi.d.ts.map +1 -0
- package/dist/assets_openapi.js +391 -0
- package/dist/assets_openapi.js.map +1 -0
- package/dist/async_upload.d.ts +20 -0
- package/dist/async_upload.d.ts.map +1 -0
- package/dist/async_upload.js +10 -0
- package/dist/async_upload.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/map_manager.d.ts +61 -0
- package/dist/map_manager.d.ts.map +1 -0
- package/dist/map_manager.js +217 -0
- package/dist/map_manager.js.map +1 -0
- package/dist/presigned_upload_service.d.ts +52 -0
- package/dist/presigned_upload_service.d.ts.map +1 -0
- package/dist/presigned_upload_service.js +152 -0
- package/dist/presigned_upload_service.js.map +1 -0
- package/dist/tileset_manager.d.ts +128 -0
- package/dist/tileset_manager.d.ts.map +1 -0
- package/dist/tileset_manager.js +705 -0
- package/dist/tileset_manager.js.map +1 -0
- package/dist/upload_processor.d.ts +42 -0
- package/dist/upload_processor.d.ts.map +1 -0
- package/dist/upload_processor.js +138 -0
- package/dist/upload_processor.js.map +1 -0
- package/dist/upload_reconciler.d.ts +44 -0
- package/dist/upload_reconciler.d.ts.map +1 -0
- package/dist/upload_reconciler.js +140 -0
- package/dist/upload_reconciler.js.map +1 -0
- package/dist/utils/zip_utils.d.ts +66 -0
- package/dist/utils/zip_utils.d.ts.map +1 -0
- package/dist/utils/zip_utils.js +169 -0
- package/dist/utils/zip_utils.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { AssetsManager } from './assets_manager.js';
|
|
2
|
+
/**
|
|
3
|
+
* Specialized Assets Manager for handling map layer data.
|
|
4
|
+
*
|
|
5
|
+
* Extends the base AssetsManager with specialized logic for:
|
|
6
|
+
* - Processing JSON layer objects containing map data
|
|
7
|
+
* - Extracting and analyzing layer metadata
|
|
8
|
+
* - Storing layer-specific information
|
|
9
|
+
*
|
|
10
|
+
* Inherits all CRUD endpoints from AssetsManager:
|
|
11
|
+
* - GET /{name} - List all layers
|
|
12
|
+
* - POST /{name}/upload - Upload layer data (overridden)
|
|
13
|
+
* - GET /{name}/:id - Get layer data
|
|
14
|
+
* - PUT /{name}/:id - Update layer metadata
|
|
15
|
+
* - DELETE /{name}/:id - Delete layer
|
|
16
|
+
* - GET /{name}/:id/download - Download layer data
|
|
17
|
+
*/
|
|
18
|
+
export class MapManager extends AssetsManager {
|
|
19
|
+
/**
|
|
20
|
+
* Override the upload handler to process JSON layer objects instead of files.
|
|
21
|
+
*
|
|
22
|
+
* Processes the layer data:
|
|
23
|
+
* 1. Validates the layer object structure
|
|
24
|
+
* 2. Extracts layer-specific metadata
|
|
25
|
+
* 3. Stores the layer data as JSON
|
|
26
|
+
*
|
|
27
|
+
* @param req - HTTP request with layer JSON data
|
|
28
|
+
* @returns DataResponse with upload result
|
|
29
|
+
*/
|
|
30
|
+
async handleUpload(req) {
|
|
31
|
+
try {
|
|
32
|
+
if (!req || !req.body) {
|
|
33
|
+
return {
|
|
34
|
+
status: 400,
|
|
35
|
+
content: JSON.stringify({
|
|
36
|
+
error: 'Invalid request: missing request body'
|
|
37
|
+
}),
|
|
38
|
+
headers: { 'Content-Type': 'application/json' }
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Authenticate request
|
|
42
|
+
const authResult = await this.authMiddleware.authenticate(req);
|
|
43
|
+
if (!authResult.success) {
|
|
44
|
+
return authResult.response;
|
|
45
|
+
}
|
|
46
|
+
const userRecord = authResult.userRecord;
|
|
47
|
+
const body = req.body;
|
|
48
|
+
const { layer, description } = body;
|
|
49
|
+
if (!layer) {
|
|
50
|
+
return {
|
|
51
|
+
status: 400,
|
|
52
|
+
content: JSON.stringify({
|
|
53
|
+
error: 'Missing required field: layer (JSON object)'
|
|
54
|
+
}),
|
|
55
|
+
headers: { 'Content-Type': 'application/json' }
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Validate layer structure
|
|
59
|
+
if (typeof layer !== 'object' || layer === null) {
|
|
60
|
+
return {
|
|
61
|
+
status: 400,
|
|
62
|
+
content: JSON.stringify({
|
|
63
|
+
error: 'Layer must be a valid JSON object'
|
|
64
|
+
}),
|
|
65
|
+
headers: { 'Content-Type': 'application/json' }
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Analyze layer content
|
|
69
|
+
const layerInfo = this.analyzeLayerContent(layer);
|
|
70
|
+
const config = this.getConfiguration();
|
|
71
|
+
const now = new Date();
|
|
72
|
+
// Convert layer object to JSON string for storage
|
|
73
|
+
const layerJson = JSON.stringify(layer, null, 2);
|
|
74
|
+
const layerBuffer = Buffer.from(layerJson, 'utf-8');
|
|
75
|
+
// Generate filename from layer name or use timestamp
|
|
76
|
+
const filename = `${layerInfo.layer_name || 'layer'}_${Date.now()}.json`;
|
|
77
|
+
// Store layer data using framework pattern
|
|
78
|
+
const url = await this.storage.save(layerBuffer, config.name, filename);
|
|
79
|
+
// Create extended metadata with layer-specific fields
|
|
80
|
+
const metadata = {
|
|
81
|
+
name: config.name,
|
|
82
|
+
type: config.contentType || 'application/json',
|
|
83
|
+
url,
|
|
84
|
+
date: now,
|
|
85
|
+
description: description || layerInfo.description || 'Map layer',
|
|
86
|
+
source: body.source || 'uploaded',
|
|
87
|
+
owner_id: userRecord.id ?? null,
|
|
88
|
+
filename,
|
|
89
|
+
// Layer-specific metadata
|
|
90
|
+
layer_type: layerInfo.layer_type,
|
|
91
|
+
layer_name: layerInfo.layer_name,
|
|
92
|
+
geometry_type: layerInfo.geometry_type,
|
|
93
|
+
properties_count: layerInfo.properties_count
|
|
94
|
+
};
|
|
95
|
+
await this.db.save(metadata);
|
|
96
|
+
return {
|
|
97
|
+
status: 200,
|
|
98
|
+
content: JSON.stringify({
|
|
99
|
+
message: 'Layer uploaded successfully',
|
|
100
|
+
layer_name: layerInfo.layer_name,
|
|
101
|
+
geometry_type: layerInfo.geometry_type,
|
|
102
|
+
properties_count: layerInfo.properties_count
|
|
103
|
+
}),
|
|
104
|
+
headers: { 'Content-Type': 'application/json' }
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
return {
|
|
109
|
+
status: 500,
|
|
110
|
+
content: JSON.stringify({
|
|
111
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
112
|
+
}),
|
|
113
|
+
headers: { 'Content-Type': 'application/json' }
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Analyze layer content to extract metadata
|
|
119
|
+
* @param layer - The layer object to analyze
|
|
120
|
+
* @returns Layer metadata information
|
|
121
|
+
*/
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
|
+
analyzeLayerContent(layer) {
|
|
124
|
+
// Default values
|
|
125
|
+
let layer_type = 'unknown';
|
|
126
|
+
let layer_name = 'layer';
|
|
127
|
+
let geometry_type;
|
|
128
|
+
let properties_count = 0;
|
|
129
|
+
// Try to detect GeoJSON
|
|
130
|
+
if (layer.type === 'FeatureCollection' && Array.isArray(layer.features)) {
|
|
131
|
+
layer_type = 'geojson';
|
|
132
|
+
layer_name = layer.name || 'geojson_layer';
|
|
133
|
+
// Analyze first feature for geometry type
|
|
134
|
+
if (layer.features.length > 0) {
|
|
135
|
+
const firstFeature = layer.features[0];
|
|
136
|
+
if (firstFeature.geometry && firstFeature.geometry.type) {
|
|
137
|
+
geometry_type = firstFeature.geometry.type.toLowerCase();
|
|
138
|
+
}
|
|
139
|
+
// Count properties in first feature
|
|
140
|
+
if (firstFeature.properties) {
|
|
141
|
+
properties_count = Object.keys(firstFeature.properties).length;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Try to detect single GeoJSON Feature
|
|
146
|
+
else if (layer.type === 'Feature' && layer.geometry) {
|
|
147
|
+
layer_type = 'geojson_feature';
|
|
148
|
+
layer_name = layer.properties?.name || 'feature';
|
|
149
|
+
geometry_type = layer.geometry.type?.toLowerCase();
|
|
150
|
+
properties_count = layer.properties ? Object.keys(layer.properties).length : 0;
|
|
151
|
+
}
|
|
152
|
+
// Try to detect other common layer formats
|
|
153
|
+
else if (layer.layers && Array.isArray(layer.layers)) {
|
|
154
|
+
layer_type = 'layer_group';
|
|
155
|
+
layer_name = layer.name || 'layer_group';
|
|
156
|
+
properties_count = layer.layers.length;
|
|
157
|
+
}
|
|
158
|
+
// Generic object
|
|
159
|
+
else {
|
|
160
|
+
layer_type = 'custom';
|
|
161
|
+
layer_name = layer.name || layer.title || layer.id || 'custom_layer';
|
|
162
|
+
properties_count = Object.keys(layer).length;
|
|
163
|
+
}
|
|
164
|
+
// Extract description from various fields
|
|
165
|
+
const description = layer.description || layer.desc || layer.summary;
|
|
166
|
+
return {
|
|
167
|
+
layer_type,
|
|
168
|
+
layer_name,
|
|
169
|
+
geometry_type,
|
|
170
|
+
properties_count,
|
|
171
|
+
description
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Override retrieve to include layer-specific metadata in the response
|
|
176
|
+
*/
|
|
177
|
+
async retrieve() {
|
|
178
|
+
try {
|
|
179
|
+
const assets = await this.getAllAssets();
|
|
180
|
+
const config = this.getConfiguration();
|
|
181
|
+
// Transform to include layer metadata
|
|
182
|
+
const assetsWithMetadata = assets.map(asset => ({
|
|
183
|
+
id: asset.id,
|
|
184
|
+
name: asset.name,
|
|
185
|
+
date: asset.date,
|
|
186
|
+
contentType: asset.contentType,
|
|
187
|
+
description: asset.description || '',
|
|
188
|
+
source: asset.source || '',
|
|
189
|
+
owner_id: asset.owner_id || null,
|
|
190
|
+
filename: asset.filename || '',
|
|
191
|
+
// Layer-specific fields
|
|
192
|
+
layer_type: asset.layer_type || '',
|
|
193
|
+
layer_name: asset.layer_name || '',
|
|
194
|
+
geometry_type: asset.geometry_type || null,
|
|
195
|
+
properties_count: asset.properties_count || 0,
|
|
196
|
+
// URLs for frontend
|
|
197
|
+
url: `/${config.endpoint}/${asset.id}`,
|
|
198
|
+
download_url: `/${config.endpoint}/${asset.id}/download`
|
|
199
|
+
}));
|
|
200
|
+
return {
|
|
201
|
+
status: 200,
|
|
202
|
+
content: JSON.stringify(assetsWithMetadata),
|
|
203
|
+
headers: { 'Content-Type': 'application/json' }
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
return {
|
|
208
|
+
status: 500,
|
|
209
|
+
content: JSON.stringify({
|
|
210
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
211
|
+
}),
|
|
212
|
+
headers: { 'Content-Type': 'application/json' }
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=map_manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map_manager.js","sourceRoot":"","sources":["../src/map_manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { DataResponse, DataRecord, TypedRequest, AssetsManagerConfiguration } from '@cepseudo/shared';
|
|
2
|
+
import type { StorageService } from '@cepseudo/storage';
|
|
3
|
+
import type { DatabaseAdapter } from '@cepseudo/database';
|
|
4
|
+
import type { AuthMiddleware } from '@cepseudo/auth';
|
|
5
|
+
/**
|
|
6
|
+
* Dependencies required by the presigned upload service.
|
|
7
|
+
* Provided by the AssetsManager that owns this service.
|
|
8
|
+
*/
|
|
9
|
+
export interface PresignedUploadDeps {
|
|
10
|
+
db: DatabaseAdapter;
|
|
11
|
+
storage: StorageService;
|
|
12
|
+
authMiddleware: AuthMiddleware;
|
|
13
|
+
getConfiguration(): AssetsManagerConfiguration;
|
|
14
|
+
getAssetById(id: string): Promise<DataRecord | undefined>;
|
|
15
|
+
validateOwnership(asset: DataRecord, userId: number, headers?: Record<string, string | string[] | undefined>): DataResponse | undefined;
|
|
16
|
+
validateFileExtension(filename: string): boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Handles presigned URL upload flow: request generation and confirmation.
|
|
20
|
+
*
|
|
21
|
+
* Extracted from AssetsManager to keep upload concerns separate from
|
|
22
|
+
* general asset CRUD operations.
|
|
23
|
+
*/
|
|
24
|
+
export declare class PresignedUploadService {
|
|
25
|
+
private deps;
|
|
26
|
+
constructor(deps: PresignedUploadDeps);
|
|
27
|
+
/**
|
|
28
|
+
* Generate a presigned PUT URL for direct client-to-storage upload.
|
|
29
|
+
*
|
|
30
|
+
* Flow:
|
|
31
|
+
* 1. Authenticate user
|
|
32
|
+
* 2. Validate body (fileName, fileSize, contentType)
|
|
33
|
+
* 3. Check storage supports presigned URLs
|
|
34
|
+
* 4. Validate file extension
|
|
35
|
+
* 5. Generate presigned PUT URL
|
|
36
|
+
* 6. Save pending DB record
|
|
37
|
+
* 7. Return { fileId, uploadUrl, key, expiresAt }
|
|
38
|
+
*/
|
|
39
|
+
handleUploadRequest(req: TypedRequest): Promise<DataResponse>;
|
|
40
|
+
/**
|
|
41
|
+
* Confirm that a file has been uploaded via the presigned URL.
|
|
42
|
+
*
|
|
43
|
+
* Flow:
|
|
44
|
+
* 1. Authenticate user
|
|
45
|
+
* 2. Fetch record, check ownership
|
|
46
|
+
* 3. Check upload_status === 'pending'
|
|
47
|
+
* 4. Verify file exists on storage via objectExists
|
|
48
|
+
* 5. Update record to completed with URL = presigned_key
|
|
49
|
+
*/
|
|
50
|
+
handleConfirm(req: TypedRequest): Promise<DataResponse>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=presigned_upload_service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presigned_upload_service.d.ts","sourceRoot":"","sources":["../src/presigned_upload_service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,YAAY,EACZ,UAAU,EACV,YAAY,EAGZ,0BAA0B,EAC7B,MAAM,kBAAkB,CAAA;AASzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,oBAAoB,CAAA;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAChC,EAAE,EAAE,eAAe,CAAA;IACnB,OAAO,EAAE,cAAc,CAAA;IACvB,cAAc,EAAE,cAAc,CAAA;IAC9B,gBAAgB,IAAI,0BAA0B,CAAA;IAC9C,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAA;IACzD,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,YAAY,GAAG,SAAS,CAAA;IACvI,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;CACnD;AAED;;;;;GAKG;AACH,qBAAa,sBAAsB;IAC/B,OAAO,CAAC,IAAI,CAAqB;gBAErB,IAAI,EAAE,mBAAmB;IAIrC;;;;;;;;;;;OAWG;IACG,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAsEnE;;;;;;;;;OASG;IACG,aAAa,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;CA+DhE"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { successResponse, errorResponse, badRequestResponse, notFoundResponse, validateData, validatePresignedUploadRequest } from '@cepseudo/shared';
|
|
2
|
+
/**
|
|
3
|
+
* Handles presigned URL upload flow: request generation and confirmation.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from AssetsManager to keep upload concerns separate from
|
|
6
|
+
* general asset CRUD operations.
|
|
7
|
+
*/
|
|
8
|
+
export class PresignedUploadService {
|
|
9
|
+
constructor(deps) {
|
|
10
|
+
this.deps = deps;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate a presigned PUT URL for direct client-to-storage upload.
|
|
14
|
+
*
|
|
15
|
+
* Flow:
|
|
16
|
+
* 1. Authenticate user
|
|
17
|
+
* 2. Validate body (fileName, fileSize, contentType)
|
|
18
|
+
* 3. Check storage supports presigned URLs
|
|
19
|
+
* 4. Validate file extension
|
|
20
|
+
* 5. Generate presigned PUT URL
|
|
21
|
+
* 6. Save pending DB record
|
|
22
|
+
* 7. Return { fileId, uploadUrl, key, expiresAt }
|
|
23
|
+
*/
|
|
24
|
+
async handleUploadRequest(req) {
|
|
25
|
+
try {
|
|
26
|
+
if (!req?.body) {
|
|
27
|
+
return badRequestResponse('Invalid request: missing request body');
|
|
28
|
+
}
|
|
29
|
+
// Authenticate user
|
|
30
|
+
const authResult = await this.deps.authMiddleware.authenticate(req);
|
|
31
|
+
if (!authResult.success) {
|
|
32
|
+
return authResult.response;
|
|
33
|
+
}
|
|
34
|
+
const userId = authResult.userRecord.id;
|
|
35
|
+
if (!userId) {
|
|
36
|
+
return errorResponse('Failed to retrieve user information');
|
|
37
|
+
}
|
|
38
|
+
// Check presigned URL support
|
|
39
|
+
if (!this.deps.storage.supportsPresignedUrls()) {
|
|
40
|
+
return badRequestResponse('Presigned uploads are not supported with the current storage backend');
|
|
41
|
+
}
|
|
42
|
+
// Validate request body
|
|
43
|
+
const validated = await validateData(validatePresignedUploadRequest, req.body);
|
|
44
|
+
const { fileName, contentType, description, source, is_public } = validated;
|
|
45
|
+
// Validate file extension
|
|
46
|
+
if (!this.deps.validateFileExtension(fileName)) {
|
|
47
|
+
const config = this.deps.getConfiguration();
|
|
48
|
+
return badRequestResponse(`Invalid file extension. Expected: ${config.extension}`);
|
|
49
|
+
}
|
|
50
|
+
const config = this.deps.getConfiguration();
|
|
51
|
+
const sanitizedFilename = fileName.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
52
|
+
const key = `${config.name}/${Date.now()}/${sanitizedFilename}`;
|
|
53
|
+
// Generate presigned URL (5 min expiry)
|
|
54
|
+
const presigned = await this.deps.storage.generatePresignedUploadUrl(key, contentType, 300);
|
|
55
|
+
// Save pending record in database
|
|
56
|
+
const metadata = {
|
|
57
|
+
name: config.name,
|
|
58
|
+
type: contentType,
|
|
59
|
+
url: '',
|
|
60
|
+
date: new Date(),
|
|
61
|
+
description: description || '',
|
|
62
|
+
source: source || '',
|
|
63
|
+
owner_id: userId,
|
|
64
|
+
filename: fileName,
|
|
65
|
+
is_public: is_public ?? true,
|
|
66
|
+
presigned_key: presigned.key,
|
|
67
|
+
presigned_expires_at: presigned.expiresAt
|
|
68
|
+
};
|
|
69
|
+
// Use upload_status field via updateById after save
|
|
70
|
+
const savedRecord = await this.deps.db.save(metadata);
|
|
71
|
+
await this.deps.db.updateById(config.name, savedRecord.id, {
|
|
72
|
+
upload_status: 'pending'
|
|
73
|
+
});
|
|
74
|
+
return successResponse({
|
|
75
|
+
fileId: savedRecord.id,
|
|
76
|
+
uploadUrl: presigned.url,
|
|
77
|
+
key: presigned.key,
|
|
78
|
+
expiresAt: presigned.expiresAt.toISOString()
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
return errorResponse(error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Confirm that a file has been uploaded via the presigned URL.
|
|
87
|
+
*
|
|
88
|
+
* Flow:
|
|
89
|
+
* 1. Authenticate user
|
|
90
|
+
* 2. Fetch record, check ownership
|
|
91
|
+
* 3. Check upload_status === 'pending'
|
|
92
|
+
* 4. Verify file exists on storage via objectExists
|
|
93
|
+
* 5. Update record to completed with URL = presigned_key
|
|
94
|
+
*/
|
|
95
|
+
async handleConfirm(req) {
|
|
96
|
+
try {
|
|
97
|
+
// Authenticate user
|
|
98
|
+
const authResult = await this.deps.authMiddleware.authenticate(req);
|
|
99
|
+
if (!authResult.success) {
|
|
100
|
+
return authResult.response;
|
|
101
|
+
}
|
|
102
|
+
const userId = authResult.userRecord.id;
|
|
103
|
+
if (!userId) {
|
|
104
|
+
return errorResponse('Failed to retrieve user information');
|
|
105
|
+
}
|
|
106
|
+
const fileId = req.params?.fileId;
|
|
107
|
+
if (!fileId) {
|
|
108
|
+
return badRequestResponse('File ID is required');
|
|
109
|
+
}
|
|
110
|
+
const config = this.deps.getConfiguration();
|
|
111
|
+
const asset = await this.deps.getAssetById(fileId);
|
|
112
|
+
if (!asset) {
|
|
113
|
+
return notFoundResponse('Asset not found');
|
|
114
|
+
}
|
|
115
|
+
// Check ownership
|
|
116
|
+
const ownershipError = this.deps.validateOwnership(asset, userId, req.headers);
|
|
117
|
+
if (ownershipError) {
|
|
118
|
+
return ownershipError;
|
|
119
|
+
}
|
|
120
|
+
// Check status
|
|
121
|
+
if (asset.upload_status !== 'pending') {
|
|
122
|
+
return {
|
|
123
|
+
status: 409,
|
|
124
|
+
content: JSON.stringify({ error: `Upload is not pending (current status: ${asset.upload_status || 'completed'})` }),
|
|
125
|
+
headers: { 'Content-Type': 'application/json' }
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// Verify file exists on storage
|
|
129
|
+
if (!asset.presigned_key) {
|
|
130
|
+
return badRequestResponse('No presigned key found for this record');
|
|
131
|
+
}
|
|
132
|
+
const existsResult = await this.deps.storage.objectExists(asset.presigned_key);
|
|
133
|
+
if (!existsResult.exists) {
|
|
134
|
+
return badRequestResponse('File not found on storage. Please upload the file using the presigned URL first.');
|
|
135
|
+
}
|
|
136
|
+
// Update record to completed
|
|
137
|
+
await this.deps.db.updateById(config.name, asset.id, {
|
|
138
|
+
upload_status: 'completed',
|
|
139
|
+
url: asset.presigned_key
|
|
140
|
+
});
|
|
141
|
+
return successResponse({
|
|
142
|
+
message: 'Upload confirmed successfully',
|
|
143
|
+
id: asset.id,
|
|
144
|
+
url: asset.presigned_key
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
return errorResponse(error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=presigned_upload_service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presigned_upload_service.js","sourceRoot":"","sources":["../src/presigned_upload_service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { AssetsManager } from './assets_manager.js';
|
|
2
|
+
import type { DataResponse, OpenAPIComponentSpec, HttpMethod, TypedRequest } from '@cepseudo/shared';
|
|
3
|
+
import type { AsyncUploadable } from './async_upload.js';
|
|
4
|
+
import type { Queue } from 'bullmq';
|
|
5
|
+
/**
|
|
6
|
+
* Metadata stored in database for a tileset.
|
|
7
|
+
* Simplified: Cesium accesses files directly from OVH.
|
|
8
|
+
*/
|
|
9
|
+
export interface TilesetMetadataRow {
|
|
10
|
+
id?: number;
|
|
11
|
+
name: string;
|
|
12
|
+
type: string;
|
|
13
|
+
/** Base path in storage for deletion (e.g., tilesets/123) */
|
|
14
|
+
url: string;
|
|
15
|
+
/** Public URL to tileset.json */
|
|
16
|
+
tileset_url: string;
|
|
17
|
+
date: Date;
|
|
18
|
+
description: string;
|
|
19
|
+
filename: string;
|
|
20
|
+
owner_id: number | null;
|
|
21
|
+
is_public?: boolean;
|
|
22
|
+
upload_status?: 'pending' | 'processing' | 'completed' | 'failed';
|
|
23
|
+
upload_job_id?: string;
|
|
24
|
+
upload_error?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Specialized Assets Manager for handling 3D Tiles tilesets.
|
|
28
|
+
*
|
|
29
|
+
* This manager extracts uploaded ZIP files and stores each file in cloud storage (OVH S3),
|
|
30
|
+
* allowing Cesium and other 3D viewers to load tilesets directly via public URLs.
|
|
31
|
+
*
|
|
32
|
+
* ## How it works
|
|
33
|
+
*
|
|
34
|
+
* 1. User uploads a ZIP containing a 3D Tiles tileset
|
|
35
|
+
* 2. ZIP is extracted and all files are stored in OVH with public-read ACL
|
|
36
|
+
* 3. Database stores only the tileset.json URL and base path
|
|
37
|
+
* 4. Cesium loads tileset.json directly from OVH
|
|
38
|
+
* 5. Cesium fetches tiles using relative paths in tileset.json (directly from OVH)
|
|
39
|
+
*
|
|
40
|
+
* ## Endpoints
|
|
41
|
+
*
|
|
42
|
+
* - GET /{endpoint} - List all tilesets with their public URLs
|
|
43
|
+
* - POST /{endpoint} - Upload tileset ZIP (sync < 50MB, async >= 50MB)
|
|
44
|
+
* - GET /{endpoint}/:id/status - Poll async upload status
|
|
45
|
+
* - PUT /{endpoint}/:id - Update tileset metadata
|
|
46
|
+
* - DELETE /{endpoint}/:id - Delete tileset and all files from storage
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* class MyTilesetManager extends TilesetManager {
|
|
51
|
+
* getConfiguration() {
|
|
52
|
+
* return {
|
|
53
|
+
* name: 'tilesets',
|
|
54
|
+
* description: 'Manage 3D Tiles tilesets',
|
|
55
|
+
* contentType: 'application/json',
|
|
56
|
+
* endpoint: 'api/tilesets',
|
|
57
|
+
* extension: '.zip'
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* }
|
|
61
|
+
*
|
|
62
|
+
* // After upload, response contains:
|
|
63
|
+
* // { tileset_url: 'https://bucket.s3.../tilesets/123/tileset.json' }
|
|
64
|
+
* //
|
|
65
|
+
* // Cesium loads directly:
|
|
66
|
+
* // Cesium.Cesium3DTileset.fromUrl(tileset_url)
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare abstract class TilesetManager extends AssetsManager implements AsyncUploadable {
|
|
70
|
+
/** Upload queue for async processing (injected by engine) */
|
|
71
|
+
protected uploadQueue: Queue | null;
|
|
72
|
+
/**
|
|
73
|
+
* Set the upload queue for async job processing.
|
|
74
|
+
* Called by DigitalTwinEngine during initialization.
|
|
75
|
+
*/
|
|
76
|
+
setUploadQueue(queue: Queue): void;
|
|
77
|
+
/**
|
|
78
|
+
* Handle tileset upload.
|
|
79
|
+
*
|
|
80
|
+
* - Files < 50MB: Synchronous extraction and upload
|
|
81
|
+
* - Files >= 50MB: Queued for async processing (returns 202)
|
|
82
|
+
*/
|
|
83
|
+
handleUpload(req: TypedRequest): Promise<DataResponse>;
|
|
84
|
+
/**
|
|
85
|
+
* Authenticate user from request headers.
|
|
86
|
+
* Returns user ID on success, or error response on failure.
|
|
87
|
+
*/
|
|
88
|
+
private authenticateUser;
|
|
89
|
+
/**
|
|
90
|
+
* Queue upload for background processing. Returns HTTP 202 immediately.
|
|
91
|
+
*/
|
|
92
|
+
private handleAsyncUpload;
|
|
93
|
+
/**
|
|
94
|
+
* Process upload synchronously.
|
|
95
|
+
*/
|
|
96
|
+
private handleSyncUpload;
|
|
97
|
+
/**
|
|
98
|
+
* Get upload status for async uploads.
|
|
99
|
+
*/
|
|
100
|
+
handleGetStatus(req: TypedRequest): Promise<DataResponse>;
|
|
101
|
+
/**
|
|
102
|
+
* Override presigned upload confirmation for tilesets.
|
|
103
|
+
* Instead of marking as completed, queue a BullMQ job for ZIP extraction.
|
|
104
|
+
*/
|
|
105
|
+
handleUploadConfirm(req: TypedRequest): Promise<DataResponse>;
|
|
106
|
+
/**
|
|
107
|
+
* List all tilesets with their public URLs.
|
|
108
|
+
*/
|
|
109
|
+
retrieve(req?: TypedRequest): Promise<DataResponse>;
|
|
110
|
+
/**
|
|
111
|
+
* Delete tileset and all files from storage.
|
|
112
|
+
*/
|
|
113
|
+
handleDelete(req: TypedRequest): Promise<DataResponse>;
|
|
114
|
+
/**
|
|
115
|
+
* Get HTTP endpoints for this manager.
|
|
116
|
+
*/
|
|
117
|
+
getEndpoints(): Array<{
|
|
118
|
+
method: HttpMethod;
|
|
119
|
+
path: string;
|
|
120
|
+
handler: (req: TypedRequest) => Promise<DataResponse>;
|
|
121
|
+
responseType?: string;
|
|
122
|
+
}>;
|
|
123
|
+
/**
|
|
124
|
+
* Generate OpenAPI specification.
|
|
125
|
+
*/
|
|
126
|
+
getOpenAPISpec(): OpenAPIComponentSpec;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=tileset_manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tileset_manager.d.ts","sourceRoot":"","sources":["../src/tileset_manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,UAAU,EAAE,YAAY,EAA2B,MAAM,kBAAkB,CAAA;AAc7H,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAQnC;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,GAAG,EAAE,MAAM,CAAA;IACX,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,IAAI,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,aAAa,CAAC,EAAE,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAA;IACjE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,8BAAsB,cAAe,SAAQ,aAAc,YAAW,eAAe;IACjF,6DAA6D;IAC7D,SAAS,CAAC,WAAW,EAAE,KAAK,GAAG,IAAI,CAAO;IAE1C;;;OAGG;IACH,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAIlC;;;;;OAKG;IACY,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAsDrE;;;OAGG;YACW,gBAAgB;IAQ9B;;OAEG;YACW,iBAAiB;IAyE/B;;OAEG;YACW,gBAAgB;IAyE9B;;OAEG;IACG,eAAe,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAwC/D;;;OAGG;IACY,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IA6F5E;;OAEG;IACY,QAAQ,CAAC,GAAG,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAwClE;;OAEG;IACY,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IA0DrE;;OAEG;IACM,YAAY,IAAI,KAAK,CAAC;QAC3B,MAAM,EAAE,UAAU,CAAA;QAClB,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;QACrD,YAAY,CAAC,EAAE,MAAM,CAAA;KACxB,CAAC;IAwDF;;OAEG;IACM,cAAc,IAAI,oBAAoB;CA0LlD"}
|