Skip to content

Commit e4fd290

Browse files
committed
feat(client): implement authentication and storage services in ObjectStackClient
1 parent 2c874f3 commit e4fd290

5 files changed

Lines changed: 127 additions & 63 deletions

File tree

packages/client/src/index.ts

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ import {
1111
ErrorCategory,
1212
GetDiscoveryResponse,
1313
GetMetaTypesResponse,
14-
GetMetaItemsResponse
14+
GetMetaItemsResponse,
15+
LoginRequest,
16+
SessionResponse,
17+
GetPresignedUrlRequest,
18+
PresignedUrlResponse,
19+
CompleteUploadRequest,
20+
FileUploadResponse
1521
} from '@objectstack/spec/api';
1622
import { Logger, createLogger } from '@objectstack/core';
1723

@@ -266,6 +272,103 @@ export class ObjectStackClient {
266272
}
267273
};
268274

275+
/**
276+
* Authentication Services
277+
*/
278+
auth = {
279+
login: async (request: LoginRequest): Promise<SessionResponse> => {
280+
const route = this.getRoute('auth');
281+
const res = await this.fetch(`${this.baseUrl}${route}/login`, {
282+
method: 'POST',
283+
body: JSON.stringify(request)
284+
});
285+
const data = await res.json();
286+
// Auto-set token if present in response
287+
if (data.data?.token) {
288+
this.token = data.data.token;
289+
}
290+
return data;
291+
},
292+
293+
logout: async () => {
294+
const route = this.getRoute('auth');
295+
await this.fetch(`${this.baseUrl}${route}/logout`, { method: 'POST' });
296+
this.token = undefined;
297+
},
298+
299+
me: async (): Promise<SessionResponse> => {
300+
const route = this.getRoute('auth');
301+
const res = await this.fetch(`${this.baseUrl}${route}/me`);
302+
return res.json();
303+
}
304+
};
305+
306+
/**
307+
* Storage Services
308+
*/
309+
storage = {
310+
upload: async (file: any, scope: string = 'user'): Promise<FileUploadResponse> => {
311+
// 1. Get Presigned URL
312+
const presignedReq: GetPresignedUrlRequest = {
313+
filename: file.name,
314+
mimeType: file.type,
315+
size: file.size,
316+
scope
317+
};
318+
319+
const route = this.getRoute('storage');
320+
const presignedRes = await this.fetch(`${this.baseUrl}${route}/upload/presigned`, {
321+
method: 'POST',
322+
body: JSON.stringify(presignedReq)
323+
});
324+
const { data: presigned } = await presignedRes.json() as { data: PresignedUrlResponse['data'] };
325+
326+
// 2. Upload to Cloud directly (Bypass API Middleware to avoid Auth headers if using S3)
327+
// Use fetchImpl directly
328+
const uploadRes = await this.fetchImpl(presigned.uploadUrl, {
329+
method: presigned.method,
330+
headers: presigned.headers,
331+
body: file
332+
});
333+
334+
if (!uploadRes.ok) {
335+
throw new Error(`Storage Upload Failed: ${uploadRes.statusText}`);
336+
}
337+
338+
// 3. Complete Upload
339+
const completeReq: CompleteUploadRequest = {
340+
fileId: presigned.fileId
341+
};
342+
const completeRes = await this.fetch(`${this.baseUrl}${route}/upload/complete`, {
343+
method: 'POST',
344+
body: JSON.stringify(completeReq)
345+
});
346+
347+
return completeRes.json();
348+
},
349+
350+
getDownloadUrl: async (fileId: string): Promise<string> => {
351+
const route = this.getRoute('storage');
352+
const res = await this.fetch(`${this.baseUrl}${route}/files/${fileId}/url`);
353+
const data = await res.json();
354+
return data.url;
355+
}
356+
};
357+
358+
/**
359+
* Automation Services
360+
*/
361+
automation = {
362+
trigger: async (triggerName: string, payload: any) => {
363+
const route = this.getRoute('automation');
364+
const res = await this.fetch(`${this.baseUrl}${route}/trigger/${triggerName}`, {
365+
method: 'POST',
366+
body: JSON.stringify(payload)
367+
});
368+
return res.json();
369+
}
370+
};
371+
269372
/**
270373
* Data Operations
271374
*/
@@ -503,10 +606,11 @@ export class ObjectStackClient {
503606
* Get the conventional route path for a given API endpoint type
504607
* ObjectStack uses standard conventions: /api/v1/data, /api/v1/metadata, /api/v1/ui
505608
*/
506-
private getRoute(type: 'data' | 'metadata' | 'ui' | 'auth' | 'analytics' | 'hub' | 'storage'): string {
609+
private getRoute(type: 'data' | 'metadata' | 'ui' | 'auth' | 'analytics' | 'hub' | 'storage' | 'automation'): string {
507610
// 1. Use discovered routes if available
508-
if (this.discoveryInfo?.routes && (this.discoveryInfo.routes as any)[type]) {
509-
return (this.discoveryInfo.routes as any)[type];
611+
// Note: Spec uses 'endpoints', mapped dynamically
612+
if (this.discoveryInfo?.endpoints && (this.discoveryInfo.endpoints as any)[type]) {
613+
return (this.discoveryInfo.endpoints as any)[type];
510614
}
511615

512616
// 2. Fallback to conventions
@@ -518,7 +622,8 @@ export class ObjectStackClient {
518622
auth: '/api/v1/auth',
519623
analytics: '/api/v1/analytics',
520624
hub: '/api/v1/hub',
521-
storage: '/api/v1/storage'
625+
storage: '/api/v1/storage',
626+
automation: '/api/v1/automation'
522627
};
523628

524629
return routeMap[type] || `/api/v1/${type}`;

packages/spec/src/api/auth.zod.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,13 @@ export const SessionResponseSchema = BaseResponseSchema.extend({
8686
export const UserProfileResponseSchema = BaseResponseSchema.extend({
8787
data: SessionUserSchema,
8888
});
89+
90+
export type AuthProvider = z.infer<typeof AuthProvider>;
91+
export type SessionUser = z.infer<typeof SessionUserSchema>;
92+
export type Session = z.infer<typeof SessionSchema>;
93+
export type LoginType = z.infer<typeof LoginType>;
94+
export type LoginRequest = z.infer<typeof LoginRequestSchema>;
95+
export type RegisterRequest = z.infer<typeof RegisterRequestSchema>;
96+
export type RefreshTokenRequest = z.infer<typeof RefreshTokenRequestSchema>;
97+
export type SessionResponse = z.infer<typeof SessionResponseSchema>;
98+
export type UserProfileResponse = z.infer<typeof UserProfileResponseSchema>;

packages/spec/src/api/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ export * from './analytics.zod';
3434
export * from './auth.zod';
3535
export * from './storage.zod';
3636
export * from './metadata.zod';
37+
export * from './auth.zod';
38+
export * from './storage.zod';

packages/spec/src/api/storage.zod.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,8 @@ export const PresignedUrlResponseSchema = BaseResponseSchema.extend({
4545
export const FileUploadResponseSchema = BaseResponseSchema.extend({
4646
data: FileMetadataSchema.describe('Uploaded file metadata'),
4747
});
48+
49+
export type GetPresignedUrlRequest = z.infer<typeof GetPresignedUrlRequestSchema>;
50+
export type CompleteUploadRequest = z.infer<typeof CompleteUploadRequestSchema>;
51+
export type PresignedUrlResponse = z.infer<typeof PresignedUrlResponseSchema>;
52+
export type FileUploadResponse = z.infer<typeof FileUploadResponseSchema>;

verify-refactor.ts

Lines changed: 0 additions & 58 deletions
This file was deleted.

0 commit comments

Comments
 (0)