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
This commit is contained in:
defiQUG
2025-11-12 22:03:42 -08:00
parent 8649ad4124
commit 92cc41d26d
258 changed files with 16021 additions and 1260 deletions

View File

@@ -13,11 +13,11 @@ export class ApiClient {
constructor(baseURL?: string) {
// Initialize service clients - each manages its own axios instance
this.identity = new IdentityClient();
this.eresidency = new EResidencyClient();
this.intake = new IntakeClient();
this.finance = new FinanceClient();
this.dataroom = new DataroomClient();
this.identity = new IdentityClient(baseURL);
this.eresidency = new EResidencyClient(baseURL);
this.intake = new IntakeClient(baseURL);
this.finance = new FinanceClient(baseURL);
this.dataroom = new DataroomClient(baseURL);
}
setAuthToken(token: string): void {

View File

@@ -1,4 +1,4 @@
import { ApiClient } from './client';
import axios, { AxiosInstance } from 'axios';
import type { eResidencyApplication, ApplicationStatus } from '@the-order/schemas';
export interface SubmitApplicationRequest {
@@ -36,14 +36,60 @@ export interface AdjudicateRequest {
}
export class EResidencyClient {
constructor(private client: ApiClient) {}
protected client: AxiosInstance;
async submitApplication(request: SubmitApplicationRequest) {
return this.client.post<eResidencyApplication>('/applications', request);
constructor(baseURL?: string) {
const apiBaseURL =
baseURL ||
(typeof window !== 'undefined'
? process.env.NEXT_PUBLIC_ERESIDENCY_SERVICE_URL || 'http://localhost:4001'
: 'http://localhost:4001');
this.client = axios.create({
baseURL: apiBaseURL,
headers: {
'Content-Type': 'application/json',
},
});
// Set up request interceptor for authentication
this.client.interceptors.request.use(
(config) => {
const token = this.getAuthToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
}
async getApplication(id: string) {
return this.client.get<eResidencyApplication>(`/applications/${id}`);
private getAuthToken(): string | null {
if (typeof window === 'undefined') return null;
return localStorage.getItem('auth_token');
}
setAuthToken(token: string): void {
if (typeof window !== 'undefined') {
localStorage.setItem('auth_token', token);
}
}
clearAuthToken(): void {
if (typeof window !== 'undefined') {
localStorage.removeItem('auth_token');
}
}
async submitApplication(request: SubmitApplicationRequest): Promise<eResidencyApplication> {
const response = await this.client.post<eResidencyApplication>('/applications', request);
return response.data;
}
async getApplication(id: string): Promise<eResidencyApplication> {
const response = await this.client.get<eResidencyApplication>(`/applications/${id}`);
return response.data;
}
async getReviewQueue(filters?: {
@@ -52,38 +98,50 @@ export class EResidencyClient {
assignedTo?: string;
limit?: number;
offset?: number;
}) {
}): Promise<{
applications: eResidencyApplication[];
total: number;
}> {
const params = new URLSearchParams();
if (filters?.riskBand) params.append('riskBand', filters.riskBand);
if (filters?.status) params.append('status', filters.status);
if (filters?.assignedTo) params.append('assignedTo', filters.assignedTo);
if (filters?.limit) params.append('limit', filters.limit.toString());
if (filters?.offset) params.append('offset', filters.offset.toString());
return this.client.get<{
const response = await this.client.get<{
applications: eResidencyApplication[];
total: number;
}>(`/review/queue?${params.toString()}`);
return response.data;
}
async getApplicationForReview(id: string) {
return this.client.get<eResidencyApplication>(`/review/applications/${id}`);
async getApplicationForReview(id: string): Promise<eResidencyApplication> {
const response = await this.client.get<eResidencyApplication>(`/review/applications/${id}`);
return response.data;
}
async adjudicateApplication(id: string, request: AdjudicateRequest) {
return this.client.post<eResidencyApplication>(`/review/applications/${id}/adjudicate`, request);
async adjudicateApplication(id: string, request: AdjudicateRequest): Promise<eResidencyApplication> {
const response = await this.client.post<eResidencyApplication>(`/review/applications/${id}/adjudicate`, request);
return response.data;
}
async revokeCredential(residentNumber: string, reason: string) {
return this.client.post('/applications/revoke', { residentNumber, reason });
async revokeCredential(residentNumber: string, reason: string): Promise<void> {
await this.client.post('/applications/revoke', { residentNumber, reason });
}
async getStatus() {
return this.client.get<Array<{
async getStatus(): Promise<Array<{
residentNumber: string;
status: string;
issuedAt?: string;
revokedAt?: string;
}>> {
const response = await this.client.get<Array<{
residentNumber: string;
status: string;
issuedAt?: string;
revokedAt?: string;
}>>('/status');
return response.data;
}
}

View File

@@ -1,4 +1,4 @@
import { ApiClient } from './client';
import axios, { AxiosInstance } from 'axios';
import type { eResidentCredential, eCitizenCredential } from '@the-order/schemas';
export interface IssueVCRequest {
@@ -49,18 +49,73 @@ export interface CredentialMetrics {
}
export class IdentityClient {
constructor(private client: ApiClient) {}
protected client: AxiosInstance;
async issueCredential(request: IssueVCRequest) {
return this.client.post<{ credential: eResidentCredential | eCitizenCredential }>('/vc/issue', request);
constructor(baseURL?: string) {
const apiBaseURL =
baseURL ||
(typeof window !== 'undefined'
? process.env.NEXT_PUBLIC_IDENTITY_SERVICE_URL || 'http://localhost:4002'
: 'http://localhost:4002');
this.client = axios.create({
baseURL: apiBaseURL,
headers: {
'Content-Type': 'application/json',
},
});
// Set up request interceptor for authentication
this.client.interceptors.request.use(
(config) => {
const token = this.getAuthToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
}
async verifyCredential(request: VerifyVCRequest) {
return this.client.post<{ valid: boolean }>('/vc/verify', request);
private getAuthToken(): string | null {
if (typeof window === 'undefined') return null;
return localStorage.getItem('auth_token');
}
async batchIssue(request: BatchIssuanceRequest) {
return this.client.post<{
setAuthToken(token: string): void {
if (typeof window !== 'undefined') {
localStorage.setItem('auth_token', token);
}
}
clearAuthToken(): void {
if (typeof window !== 'undefined') {
localStorage.removeItem('auth_token');
}
}
async issueCredential(request: IssueVCRequest): Promise<{ credential: eResidentCredential | eCitizenCredential }> {
const response = await this.client.post<{ credential: eResidentCredential | eCitizenCredential }>('/vc/issue', request);
return response.data;
}
async verifyCredential(request: VerifyVCRequest): Promise<{ valid: boolean }> {
const response = await this.client.post<{ valid: boolean }>('/vc/verify', request);
return response.data;
}
async batchIssue(request: BatchIssuanceRequest): Promise<{
jobId: string;
total: number;
accepted: number;
results: Array<{
index: number;
credentialId?: string;
error?: string;
}>;
}> {
const response = await this.client.post<{
jobId: string;
total: number;
accepted: number;
@@ -70,21 +125,31 @@ export class IdentityClient {
error?: string;
}>;
}>('/vc/issue/batch', request);
return response.data;
}
async revokeCredential(credentialId: string, reason?: string) {
return this.client.post('/vc/revoke', { credentialId, reason });
async revokeCredential(credentialId: string, reason?: string): Promise<void> {
await this.client.post('/vc/revoke', { credentialId, reason });
}
async getMetrics(startDate?: Date, endDate?: Date) {
async getMetrics(startDate?: Date, endDate?: Date): Promise<CredentialMetrics> {
const params = new URLSearchParams();
if (startDate) params.append('startDate', startDate.toISOString());
if (endDate) params.append('endDate', endDate.toISOString());
return this.client.get<CredentialMetrics>(`/metrics?${params.toString()}`);
const response = await this.client.get<CredentialMetrics>(`/metrics?${params.toString()}`);
return response.data;
}
async getMetricsDashboard() {
return this.client.get<{
async getMetricsDashboard(): Promise<{
summary: CredentialMetrics;
trends: {
daily: Array<{ date: string; count: number }>;
weekly: Array<{ week: string; count: number }>;
monthly: Array<{ month: string; count: number }>;
};
topCredentialTypes: Array<{ type: string; count: number; percentage: number }>;
}> {
const response = await this.client.get<{
summary: CredentialMetrics;
trends: {
daily: Array<{ date: string; count: number }>;
@@ -93,6 +158,7 @@ export class IdentityClient {
};
topCredentialTypes: Array<{ type: string; count: number; percentage: number }>;
}>('/metrics/dashboard');
return response.data;
}
async searchAuditLogs(filters: {
@@ -107,8 +173,35 @@ export class IdentityClient {
ipAddress?: string;
page?: number;
pageSize?: number;
}) {
return this.client.post('/metrics/audit/search', filters);
}): Promise<{
logs: Array<{
id: string;
action: string;
credentialId?: string;
subjectDid?: string;
performedBy?: string;
timestamp: string;
metadata?: Record<string, unknown>;
}>;
total: number;
page: number;
pageSize: number;
}> {
const response = await this.client.post<{
logs: Array<{
id: string;
action: string;
credentialId?: string;
subjectDid?: string;
performedBy?: string;
timestamp: string;
metadata?: Record<string, unknown>;
}>;
total: number;
page: number;
pageSize: number;
}>('/metrics/audit/search', filters);
return response.data;
}
}

View File

@@ -1,7 +1,7 @@
import axios, { AxiosInstance } from 'axios';
export interface DocumentUpload {
file: File | Blob;
file: File | Blob | Buffer;
documentType: string;
metadata?: Record<string, unknown>;
}
@@ -65,7 +65,11 @@ export class IntakeClient {
async uploadDocument(upload: DocumentUpload): Promise<DocumentMetadata> {
const formData = new FormData();
formData.append('file', upload.file);
// FormData.append accepts File, Blob, or string, but Buffer needs to be converted
const file: File | Blob = upload.file instanceof Buffer
? new Blob([new Uint8Array(upload.file)])
: (upload.file as File | Blob);
formData.append('file', file);
formData.append('documentType', upload.documentType);
if (upload.metadata) {
formData.append('metadata', JSON.stringify(upload.metadata));