- Add Legal Office of the Master seal (SVG design with Maltese Cross, scales of justice, legal scroll) - Create legal-office-manifest-template.json for Legal Office credentials - Update SEAL_MAPPING.md and DESIGN_GUIDE.md with Legal Office seal documentation - Complete Azure CDN infrastructure deployment: - Resource group, storage account, and container created - 17 PNG seal files uploaded to Azure Blob Storage - All manifest templates updated with Azure URLs - Configuration files generated (azure-cdn-config.env) - Add comprehensive Azure CDN setup scripts and documentation - Fix manifest URL generation to prevent double slashes - Verify all seals accessible via HTTPS
83 lines
2.6 KiB
JavaScript
83 lines
2.6 KiB
JavaScript
/**
|
|
* Storage abstraction for S3/GCS
|
|
*/
|
|
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, HeadObjectCommand, } from '@aws-sdk/client-s3';
|
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
export class StorageClient {
|
|
config;
|
|
s3Client;
|
|
bucket;
|
|
constructor(config) {
|
|
this.config = config;
|
|
this.bucket = config.bucket;
|
|
this.s3Client = new S3Client({
|
|
region: config.region || 'us-east-1',
|
|
credentials: config.accessKeyId && config.secretAccessKey
|
|
? {
|
|
accessKeyId: config.accessKeyId,
|
|
secretAccessKey: config.secretAccessKey,
|
|
}
|
|
: undefined,
|
|
});
|
|
}
|
|
async upload(object) {
|
|
const command = new PutObjectCommand({
|
|
Bucket: this.bucket,
|
|
Key: object.key,
|
|
Body: typeof object.content === 'string' ? Buffer.from(object.content) : object.content,
|
|
ContentType: object.contentType,
|
|
Metadata: object.metadata,
|
|
});
|
|
await this.s3Client.send(command);
|
|
return object.key;
|
|
}
|
|
async download(key) {
|
|
const command = new GetObjectCommand({
|
|
Bucket: this.bucket,
|
|
Key: key,
|
|
});
|
|
const response = await this.s3Client.send(command);
|
|
if (!response.Body) {
|
|
throw new Error(`Object ${key} not found or empty`);
|
|
}
|
|
const chunks = [];
|
|
if (response.Body) {
|
|
const body = response.Body;
|
|
for await (const chunk of body) {
|
|
chunks.push(chunk);
|
|
}
|
|
}
|
|
return Buffer.concat(chunks);
|
|
}
|
|
async delete(key) {
|
|
const command = new DeleteObjectCommand({
|
|
Bucket: this.bucket,
|
|
Key: key,
|
|
});
|
|
await this.s3Client.send(command);
|
|
}
|
|
async getPresignedUrl(key, expiresIn) {
|
|
const command = new GetObjectCommand({
|
|
Bucket: this.bucket,
|
|
Key: key,
|
|
});
|
|
return getSignedUrl(this.s3Client, command, { expiresIn });
|
|
}
|
|
async objectExists(key) {
|
|
try {
|
|
const command = new HeadObjectCommand({
|
|
Bucket: this.bucket,
|
|
Key: key,
|
|
});
|
|
await this.s3Client.send(command);
|
|
return true;
|
|
}
|
|
catch (error) {
|
|
if (error && typeof error === 'object' && 'name' in error && error.name === 'NotFound') {
|
|
return false;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
//# sourceMappingURL=storage.js.map
|