@comergehq/studio 0.1.2 → 0.1.4
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/dist/index.d.mts +2 -10
- package/dist/index.d.ts +2 -10
- package/dist/index.js +293 -264
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +251 -222
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -5
- package/src/components/chat/ChatComposer.tsx +277 -0
- package/src/components/chat/ChatHeader.tsx +31 -0
- package/src/components/chat/ChatMessageBubble.tsx +69 -0
- package/src/components/chat/ChatMessageList.tsx +137 -0
- package/src/components/chat/ChatPage.tsx +69 -0
- package/src/components/chat/ForkNoticeBanner.tsx +66 -0
- package/src/components/chat/MultilineTextInput.tsx +46 -0
- package/src/components/chat/ScrollToBottomButton.tsx +78 -0
- package/src/components/chat/TypingIndicator.tsx +54 -0
- package/src/components/chat/index.ts +28 -0
- package/src/components/comments/AppCommentsSheet.tsx +213 -0
- package/src/components/comments/CommentRow.tsx +63 -0
- package/src/components/comments/formatTimeAgo.ts +3 -0
- package/src/components/comments/index.ts +3 -0
- package/src/components/comments/useAppComments.ts +74 -0
- package/src/components/comments/useAppDetails.ts +35 -0
- package/src/components/comments/useIosKeyboardSnapFix.ts +24 -0
- package/src/components/dialogs/ConfirmMergeRequestDialog.tsx +156 -0
- package/src/components/dialogs/index.ts +4 -0
- package/src/components/draw/DrawColorPicker.tsx +77 -0
- package/src/components/draw/DrawModeOverlay.tsx +144 -0
- package/src/components/draw/DrawSurface.tsx +127 -0
- package/src/components/draw/DrawToolbar.tsx +253 -0
- package/src/components/draw/index.ts +15 -0
- package/src/components/draw/optionalHaptics.ts +15 -0
- package/src/components/draw/strokes.ts +21 -0
- package/src/components/draw/types.ts +9 -0
- package/src/components/floating-draggable-button/FloatingDraggableButton.tsx +323 -0
- package/src/components/floating-draggable-button/constants.ts +17 -0
- package/src/components/floating-draggable-button/index.ts +4 -0
- package/src/components/floating-draggable-button/types.ts +63 -0
- package/src/components/icons/MergeIcon.tsx +14 -0
- package/src/components/icons/StudioIcons.tsx +66 -0
- package/src/components/index.ts +17 -0
- package/src/components/merge-requests/MergeRequestStatusCard.tsx +179 -0
- package/src/components/merge-requests/ReviewMergeRequestActionButton.tsx +62 -0
- package/src/components/merge-requests/ReviewMergeRequestCard.tsx +192 -0
- package/src/components/merge-requests/ReviewMergeRequestCarousel.tsx +132 -0
- package/src/components/merge-requests/index.ts +7 -0
- package/src/components/merge-requests/mergeRequestStatusDisplay.ts +23 -0
- package/src/components/merge-requests/toIsoString.ts +9 -0
- package/src/components/merge-requests/useControlledExpansion.ts +16 -0
- package/src/components/models/index.ts +9 -0
- package/src/components/models/types.ts +43 -0
- package/src/components/overlays/EdgeGlowFrame.tsx +105 -0
- package/src/components/overlays/index.ts +4 -0
- package/src/components/preview/PreviewHeroCard.tsx +58 -0
- package/src/components/preview/PreviewImage.tsx +22 -0
- package/src/components/preview/PreviewMetaRow.tsx +70 -0
- package/src/components/preview/PreviewPage.tsx +36 -0
- package/src/components/preview/PreviewPlaceholder.tsx +72 -0
- package/src/components/preview/PreviewStatusBadge.tsx +63 -0
- package/src/components/preview/StatsBar.tsx +109 -0
- package/src/components/preview/index.ts +22 -0
- package/src/components/primitives/Avatar.tsx +68 -0
- package/src/components/primitives/Button.tsx +102 -0
- package/src/components/primitives/Card.tsx +30 -0
- package/src/components/primitives/Divider.tsx +17 -0
- package/src/components/primitives/Icon.tsx +40 -0
- package/src/components/primitives/MarkdownText.tsx +72 -0
- package/src/components/primitives/Modal.tsx +53 -0
- package/src/components/primitives/Surface.tsx +42 -0
- package/src/components/primitives/Text.tsx +83 -0
- package/src/components/primitives/index.ts +35 -0
- package/src/components/primitives/types.ts +30 -0
- package/src/components/studio-sheet/StudioBottomSheet.tsx +114 -0
- package/src/components/studio-sheet/StudioSheetBackground.tsx +63 -0
- package/src/components/studio-sheet/StudioSheetHeader.tsx +35 -0
- package/src/components/studio-sheet/StudioSheetHeaderIconButton.tsx +109 -0
- package/src/components/studio-sheet/StudioSheetPager.tsx +66 -0
- package/src/components/studio-sheet/index.ts +18 -0
- package/src/components/studio-sheet/types.ts +5 -0
- package/src/components/utils/color.ts +25 -0
- package/src/components/utils/formatTimeAgo.ts +19 -0
- package/src/core/logger.ts +42 -0
- package/src/core/services/http/baseUrl.ts +3 -0
- package/src/core/services/http/index.ts +128 -0
- package/src/core/services/http/public.ts +33 -0
- package/src/core/services/supabase/auth.ts +41 -0
- package/src/core/services/supabase/client.ts +43 -0
- package/src/core/services/supabase/index.ts +7 -0
- package/src/data/agent/remote.ts +30 -0
- package/src/data/agent/repository.ts +34 -0
- package/src/data/agent/types.ts +28 -0
- package/src/data/apps/bundles/remote.ts +47 -0
- package/src/data/apps/bundles/repository.ts +35 -0
- package/src/data/apps/bundles/types.ts +27 -0
- package/src/data/apps/images/remote.ts +61 -0
- package/src/data/apps/images/repository.ts +47 -0
- package/src/data/apps/remote.ts +97 -0
- package/src/data/apps/repository.ts +185 -0
- package/src/data/apps/types.ts +206 -0
- package/src/data/attachment/remote.ts +32 -0
- package/src/data/attachment/repository.ts +40 -0
- package/src/data/attachment/types.ts +42 -0
- package/src/data/base-remote.ts +3 -0
- package/src/data/base-repository.ts +11 -0
- package/src/data/comments/likes/remote.ts +87 -0
- package/src/data/comments/likes/repository.ts +61 -0
- package/src/data/comments/likes/types.ts +47 -0
- package/src/data/comments/remote.ts +71 -0
- package/src/data/comments/repository.ts +53 -0
- package/src/data/comments/types.ts +60 -0
- package/src/data/github/remote.ts +23 -0
- package/src/data/github/repository.ts +35 -0
- package/src/data/github/types.ts +23 -0
- package/src/data/home/remote.ts +24 -0
- package/src/data/home/repository.ts +28 -0
- package/src/data/home/types.ts +70 -0
- package/src/data/index.ts +3 -0
- package/src/data/likes/remote.ts +57 -0
- package/src/data/likes/repository.ts +47 -0
- package/src/data/likes/types.ts +46 -0
- package/src/data/me/remote.ts +28 -0
- package/src/data/me/repository.ts +30 -0
- package/src/data/me/types.ts +14 -0
- package/src/data/merge-requests/remote.ts +76 -0
- package/src/data/merge-requests/repository.ts +66 -0
- package/src/data/merge-requests/types.ts +33 -0
- package/src/data/messages/remote.ts +21 -0
- package/src/data/messages/repository.ts +104 -0
- package/src/data/messages/types.ts +20 -0
- package/src/data/public/studio-config/remote.ts +19 -0
- package/src/data/public/studio-config/repository.ts +23 -0
- package/src/data/public/studio-config/types.ts +6 -0
- package/src/data/ratings/remote.ts +76 -0
- package/src/data/ratings/repository.ts +63 -0
- package/src/data/ratings/types.ts +57 -0
- package/src/data/threads/remote.ts +40 -0
- package/src/data/threads/repository.ts +41 -0
- package/src/data/threads/types.ts +25 -0
- package/src/data/types.ts +8 -0
- package/src/data/users/remote.ts +31 -0
- package/src/data/users/repository.ts +45 -0
- package/src/data/users/types.ts +15 -0
- package/src/index.ts +6 -0
- package/src/studio/ComergeStudio.tsx +239 -0
- package/src/studio/bootstrap/StudioBootstrap.tsx +45 -0
- package/src/studio/bootstrap/useStudioBootstrap.ts +55 -0
- package/src/studio/hooks/useApp.ts +83 -0
- package/src/studio/hooks/useAppStats.ts +111 -0
- package/src/studio/hooks/useAttachmentUpload.ts +59 -0
- package/src/studio/hooks/useBundleManager.ts +389 -0
- package/src/studio/hooks/useMergeRequests.ts +173 -0
- package/src/studio/hooks/useStudioActions.ts +96 -0
- package/src/studio/hooks/useThreadMessages.ts +85 -0
- package/src/studio/lib/chat.ts +34 -0
- package/src/studio/ui/ChatPanel.tsx +154 -0
- package/src/studio/ui/ConfirmMergeFlow.tsx +55 -0
- package/src/studio/ui/PreviewPanel.tsx +131 -0
- package/src/studio/ui/RuntimeRenderer.tsx +40 -0
- package/src/studio/ui/StudioOverlay.tsx +257 -0
- package/src/studio/ui/preview-panel/PressableCardRow.tsx +49 -0
- package/src/studio/ui/preview-panel/PreviewCollaborateSection.tsx +174 -0
- package/src/studio/ui/preview-panel/PreviewCustomizeSection.tsx +160 -0
- package/src/studio/ui/preview-panel/PreviewHeroSection.tsx +56 -0
- package/src/studio/ui/preview-panel/PreviewMetaSection.tsx +67 -0
- package/src/studio/ui/preview-panel/PreviewPanelHeader.tsx +48 -0
- package/src/studio/ui/preview-panel/SectionTitle.tsx +31 -0
- package/src/studio/ui/preview-panel/usePreviewPanelData.ts +132 -0
- package/src/studio/ui/preview-panel/utils.ts +29 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/tokens.ts +118 -0
- package/src/theme/types.ts +90 -0
- package/src/theme/useTheme.ts +11 -0
- package/dist/assets/images/merge.svg +0 -3
- package/dist/merge-72UG27QV.svg +0 -3
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
|
|
3
|
+
import { BASE_URL } from "./baseUrl";
|
|
4
|
+
|
|
5
|
+
const CLIENT_KEY_HEADER = "x-comerge-api-key";
|
|
6
|
+
let clientApiKey: string | null = null;
|
|
7
|
+
|
|
8
|
+
export const publicApi = axios.create({
|
|
9
|
+
baseURL: BASE_URL,
|
|
10
|
+
timeout: 30_000,
|
|
11
|
+
headers: {
|
|
12
|
+
Accept: "application/json",
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export function setClientApiKey(apiKey: string) {
|
|
18
|
+
const trimmed = apiKey?.trim?.() ?? "";
|
|
19
|
+
if (!trimmed) {
|
|
20
|
+
throw new Error("comerge-studio: apiKey is required");
|
|
21
|
+
}
|
|
22
|
+
clientApiKey = trimmed;
|
|
23
|
+
publicApi.defaults.headers.common[CLIENT_KEY_HEADER] = trimmed;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
publicApi.interceptors.request.use((config) => {
|
|
27
|
+
if (!clientApiKey) return config;
|
|
28
|
+
config.headers = config.headers ?? {};
|
|
29
|
+
(config.headers as any)[CLIENT_KEY_HEADER] = clientApiKey;
|
|
30
|
+
return config;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { User } from '@supabase/supabase-js';
|
|
2
|
+
|
|
3
|
+
import { getSupabaseClient } from './client';
|
|
4
|
+
|
|
5
|
+
export type EnsureAnonymousSessionResult = {
|
|
6
|
+
user: User;
|
|
7
|
+
isNew: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type EnsureAuthenticatedSessionResult = {
|
|
11
|
+
user: User;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export async function ensureAuthenticatedSession(): Promise<EnsureAuthenticatedSessionResult> {
|
|
15
|
+
const supabase = getSupabaseClient();
|
|
16
|
+
const { data, error } = await supabase.auth.getSession();
|
|
17
|
+
if (error) throw error;
|
|
18
|
+
const user = data.session?.user ?? null;
|
|
19
|
+
if (!user) {
|
|
20
|
+
throw new Error('comerge-studio: no authenticated Supabase session found.');
|
|
21
|
+
}
|
|
22
|
+
return { user };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function ensureAnonymousSession(): Promise<EnsureAnonymousSessionResult> {
|
|
26
|
+
const supabase = getSupabaseClient();
|
|
27
|
+
|
|
28
|
+
const { data: sessionData, error: sessionError } = await supabase.auth.getSession();
|
|
29
|
+
if (sessionError) throw sessionError;
|
|
30
|
+
|
|
31
|
+
const existingUser = sessionData.session?.user ?? null;
|
|
32
|
+
if (existingUser) return { user: existingUser, isNew: false };
|
|
33
|
+
|
|
34
|
+
const { data, error } = await supabase.auth.signInAnonymously();
|
|
35
|
+
if (error) throw error;
|
|
36
|
+
if (!data.user) throw new Error('comerge-studio: anonymous sign-in returned no user');
|
|
37
|
+
|
|
38
|
+
return { user: data.user, isNew: true };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createClient, type SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
|
|
3
|
+
let clientSingleton: SupabaseClient | null = null;
|
|
4
|
+
let injectedClient: SupabaseClient | null = null;
|
|
5
|
+
let hasInjectedClient = false;
|
|
6
|
+
let runtimeConfig: { url: string; anonKey: string } | null = null;
|
|
7
|
+
|
|
8
|
+
export function setSupabaseClient(client: SupabaseClient) {
|
|
9
|
+
injectedClient = client;
|
|
10
|
+
clientSingleton = client;
|
|
11
|
+
hasInjectedClient = true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isSupabaseClientInjected(): boolean {
|
|
15
|
+
return hasInjectedClient;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function setSupabaseConfig(config: { url: string; anonKey: string }) {
|
|
19
|
+
runtimeConfig = config;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getSupabaseClient(): SupabaseClient {
|
|
23
|
+
if (clientSingleton) return clientSingleton;
|
|
24
|
+
if (injectedClient) return injectedClient;
|
|
25
|
+
|
|
26
|
+
if (!runtimeConfig?.url) {
|
|
27
|
+
throw new Error('comerge-studio: Supabase config not initialized (missing url).');
|
|
28
|
+
}
|
|
29
|
+
if (!runtimeConfig?.anonKey) {
|
|
30
|
+
throw new Error('comerge-studio: Supabase config not initialized (missing anonKey).');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
clientSingleton = createClient(runtimeConfig.url, runtimeConfig.anonKey, {
|
|
34
|
+
auth: {
|
|
35
|
+
autoRefreshToken: true,
|
|
36
|
+
persistSession: true,
|
|
37
|
+
detectSessionInUrl: false,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return clientSingleton;
|
|
42
|
+
}
|
|
43
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { getSupabaseClient, setSupabaseClient } from './client';
|
|
2
|
+
export type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
|
|
4
|
+
export { ensureAnonymousSession, ensureAuthenticatedSession } from './auth';
|
|
5
|
+
export type { EnsureAnonymousSessionResult, EnsureAuthenticatedSessionResult } from './auth';
|
|
6
|
+
|
|
7
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { api } from '../../core/services/http';
|
|
2
|
+
import type {
|
|
3
|
+
CreateAgentAppRequest,
|
|
4
|
+
EditAgentAppRequest,
|
|
5
|
+
AgentCreateAppResult,
|
|
6
|
+
AgentEditAppResult,
|
|
7
|
+
} from './types';
|
|
8
|
+
import type { ServiceResponse } from '../types';
|
|
9
|
+
import { BaseRemote } from '../base-remote';
|
|
10
|
+
|
|
11
|
+
export interface AgentRemoteDataSource {
|
|
12
|
+
createApp(payload: CreateAgentAppRequest): Promise<ServiceResponse<AgentCreateAppResult>>;
|
|
13
|
+
editApp(payload: EditAgentAppRequest): Promise<ServiceResponse<AgentEditAppResult>>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class AgentRemoteDataSourceImpl extends BaseRemote implements AgentRemoteDataSource {
|
|
17
|
+
async createApp(payload: CreateAgentAppRequest): Promise<ServiceResponse<AgentCreateAppResult>> {
|
|
18
|
+
const { data } = await api.post<ServiceResponse<AgentCreateAppResult>>('/v1/agent/createApp', payload);
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async editApp(payload: EditAgentAppRequest): Promise<ServiceResponse<AgentEditAppResult>> {
|
|
23
|
+
const { data } = await api.post<ServiceResponse<AgentEditAppResult>>('/v1/agent/editApp', payload);
|
|
24
|
+
return data;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const agentRemoteDataSource: AgentRemoteDataSource = new AgentRemoteDataSourceImpl();
|
|
29
|
+
|
|
30
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AgentRemoteDataSource } from './remote';
|
|
2
|
+
import { agentRemoteDataSource } from './remote';
|
|
3
|
+
import type {
|
|
4
|
+
AgentCreateAppResult,
|
|
5
|
+
AgentEditAppResult,
|
|
6
|
+
CreateAgentAppRequest,
|
|
7
|
+
EditAgentAppRequest,
|
|
8
|
+
} from './types';
|
|
9
|
+
import { BaseRepository } from '../../data/base-repository';
|
|
10
|
+
|
|
11
|
+
export interface AgentRepository {
|
|
12
|
+
createApp(payload: CreateAgentAppRequest): Promise<AgentCreateAppResult>;
|
|
13
|
+
editApp(payload: EditAgentAppRequest): Promise<AgentEditAppResult>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class AgentRepositoryImpl extends BaseRepository implements AgentRepository {
|
|
17
|
+
constructor(private readonly remote: AgentRemoteDataSource) {
|
|
18
|
+
super();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async createApp(payload: CreateAgentAppRequest): Promise<AgentCreateAppResult> {
|
|
22
|
+
const res = await this.remote.createApp(payload);
|
|
23
|
+
return this.unwrapOrThrow(res);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async editApp(payload: EditAgentAppRequest): Promise<AgentEditAppResult> {
|
|
27
|
+
const res = await this.remote.editApp(payload);
|
|
28
|
+
return this.unwrapOrThrow(res);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const agentRepository: AgentRepository = new AgentRepositoryImpl(agentRemoteDataSource);
|
|
33
|
+
|
|
34
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type CreateAgentAppRequest = {
|
|
2
|
+
prompt: string;
|
|
3
|
+
thread_id: string;
|
|
4
|
+
app_id?: string;
|
|
5
|
+
attachments?: AttachmentMeta[];
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type EditAgentAppRequest = {
|
|
9
|
+
prompt: string;
|
|
10
|
+
thread_id: string;
|
|
11
|
+
app_id: string;
|
|
12
|
+
attachments?: AttachmentMeta[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type AgentCreateAppResult = {
|
|
16
|
+
threadId: string;
|
|
17
|
+
projectId: string;
|
|
18
|
+
appId: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type AgentEditAppResult = {
|
|
22
|
+
threadId: string;
|
|
23
|
+
appId: string;
|
|
24
|
+
sandboxExternalId: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
import type { AttachmentMeta } from '../../data/attachment/types';
|
|
28
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { api } from '../../../core/services/http';
|
|
2
|
+
import type { ServiceResponse } from '../../types';
|
|
3
|
+
import { BaseRemote } from '../../base-remote';
|
|
4
|
+
import type { Bundle, InitiateBundleRequest } from './types';
|
|
5
|
+
|
|
6
|
+
export interface BundlesRemoteDataSource {
|
|
7
|
+
initiate(appId: string, payload: InitiateBundleRequest): Promise<ServiceResponse<Bundle>>;
|
|
8
|
+
getById(appId: string, bundleId: string): Promise<ServiceResponse<Bundle>>;
|
|
9
|
+
getSignedDownloadUrl(
|
|
10
|
+
appId: string,
|
|
11
|
+
bundleId: string,
|
|
12
|
+
options?: { redirect?: boolean }
|
|
13
|
+
): Promise<ServiceResponse<{ url: string; redirect: boolean }>>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class BundlesRemoteDataSourceImpl extends BaseRemote implements BundlesRemoteDataSource {
|
|
17
|
+
async initiate(appId: string, payload: InitiateBundleRequest): Promise<ServiceResponse<Bundle>> {
|
|
18
|
+
const { data } = await api.post<ServiceResponse<Bundle>>(
|
|
19
|
+
`/v1/apps/${encodeURIComponent(appId)}/bundles`,
|
|
20
|
+
payload
|
|
21
|
+
);
|
|
22
|
+
return data;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async getById(appId: string, bundleId: string): Promise<ServiceResponse<Bundle>> {
|
|
26
|
+
const { data } = await api.get<ServiceResponse<Bundle>>(
|
|
27
|
+
`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}`
|
|
28
|
+
);
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getSignedDownloadUrl(
|
|
33
|
+
appId: string,
|
|
34
|
+
bundleId: string,
|
|
35
|
+
options?: { redirect?: boolean }
|
|
36
|
+
): Promise<ServiceResponse<{ url: string; redirect: boolean }>> {
|
|
37
|
+
const { data } = await api.get<ServiceResponse<{ url: string; redirect: boolean }>>(
|
|
38
|
+
`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/download`,
|
|
39
|
+
{ params: { redirect: options?.redirect ?? false } }
|
|
40
|
+
);
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const bundlesRemoteDataSource: BundlesRemoteDataSource = new BundlesRemoteDataSourceImpl();
|
|
46
|
+
|
|
47
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BundlesRemoteDataSource } from './remote';
|
|
2
|
+
import { bundlesRemoteDataSource } from './remote';
|
|
3
|
+
import type { Bundle, InitiateBundleRequest } from './types';
|
|
4
|
+
import { BaseRepository } from '../../base-repository';
|
|
5
|
+
|
|
6
|
+
export interface BundlesRepository {
|
|
7
|
+
initiate(appId: string, payload: InitiateBundleRequest): Promise<Bundle>;
|
|
8
|
+
getById(appId: string, bundleId: string): Promise<Bundle>;
|
|
9
|
+
getSignedDownloadUrl(appId: string, bundleId: string, options?: { redirect?: boolean }): Promise<{ url: string; redirect: boolean }>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class BundlesRepositoryImpl extends BaseRepository implements BundlesRepository {
|
|
13
|
+
constructor(private readonly remote: BundlesRemoteDataSource) {
|
|
14
|
+
super();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async initiate(appId: string, payload: InitiateBundleRequest): Promise<Bundle> {
|
|
18
|
+
const res = await this.remote.initiate(appId, payload);
|
|
19
|
+
return this.unwrapOrThrow(res);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async getById(appId: string, bundleId: string): Promise<Bundle> {
|
|
23
|
+
const res = await this.remote.getById(appId, bundleId);
|
|
24
|
+
return this.unwrapOrThrow(res);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async getSignedDownloadUrl(appId: string, bundleId: string, options?: { redirect?: boolean }): Promise<{ url: string; redirect: boolean }> {
|
|
28
|
+
const res = await this.remote.getSignedDownloadUrl(appId, bundleId, options);
|
|
29
|
+
return this.unwrapOrThrow(res);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const bundlesRepository: BundlesRepository = new BundlesRepositoryImpl(bundlesRemoteDataSource);
|
|
34
|
+
|
|
35
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type Platform = 'ios' | 'android';
|
|
2
|
+
|
|
3
|
+
export type BundleStatus = 'pending' | 'building' | 'succeeded' | 'failed';
|
|
4
|
+
|
|
5
|
+
export type Bundle = {
|
|
6
|
+
id: string;
|
|
7
|
+
appId: string;
|
|
8
|
+
fingerprint: string;
|
|
9
|
+
platform: Platform;
|
|
10
|
+
status: BundleStatus;
|
|
11
|
+
storageKey: string | null;
|
|
12
|
+
size: number | null;
|
|
13
|
+
checksumSha256: string | null;
|
|
14
|
+
contentType: string | null;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
updatedAt: string;
|
|
17
|
+
expiresAt: string | null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type InitiateBundleRequest = {
|
|
21
|
+
commitId?: string;
|
|
22
|
+
platform: Platform;
|
|
23
|
+
expiresAt?: string; // ISO string
|
|
24
|
+
idempotencyKey?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { api } from '../../../core/services/http';
|
|
2
|
+
import type { ServiceResponse } from '../../types';
|
|
3
|
+
import { BaseRemote } from '../../base-remote';
|
|
4
|
+
|
|
5
|
+
export type GetAppImageSignedUrlOptions = {
|
|
6
|
+
variant?: string;
|
|
7
|
+
redirect?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type AppImageSignedUrlBatchResponse = {
|
|
11
|
+
urls: Record<string, string | null>;
|
|
12
|
+
errors?: Record<string, { message: string; statusCode: number }>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export interface AppImagesRemoteDataSource {
|
|
16
|
+
getSignedUrl(
|
|
17
|
+
appId: string,
|
|
18
|
+
options?: GetAppImageSignedUrlOptions
|
|
19
|
+
): Promise<ServiceResponse<{ url: string; redirect: boolean }>>;
|
|
20
|
+
getSignedUrlsBatch(
|
|
21
|
+
appIds: string[],
|
|
22
|
+
options?: Pick<GetAppImageSignedUrlOptions, 'variant'>
|
|
23
|
+
): Promise<ServiceResponse<AppImageSignedUrlBatchResponse>>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class AppImagesRemoteDataSourceImpl extends BaseRemote implements AppImagesRemoteDataSource {
|
|
27
|
+
async getSignedUrl(
|
|
28
|
+
appId: string,
|
|
29
|
+
options?: GetAppImageSignedUrlOptions
|
|
30
|
+
): Promise<ServiceResponse<{ url: string; redirect: boolean }>> {
|
|
31
|
+
const { data } = await api.get<ServiceResponse<{ url: string; redirect: boolean }>>(
|
|
32
|
+
`/v1/apps/${encodeURIComponent(appId)}/image/url`,
|
|
33
|
+
{
|
|
34
|
+
params: {
|
|
35
|
+
variant: options?.variant,
|
|
36
|
+
redirect: options?.redirect ?? false,
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async getSignedUrlsBatch(
|
|
44
|
+
appIds: string[],
|
|
45
|
+
options?: Pick<GetAppImageSignedUrlOptions, 'variant'>
|
|
46
|
+
): Promise<ServiceResponse<AppImageSignedUrlBatchResponse>> {
|
|
47
|
+
const payload = {
|
|
48
|
+
appIds,
|
|
49
|
+
variant: options?.variant,
|
|
50
|
+
};
|
|
51
|
+
const { data } = await api.post<ServiceResponse<AppImageSignedUrlBatchResponse>>(
|
|
52
|
+
'/v1/apps/image/url/batch',
|
|
53
|
+
payload
|
|
54
|
+
);
|
|
55
|
+
return data;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const appImagesRemoteDataSource: AppImagesRemoteDataSource = new AppImagesRemoteDataSourceImpl();
|
|
60
|
+
|
|
61
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AppImageSignedUrlBatchResponse,
|
|
3
|
+
AppImagesRemoteDataSource,
|
|
4
|
+
GetAppImageSignedUrlOptions,
|
|
5
|
+
} from './remote';
|
|
6
|
+
import { appImagesRemoteDataSource } from './remote';
|
|
7
|
+
import { BaseRepository } from '../../base-repository';
|
|
8
|
+
|
|
9
|
+
export interface AppImagesRepository {
|
|
10
|
+
getSignedUrl(
|
|
11
|
+
appId: string,
|
|
12
|
+
options?: GetAppImageSignedUrlOptions
|
|
13
|
+
): Promise<{ url: string; redirect: boolean }>;
|
|
14
|
+
getSignedUrlsBatch(
|
|
15
|
+
appIds: string[],
|
|
16
|
+
options?: Pick<GetAppImageSignedUrlOptions, 'variant'>
|
|
17
|
+
): Promise<AppImageSignedUrlBatchResponse>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class AppImagesRepositoryImpl extends BaseRepository implements AppImagesRepository {
|
|
21
|
+
constructor(private readonly remote: AppImagesRemoteDataSource) {
|
|
22
|
+
super();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async getSignedUrl(
|
|
26
|
+
appId: string,
|
|
27
|
+
options?: GetAppImageSignedUrlOptions
|
|
28
|
+
): Promise<{ url: string; redirect: boolean }> {
|
|
29
|
+
const res = await this.remote.getSignedUrl(appId, options);
|
|
30
|
+
return this.unwrapOrThrow(res);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getSignedUrlsBatch(
|
|
34
|
+
appIds: string[],
|
|
35
|
+
options?: Pick<GetAppImageSignedUrlOptions, 'variant'>
|
|
36
|
+
): Promise<AppImageSignedUrlBatchResponse> {
|
|
37
|
+
const res = await this.remote.getSignedUrlsBatch(appIds, options);
|
|
38
|
+
if (res.responseObject && !res.success) {
|
|
39
|
+
return res.responseObject;
|
|
40
|
+
}
|
|
41
|
+
return this.unwrapOrThrow(res);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const appImagesRepository: AppImagesRepository = new AppImagesRepositoryImpl(appImagesRemoteDataSource);
|
|
46
|
+
|
|
47
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { api } from '../../core/services/http';
|
|
2
|
+
import type { ServiceResponse } from '../types';
|
|
3
|
+
import { BaseRemote } from '../base-remote';
|
|
4
|
+
import type {
|
|
5
|
+
App,
|
|
6
|
+
AppAnalyticsParams,
|
|
7
|
+
AppAnalyticsPoint,
|
|
8
|
+
AppInsights,
|
|
9
|
+
AppsSummary,
|
|
10
|
+
ForkAppRequest,
|
|
11
|
+
ImportGithubAppRequest,
|
|
12
|
+
ImportGithubAppResponse,
|
|
13
|
+
ListAppsSummaryParams,
|
|
14
|
+
ListLikedAppsParams,
|
|
15
|
+
ListPublicAppsParams,
|
|
16
|
+
LikedAppsList,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
export interface AppsRemoteDataSource {
|
|
20
|
+
list(projectId?: string): Promise<ServiceResponse<App[]>>;
|
|
21
|
+
listPublicOthers(params?: ListPublicAppsParams): Promise<ServiceResponse<App[]>>;
|
|
22
|
+
listSummary(params?: ListAppsSummaryParams): Promise<ServiceResponse<AppsSummary>>;
|
|
23
|
+
listLiked(params?: ListLikedAppsParams): Promise<ServiceResponse<LikedAppsList>>;
|
|
24
|
+
getById(appId: string): Promise<ServiceResponse<App>>;
|
|
25
|
+
fork(appId: string, payload: ForkAppRequest): Promise<ServiceResponse<App>>;
|
|
26
|
+
getInsights(appId: string): Promise<ServiceResponse<AppInsights>>;
|
|
27
|
+
getAnalytics(appId: string, params: AppAnalyticsParams): Promise<ServiceResponse<AppAnalyticsPoint[]>>;
|
|
28
|
+
importFromGithub(payload: ImportGithubAppRequest): Promise<ServiceResponse<ImportGithubAppResponse>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class AppsRemoteDataSourceImpl extends BaseRemote implements AppsRemoteDataSource {
|
|
32
|
+
async list(projectId?: string): Promise<ServiceResponse<App[]>> {
|
|
33
|
+
const params = projectId ? { projectId } : undefined;
|
|
34
|
+
const { data } = await api.get<ServiceResponse<App[]>>('/v1/apps', { params });
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async listPublicOthers(params?: ListPublicAppsParams): Promise<ServiceResponse<App[]>> {
|
|
39
|
+
const query = params ? { ...params } : undefined;
|
|
40
|
+
const { data } = await api.get<ServiceResponse<App[]>>('/v1/apps/public', { params: query });
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async listSummary(params?: ListAppsSummaryParams): Promise<ServiceResponse<AppsSummary>> {
|
|
45
|
+
const query = params ? { ...params } : undefined;
|
|
46
|
+
const { data } = await api.get<ServiceResponse<AppsSummary>>('/v1/apps/summary', {
|
|
47
|
+
params: query,
|
|
48
|
+
});
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async listLiked(params?: ListLikedAppsParams): Promise<ServiceResponse<LikedAppsList>> {
|
|
53
|
+
const query = params ? { ...params } : undefined;
|
|
54
|
+
const { data } = await api.get<ServiceResponse<LikedAppsList>>('/v1/apps/likes/mine', {
|
|
55
|
+
params: query,
|
|
56
|
+
});
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async getById(appId: string): Promise<ServiceResponse<App>> {
|
|
61
|
+
const { data } = await api.get<ServiceResponse<App>>(`/v1/apps/${encodeURIComponent(appId)}`);
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async fork(appId: string, payload: ForkAppRequest): Promise<ServiceResponse<App>> {
|
|
66
|
+
const { data } = await api.post<ServiceResponse<App>>(`/v1/apps/${encodeURIComponent(appId)}/fork`, payload);
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async importFromGithub(payload: ImportGithubAppRequest): Promise<ServiceResponse<ImportGithubAppResponse>> {
|
|
71
|
+
const { data } = await api.post<ServiceResponse<ImportGithubAppResponse>>('/v1/apps/import/github', payload);
|
|
72
|
+
return data;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async getInsights(appId: string): Promise<ServiceResponse<AppInsights>> {
|
|
76
|
+
const { data } = await api.get<ServiceResponse<AppInsights>>(
|
|
77
|
+
`/v1/apps/${encodeURIComponent(appId)}/insights`,
|
|
78
|
+
);
|
|
79
|
+
return data;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async getAnalytics(
|
|
83
|
+
appId: string,
|
|
84
|
+
params: AppAnalyticsParams,
|
|
85
|
+
): Promise<ServiceResponse<AppAnalyticsPoint[]>> {
|
|
86
|
+
const query = { ...params };
|
|
87
|
+
const { data } = await api.get<ServiceResponse<AppAnalyticsPoint[]>>(
|
|
88
|
+
`/v1/apps/${encodeURIComponent(appId)}/analytics`,
|
|
89
|
+
{ params: query },
|
|
90
|
+
);
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const appsRemoteDataSource: AppsRemoteDataSource = new AppsRemoteDataSourceImpl();
|
|
96
|
+
|
|
97
|
+
|