@comergehq/studio 0.1.1 → 0.1.3
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.js +255 -245
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +213 -203
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
- 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 +14 -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 +246 -0
- package/src/studio/bootstrap/StudioBootstrap.tsx +45 -0
- package/src/studio/bootstrap/useStudioBootstrap.ts +51 -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,185 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
App,
|
|
3
|
+
AppAnalyticsParams,
|
|
4
|
+
AppAnalyticsPoint,
|
|
5
|
+
AppInsights,
|
|
6
|
+
AppsSummary,
|
|
7
|
+
ForkAppRequest,
|
|
8
|
+
ImportGithubAppRequest,
|
|
9
|
+
ImportGithubAppResponse,
|
|
10
|
+
ListAppsSummaryParams,
|
|
11
|
+
ListLikedAppsParams,
|
|
12
|
+
ListPublicAppsParams,
|
|
13
|
+
LikedAppsList,
|
|
14
|
+
} from './types';
|
|
15
|
+
import { appsRemoteDataSource } from './remote';
|
|
16
|
+
import type { AppsRemoteDataSource } from './remote';
|
|
17
|
+
import { BaseRepository } from '../../data/base-repository';
|
|
18
|
+
import { getSupabaseClient } from '../../core/services/supabase';
|
|
19
|
+
|
|
20
|
+
type DbAppRow = {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
description: string | null;
|
|
24
|
+
apple_app_store_category: string | null;
|
|
25
|
+
google_play_category: string | null;
|
|
26
|
+
pg_rating: string | null;
|
|
27
|
+
project_id: string;
|
|
28
|
+
platform: string | null;
|
|
29
|
+
is_public: boolean;
|
|
30
|
+
created_by: string;
|
|
31
|
+
status: App['status'];
|
|
32
|
+
status_error: string | null;
|
|
33
|
+
status_changed_at: string | null;
|
|
34
|
+
head_commit_id: string | null;
|
|
35
|
+
forked_from_commit_id: string | null;
|
|
36
|
+
forked_from_app_id: string | null;
|
|
37
|
+
thread_id: string | null;
|
|
38
|
+
created_at: string;
|
|
39
|
+
updated_at: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type AppSubscriptionHandlers = {
|
|
43
|
+
onInsert?: (app: App) => void;
|
|
44
|
+
onUpdate?: (app: App) => void;
|
|
45
|
+
onDelete?: (app: App) => void;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function mapDbAppRow(row: DbAppRow): App {
|
|
49
|
+
return {
|
|
50
|
+
id: row.id,
|
|
51
|
+
name: row.name,
|
|
52
|
+
description: row.description,
|
|
53
|
+
appleAppStoreCategory: row.apple_app_store_category,
|
|
54
|
+
googlePlayCategory: row.google_play_category,
|
|
55
|
+
pgRating: row.pg_rating,
|
|
56
|
+
projectId: row.project_id,
|
|
57
|
+
platform: row.platform,
|
|
58
|
+
isPublic: row.is_public,
|
|
59
|
+
createdBy: row.created_by,
|
|
60
|
+
status: row.status,
|
|
61
|
+
statusError: row.status_error,
|
|
62
|
+
statusChangedAt: row.status_changed_at,
|
|
63
|
+
headCommitId: row.head_commit_id,
|
|
64
|
+
forkedFromCommitId: row.forked_from_commit_id,
|
|
65
|
+
forkedFromAppId: row.forked_from_app_id,
|
|
66
|
+
threadId: row.thread_id,
|
|
67
|
+
createdAt: row.created_at,
|
|
68
|
+
updatedAt: row.updated_at,
|
|
69
|
+
insights: null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface AppsRepository {
|
|
74
|
+
list(projectId?: string): Promise<App[]>;
|
|
75
|
+
listPublicOthers(params?: ListPublicAppsParams): Promise<App[]>;
|
|
76
|
+
getSummary(params?: ListAppsSummaryParams): Promise<AppsSummary>;
|
|
77
|
+
listLiked(params?: ListLikedAppsParams): Promise<LikedAppsList>;
|
|
78
|
+
getById(appId: string): Promise<App>;
|
|
79
|
+
fork(appId: string, payload: ForkAppRequest): Promise<App>;
|
|
80
|
+
getInsights(appId: string): Promise<AppInsights>;
|
|
81
|
+
getAnalytics(appId: string, params: AppAnalyticsParams): Promise<AppAnalyticsPoint[]>;
|
|
82
|
+
subscribeCreatedApps(userId: string, handlers: AppSubscriptionHandlers): () => void;
|
|
83
|
+
subscribeApp(appId: string, handlers: AppSubscriptionHandlers): () => void;
|
|
84
|
+
importFromGithub(payload: ImportGithubAppRequest): Promise<ImportGithubAppResponse>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class AppsRepositoryImpl extends BaseRepository implements AppsRepository {
|
|
88
|
+
constructor(private readonly remote: AppsRemoteDataSource) {
|
|
89
|
+
super();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async list(projectId?: string): Promise<App[]> {
|
|
93
|
+
const res = await this.remote.list(projectId);
|
|
94
|
+
return this.unwrapOrThrow(res);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async listPublicOthers(params?: ListPublicAppsParams): Promise<App[]> {
|
|
98
|
+
const res = await this.remote.listPublicOthers(params);
|
|
99
|
+
return this.unwrapOrThrow(res);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async getSummary(params?: ListAppsSummaryParams): Promise<AppsSummary> {
|
|
103
|
+
const res = await this.remote.listSummary(params);
|
|
104
|
+
return this.unwrapOrThrow(res);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async listLiked(params?: ListLikedAppsParams): Promise<LikedAppsList> {
|
|
108
|
+
const res = await this.remote.listLiked(params);
|
|
109
|
+
return this.unwrapOrThrow(res);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getById(appId: string): Promise<App> {
|
|
113
|
+
const res = await this.remote.getById(appId);
|
|
114
|
+
return this.unwrapOrThrow(res);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async fork(appId: string, payload: ForkAppRequest): Promise<App> {
|
|
118
|
+
const res = await this.remote.fork(appId, payload);
|
|
119
|
+
return this.unwrapOrThrow(res);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async getInsights(appId: string): Promise<AppInsights> {
|
|
123
|
+
const res = await this.remote.getInsights(appId);
|
|
124
|
+
return this.unwrapOrThrow(res);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async getAnalytics(appId: string, params: AppAnalyticsParams): Promise<AppAnalyticsPoint[]> {
|
|
128
|
+
const res = await this.remote.getAnalytics(appId, params);
|
|
129
|
+
return this.unwrapOrThrow(res);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async importFromGithub(payload: ImportGithubAppRequest): Promise<ImportGithubAppResponse> {
|
|
133
|
+
const res = await this.remote.importFromGithub(payload);
|
|
134
|
+
return this.unwrapOrThrow(res);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
subscribeCreatedApps(userId: string, handlers: AppSubscriptionHandlers): () => void {
|
|
138
|
+
if (!userId) return () => {};
|
|
139
|
+
return this.subscribeToAppChannel(`apps:createdBy:${userId}`, `created_by=eq.${userId}`, handlers);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
subscribeApp(appId: string, handlers: AppSubscriptionHandlers): () => void {
|
|
143
|
+
if (!appId) return () => {};
|
|
144
|
+
return this.subscribeToAppChannel(`apps:id:${appId}`, `id=eq.${appId}`, handlers);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private subscribeToAppChannel(channelKey: string, filter: string, handlers: AppSubscriptionHandlers): () => void {
|
|
148
|
+
const supabase = getSupabaseClient();
|
|
149
|
+
const channel = supabase
|
|
150
|
+
.channel(channelKey)
|
|
151
|
+
.on(
|
|
152
|
+
'postgres_changes',
|
|
153
|
+
{ event: 'INSERT', schema: 'public', table: 'app', filter },
|
|
154
|
+
(payload) => {
|
|
155
|
+
console.log('[subscribeToAppChannel] onInsert', payload);
|
|
156
|
+
handlers.onInsert?.(mapDbAppRow(payload.new as DbAppRow));
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
.on(
|
|
160
|
+
'postgres_changes',
|
|
161
|
+
{ event: 'UPDATE', schema: 'public', table: 'app', filter },
|
|
162
|
+
(payload) => {
|
|
163
|
+
console.log('[subscribeToAppChannel] onUpdate', payload);
|
|
164
|
+
handlers.onUpdate?.(mapDbAppRow(payload.new as DbAppRow));
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
.on(
|
|
168
|
+
'postgres_changes',
|
|
169
|
+
{ event: 'DELETE', schema: 'public', table: 'app', filter },
|
|
170
|
+
(payload) => {
|
|
171
|
+
console.log('[subscribeToAppChannel] onDelete', payload);
|
|
172
|
+
handlers.onDelete?.(mapDbAppRow(payload.old as DbAppRow));
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
.subscribe();
|
|
176
|
+
|
|
177
|
+
return () => {
|
|
178
|
+
supabase.removeChannel(channel);
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const appsRepository: AppsRepository = new AppsRepositoryImpl(appsRemoteDataSource);
|
|
184
|
+
|
|
185
|
+
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
export type AppInsightsSummary = {
|
|
2
|
+
totalDownloads: number;
|
|
3
|
+
totalDownloadUsers: number;
|
|
4
|
+
totalLikes: number;
|
|
5
|
+
totalComments: number;
|
|
6
|
+
totalRatings: number;
|
|
7
|
+
averageRating: number | null;
|
|
8
|
+
totalMergeRequests: number;
|
|
9
|
+
totalForks: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type DownloadUserSummary = {
|
|
13
|
+
userId: string;
|
|
14
|
+
totalDownloads: number;
|
|
15
|
+
lastDownloadAt: string | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ForkEntry = {
|
|
19
|
+
appId: string;
|
|
20
|
+
userId: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type MergeRequestEntry = {
|
|
25
|
+
id: string;
|
|
26
|
+
sourceAppId: string;
|
|
27
|
+
createdBy: string;
|
|
28
|
+
status: string;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type LikeEntry = {
|
|
33
|
+
userId: string;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type CommentEntry = {
|
|
38
|
+
id: string;
|
|
39
|
+
userId: string;
|
|
40
|
+
commentType: string;
|
|
41
|
+
createdAt: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type RatingEntry = {
|
|
45
|
+
userId: string;
|
|
46
|
+
rating: number;
|
|
47
|
+
createdAt: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type AppInsights = {
|
|
51
|
+
downloads: {
|
|
52
|
+
total: number;
|
|
53
|
+
uniqueUsers: number;
|
|
54
|
+
perUser: DownloadUserSummary[];
|
|
55
|
+
};
|
|
56
|
+
forks: {
|
|
57
|
+
total: number;
|
|
58
|
+
entries: ForkEntry[];
|
|
59
|
+
};
|
|
60
|
+
mergeRequests: {
|
|
61
|
+
total: number;
|
|
62
|
+
approved: number;
|
|
63
|
+
merged: number;
|
|
64
|
+
entries: MergeRequestEntry[];
|
|
65
|
+
};
|
|
66
|
+
likes: {
|
|
67
|
+
total: number;
|
|
68
|
+
entries: LikeEntry[];
|
|
69
|
+
};
|
|
70
|
+
comments: {
|
|
71
|
+
total: number;
|
|
72
|
+
entries: CommentEntry[];
|
|
73
|
+
};
|
|
74
|
+
ratings: {
|
|
75
|
+
total: number;
|
|
76
|
+
average: number | null;
|
|
77
|
+
entries: RatingEntry[];
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const APP_METRIC_TYPES = [
|
|
82
|
+
'downloads',
|
|
83
|
+
'likes',
|
|
84
|
+
'comments',
|
|
85
|
+
'forks',
|
|
86
|
+
'mergeRequests',
|
|
87
|
+
'mergeRequestApprovals',
|
|
88
|
+
] as const;
|
|
89
|
+
|
|
90
|
+
export type AppMetricType = (typeof APP_METRIC_TYPES)[number];
|
|
91
|
+
|
|
92
|
+
export const APP_ANALYTIC_INTERVALS = ['day', 'hour'] as const;
|
|
93
|
+
|
|
94
|
+
export type AppAnalyticsInterval = (typeof APP_ANALYTIC_INTERVALS)[number];
|
|
95
|
+
|
|
96
|
+
export type AppAnalyticsParams = {
|
|
97
|
+
type: AppMetricType;
|
|
98
|
+
startDate: string;
|
|
99
|
+
endDate: string;
|
|
100
|
+
interval?: AppAnalyticsInterval;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export type AppAnalyticsPoint = {
|
|
104
|
+
date: string;
|
|
105
|
+
value: number;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type App = {
|
|
109
|
+
id: string;
|
|
110
|
+
name: string;
|
|
111
|
+
description: string | null;
|
|
112
|
+
appleAppStoreCategory: string | null;
|
|
113
|
+
googlePlayCategory: string | null;
|
|
114
|
+
pgRating: string | null;
|
|
115
|
+
projectId: string;
|
|
116
|
+
platform: string | null;
|
|
117
|
+
isPublic: boolean;
|
|
118
|
+
isLiked?: boolean;
|
|
119
|
+
createdBy: string;
|
|
120
|
+
status: 'ready' | 'creating' | 'editing' | 'forking' | 'merging' | 'error' | 'archived';
|
|
121
|
+
statusError: string | null;
|
|
122
|
+
statusChangedAt: string | null;
|
|
123
|
+
headCommitId: string | null;
|
|
124
|
+
forkedFromCommitId: string | null;
|
|
125
|
+
forkedFromAppId: string | null;
|
|
126
|
+
threadId: string | null;
|
|
127
|
+
createdAt: string;
|
|
128
|
+
updatedAt: string;
|
|
129
|
+
insights?: AppInsightsSummary | null;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export type AppStatus = App['status'];
|
|
133
|
+
|
|
134
|
+
export type ForkAppRequest = {
|
|
135
|
+
name?: string;
|
|
136
|
+
platform?: string;
|
|
137
|
+
forkedFromCommitId?: string;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type ImportGithubAppRequest = {
|
|
141
|
+
repoFullName: string;
|
|
142
|
+
branch?: string;
|
|
143
|
+
path?: string;
|
|
144
|
+
appName?: string;
|
|
145
|
+
threadId?: string;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export type ImportGithubAppResponse = {
|
|
149
|
+
appId: string;
|
|
150
|
+
projectId: string;
|
|
151
|
+
threadId: string;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export type ListPublicAppsParams = {
|
|
155
|
+
limit?: number;
|
|
156
|
+
offset?: number;
|
|
157
|
+
q?: string;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export type ListAppsSummaryParams = ListPublicAppsParams & {
|
|
161
|
+
projectId?: string;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export type AppsSummary = {
|
|
165
|
+
mine: App[];
|
|
166
|
+
public: App[];
|
|
167
|
+
pagination: {
|
|
168
|
+
limit: number;
|
|
169
|
+
offset: number;
|
|
170
|
+
q: string | null;
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export type ListLikedAppsParams = {
|
|
175
|
+
limit?: number;
|
|
176
|
+
offset?: number;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export type LikedAppListItem = {
|
|
180
|
+
likeId: string;
|
|
181
|
+
appId: string;
|
|
182
|
+
likedAt: string;
|
|
183
|
+
app: App;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export type LikedAppsList = {
|
|
187
|
+
items: LikedAppListItem[];
|
|
188
|
+
pageInfo: {
|
|
189
|
+
limit: number;
|
|
190
|
+
offset: number;
|
|
191
|
+
total: number;
|
|
192
|
+
hasMore: boolean;
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const APP_STATUS_LABEL: Record<AppStatus, string> = {
|
|
197
|
+
ready: 'Ready',
|
|
198
|
+
creating: 'Creating',
|
|
199
|
+
editing: 'Editing',
|
|
200
|
+
forking: 'Forking',
|
|
201
|
+
merging: 'Merging',
|
|
202
|
+
error: 'Error',
|
|
203
|
+
archived: 'Archived',
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { api } from '../../core/services/http';
|
|
2
|
+
import { BaseRemote } from '../base-remote';
|
|
3
|
+
import type { ServiceResponse } from '../types';
|
|
4
|
+
import type {
|
|
5
|
+
PresignAttachmentsRequest,
|
|
6
|
+
PresignAttachmentsResponse,
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
9
|
+
export interface AttachmentRemoteDataSource {
|
|
10
|
+
presign(
|
|
11
|
+
payload: PresignAttachmentsRequest
|
|
12
|
+
): Promise<ServiceResponse<PresignAttachmentsResponse>>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class AttachmentRemoteDataSourceImpl
|
|
16
|
+
extends BaseRemote
|
|
17
|
+
implements AttachmentRemoteDataSource
|
|
18
|
+
{
|
|
19
|
+
async presign(
|
|
20
|
+
payload: PresignAttachmentsRequest
|
|
21
|
+
): Promise<ServiceResponse<PresignAttachmentsResponse>> {
|
|
22
|
+
const { data } = await api.post<ServiceResponse<PresignAttachmentsResponse>>(
|
|
23
|
+
'/v1/attachments/presign',
|
|
24
|
+
payload
|
|
25
|
+
);
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const attachmentRemoteDataSource: AttachmentRemoteDataSource =
|
|
31
|
+
new AttachmentRemoteDataSourceImpl();
|
|
32
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AttachmentRemoteDataSource } from './remote';
|
|
2
|
+
import { attachmentRemoteDataSource } from './remote';
|
|
3
|
+
import type {
|
|
4
|
+
PresignAttachmentsRequest,
|
|
5
|
+
PresignAttachmentsResponse,
|
|
6
|
+
PresignedUpload,
|
|
7
|
+
} from './types';
|
|
8
|
+
import { BaseRepository } from '../../data/base-repository';
|
|
9
|
+
|
|
10
|
+
export interface AttachmentRepository {
|
|
11
|
+
presign(payload: PresignAttachmentsRequest): Promise<PresignAttachmentsResponse>;
|
|
12
|
+
upload(upload: PresignedUpload, file: Blob | File): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class AttachmentRepositoryImpl extends BaseRepository implements AttachmentRepository {
|
|
16
|
+
constructor(private readonly remote: AttachmentRemoteDataSource) {
|
|
17
|
+
super();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async presign(payload: PresignAttachmentsRequest): Promise<PresignAttachmentsResponse> {
|
|
21
|
+
const res = await this.remote.presign(payload);
|
|
22
|
+
return this.unwrapOrThrow(res);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async upload(upload: PresignedUpload, file: Blob | File): Promise<void> {
|
|
26
|
+
const resp = await fetch(upload.uploadUrl, {
|
|
27
|
+
method: 'PUT',
|
|
28
|
+
headers: upload.headers,
|
|
29
|
+
body: file,
|
|
30
|
+
});
|
|
31
|
+
if (!resp.ok) {
|
|
32
|
+
throw new Error(`upload failed: ${resp.status}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const attachmentRepository: AttachmentRepository = new AttachmentRepositoryImpl(
|
|
38
|
+
attachmentRemoteDataSource
|
|
39
|
+
);
|
|
40
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type AttachmentMeta = {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
bucket: string;
|
|
5
|
+
path: string;
|
|
6
|
+
mimeType: string;
|
|
7
|
+
size: number;
|
|
8
|
+
checksum?: string;
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
durationMs?: number;
|
|
12
|
+
uploadStatus?: 'pending' | 'uploaded';
|
|
13
|
+
downloadUrl?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type PresignFile = {
|
|
17
|
+
name: string;
|
|
18
|
+
size: number;
|
|
19
|
+
mimeType: string;
|
|
20
|
+
checksum?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type PresignAttachmentsRequest = {
|
|
24
|
+
threadId: string;
|
|
25
|
+
files: PresignFile[];
|
|
26
|
+
appId?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type PresignedUpload = {
|
|
30
|
+
uploadUrl: string;
|
|
31
|
+
headers: Record<string, string>;
|
|
32
|
+
token: string | null;
|
|
33
|
+
attachment: AttachmentMeta;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type PresignAttachmentsResponse = {
|
|
37
|
+
appId: string;
|
|
38
|
+
threadId: string;
|
|
39
|
+
expiresIn: number;
|
|
40
|
+
uploads: PresignedUpload[];
|
|
41
|
+
};
|
|
42
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ServiceResponse } from './types';
|
|
2
|
+
|
|
3
|
+
export abstract class BaseRepository {
|
|
4
|
+
protected unwrapOrThrow<T>(res: ServiceResponse<T>): T {
|
|
5
|
+
if (res.success && res.responseObject) return res.responseObject;
|
|
6
|
+
const msg = res.message || 'Request failed';
|
|
7
|
+
throw new Error(msg);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { api } from '../../../core/services/http';
|
|
2
|
+
import { BaseRemote } from '../../base-remote';
|
|
3
|
+
import type { ServiceResponse } from '../../types';
|
|
4
|
+
import type {
|
|
5
|
+
AppCommentLikeList,
|
|
6
|
+
AppCommentLikeMutationResult,
|
|
7
|
+
AppCommentLikeStatsResult,
|
|
8
|
+
CreateAppCommentLikeInput,
|
|
9
|
+
ListAppCommentLikesQuery,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
export interface AppCommentLikesRemoteDataSource {
|
|
13
|
+
list(
|
|
14
|
+
appId: string,
|
|
15
|
+
commentId: string,
|
|
16
|
+
query?: ListAppCommentLikesQuery
|
|
17
|
+
): Promise<ServiceResponse<AppCommentLikeList>>;
|
|
18
|
+
create(
|
|
19
|
+
appId: string,
|
|
20
|
+
commentId: string,
|
|
21
|
+
payload: CreateAppCommentLikeInput
|
|
22
|
+
): Promise<ServiceResponse<AppCommentLikeMutationResult>>;
|
|
23
|
+
removeById(
|
|
24
|
+
appId: string,
|
|
25
|
+
commentId: string,
|
|
26
|
+
likeId: string
|
|
27
|
+
): Promise<ServiceResponse<AppCommentLikeStatsResult>>;
|
|
28
|
+
removeMine(appId: string, commentId: string): Promise<ServiceResponse<AppCommentLikeStatsResult>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class AppCommentLikesRemoteDataSourceImpl
|
|
32
|
+
extends BaseRemote
|
|
33
|
+
implements AppCommentLikesRemoteDataSource
|
|
34
|
+
{
|
|
35
|
+
async list(
|
|
36
|
+
appId: string,
|
|
37
|
+
commentId: string,
|
|
38
|
+
query?: ListAppCommentLikesQuery
|
|
39
|
+
): Promise<ServiceResponse<AppCommentLikeList>> {
|
|
40
|
+
const params = query ? { ...query } : undefined;
|
|
41
|
+
const { data } = await api.get<ServiceResponse<AppCommentLikeList>>(
|
|
42
|
+
`/v1/apps/${encodeURIComponent(appId)}/comments/${encodeURIComponent(commentId)}/likes`,
|
|
43
|
+
{ params }
|
|
44
|
+
);
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async create(
|
|
49
|
+
appId: string,
|
|
50
|
+
commentId: string,
|
|
51
|
+
payload: CreateAppCommentLikeInput
|
|
52
|
+
): Promise<ServiceResponse<AppCommentLikeMutationResult>> {
|
|
53
|
+
const { data } = await api.post<ServiceResponse<AppCommentLikeMutationResult>>(
|
|
54
|
+
`/v1/apps/${encodeURIComponent(appId)}/comments/${encodeURIComponent(commentId)}/likes`,
|
|
55
|
+
payload
|
|
56
|
+
);
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async removeById(
|
|
61
|
+
appId: string,
|
|
62
|
+
commentId: string,
|
|
63
|
+
likeId: string
|
|
64
|
+
): Promise<ServiceResponse<AppCommentLikeStatsResult>> {
|
|
65
|
+
const { data } = await api.delete<ServiceResponse<AppCommentLikeStatsResult>>(
|
|
66
|
+
`/v1/apps/${encodeURIComponent(appId)}/comments/${encodeURIComponent(commentId)}/likes/${encodeURIComponent(
|
|
67
|
+
likeId
|
|
68
|
+
)}`
|
|
69
|
+
);
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async removeMine(
|
|
74
|
+
appId: string,
|
|
75
|
+
commentId: string
|
|
76
|
+
): Promise<ServiceResponse<AppCommentLikeStatsResult>> {
|
|
77
|
+
const { data } = await api.delete<ServiceResponse<AppCommentLikeStatsResult>>(
|
|
78
|
+
`/v1/apps/${encodeURIComponent(appId)}/comments/${encodeURIComponent(commentId)}/likes/me`
|
|
79
|
+
);
|
|
80
|
+
return data;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const appCommentLikesRemoteDataSource: AppCommentLikesRemoteDataSource =
|
|
85
|
+
new AppCommentLikesRemoteDataSourceImpl();
|
|
86
|
+
|
|
87
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { BaseRepository } from '../../base-repository';
|
|
2
|
+
import type { AppCommentLikesRemoteDataSource } from './remote';
|
|
3
|
+
import { appCommentLikesRemoteDataSource } from './remote';
|
|
4
|
+
import type {
|
|
5
|
+
AppCommentLikeList,
|
|
6
|
+
AppCommentLikeMutationResult,
|
|
7
|
+
AppCommentLikeStatsResult,
|
|
8
|
+
CreateAppCommentLikeInput,
|
|
9
|
+
ListAppCommentLikesQuery,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
export interface AppCommentLikesRepository {
|
|
13
|
+
list(appId: string, commentId: string, query?: ListAppCommentLikesQuery): Promise<AppCommentLikeList>;
|
|
14
|
+
create(
|
|
15
|
+
appId: string,
|
|
16
|
+
commentId: string,
|
|
17
|
+
payload: CreateAppCommentLikeInput
|
|
18
|
+
): Promise<AppCommentLikeMutationResult>;
|
|
19
|
+
removeById(appId: string, commentId: string, likeId: string): Promise<AppCommentLikeStatsResult>;
|
|
20
|
+
removeMine(appId: string, commentId: string): Promise<AppCommentLikeStatsResult>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class AppCommentLikesRepositoryImpl extends BaseRepository implements AppCommentLikesRepository {
|
|
24
|
+
constructor(private readonly remote: AppCommentLikesRemoteDataSource) {
|
|
25
|
+
super();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async list(appId: string, commentId: string, query?: ListAppCommentLikesQuery): Promise<AppCommentLikeList> {
|
|
29
|
+
const res = await this.remote.list(appId, commentId, query);
|
|
30
|
+
return this.unwrapOrThrow(res);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async create(
|
|
34
|
+
appId: string,
|
|
35
|
+
commentId: string,
|
|
36
|
+
payload: CreateAppCommentLikeInput
|
|
37
|
+
): Promise<AppCommentLikeMutationResult> {
|
|
38
|
+
const res = await this.remote.create(appId, commentId, payload);
|
|
39
|
+
return this.unwrapOrThrow(res);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async removeById(
|
|
43
|
+
appId: string,
|
|
44
|
+
commentId: string,
|
|
45
|
+
likeId: string
|
|
46
|
+
): Promise<AppCommentLikeStatsResult> {
|
|
47
|
+
const res = await this.remote.removeById(appId, commentId, likeId);
|
|
48
|
+
return this.unwrapOrThrow(res);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async removeMine(appId: string, commentId: string): Promise<AppCommentLikeStatsResult> {
|
|
52
|
+
const res = await this.remote.removeMine(appId, commentId);
|
|
53
|
+
return this.unwrapOrThrow(res);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const appCommentLikesRepository: AppCommentLikesRepository = new AppCommentLikesRepositoryImpl(
|
|
58
|
+
appCommentLikesRemoteDataSource
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
|