Files
the-order/packages/storage/src/storage.ts
T
defiQUG 92cc41d26d Add Legal Office seal and complete Azure CDN deployment
- 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
2025-11-12 22:03:42 -08:00

116 lines
2.7 KiB
TypeScript

/**
* 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 interface StorageConfig {
provider: 's3' | 'gcs';
bucket: string;
region?: string;
accessKeyId?: string;
secretAccessKey?: string;
}
export interface StorageObject {
key: string;
content: Buffer | string;
contentType?: string;
metadata?: Record<string, string>;
}
export class StorageClient {
protected s3Client: S3Client;
protected bucket: string;
constructor(protected config: StorageConfig) {
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: StorageObject): Promise<string> {
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: string): Promise<Buffer> {
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: Uint8Array[] = [];
if (response.Body) {
const body = response.Body as AsyncIterable<Uint8Array>;
for await (const chunk of body) {
chunks.push(chunk);
}
}
return Buffer.concat(chunks);
}
async delete(key: string): Promise<void> {
const command = new DeleteObjectCommand({
Bucket: this.bucket,
Key: key,
});
await this.s3Client.send(command);
}
async getPresignedUrl(key: string, expiresIn: number): Promise<string> {
const command = new GetObjectCommand({
Bucket: this.bucket,
Key: key,
});
return getSignedUrl(this.s3Client, command, { expiresIn });
}
async objectExists(key: string): Promise<boolean> {
try {
const command = new HeadObjectCommand({
Bucket: this.bucket,
Key: key,
});
await this.s3Client.send(command);
return true;
} catch (error: unknown) {
if (error && typeof error === 'object' && 'name' in error && error.name === 'NotFound') {
return false;
}
throw error;
}
}
}