@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.
Files changed (172) hide show
  1. package/dist/index.js +255 -245
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +213 -203
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +9 -6
  6. package/src/components/chat/ChatComposer.tsx +277 -0
  7. package/src/components/chat/ChatHeader.tsx +31 -0
  8. package/src/components/chat/ChatMessageBubble.tsx +69 -0
  9. package/src/components/chat/ChatMessageList.tsx +137 -0
  10. package/src/components/chat/ChatPage.tsx +69 -0
  11. package/src/components/chat/ForkNoticeBanner.tsx +66 -0
  12. package/src/components/chat/MultilineTextInput.tsx +46 -0
  13. package/src/components/chat/ScrollToBottomButton.tsx +78 -0
  14. package/src/components/chat/TypingIndicator.tsx +54 -0
  15. package/src/components/chat/index.ts +28 -0
  16. package/src/components/comments/AppCommentsSheet.tsx +213 -0
  17. package/src/components/comments/CommentRow.tsx +63 -0
  18. package/src/components/comments/formatTimeAgo.ts +3 -0
  19. package/src/components/comments/index.ts +3 -0
  20. package/src/components/comments/useAppComments.ts +74 -0
  21. package/src/components/comments/useAppDetails.ts +35 -0
  22. package/src/components/comments/useIosKeyboardSnapFix.ts +24 -0
  23. package/src/components/dialogs/ConfirmMergeRequestDialog.tsx +156 -0
  24. package/src/components/dialogs/index.ts +4 -0
  25. package/src/components/draw/DrawColorPicker.tsx +77 -0
  26. package/src/components/draw/DrawModeOverlay.tsx +144 -0
  27. package/src/components/draw/DrawSurface.tsx +127 -0
  28. package/src/components/draw/DrawToolbar.tsx +253 -0
  29. package/src/components/draw/index.ts +15 -0
  30. package/src/components/draw/optionalHaptics.ts +15 -0
  31. package/src/components/draw/strokes.ts +21 -0
  32. package/src/components/draw/types.ts +9 -0
  33. package/src/components/floating-draggable-button/FloatingDraggableButton.tsx +323 -0
  34. package/src/components/floating-draggable-button/constants.ts +17 -0
  35. package/src/components/floating-draggable-button/index.ts +4 -0
  36. package/src/components/floating-draggable-button/types.ts +63 -0
  37. package/src/components/icons/MergeIcon.tsx +14 -0
  38. package/src/components/icons/StudioIcons.tsx +66 -0
  39. package/src/components/index.ts +17 -0
  40. package/src/components/merge-requests/MergeRequestStatusCard.tsx +179 -0
  41. package/src/components/merge-requests/ReviewMergeRequestActionButton.tsx +62 -0
  42. package/src/components/merge-requests/ReviewMergeRequestCard.tsx +192 -0
  43. package/src/components/merge-requests/ReviewMergeRequestCarousel.tsx +132 -0
  44. package/src/components/merge-requests/index.ts +7 -0
  45. package/src/components/merge-requests/mergeRequestStatusDisplay.ts +23 -0
  46. package/src/components/merge-requests/toIsoString.ts +9 -0
  47. package/src/components/merge-requests/useControlledExpansion.ts +16 -0
  48. package/src/components/models/index.ts +9 -0
  49. package/src/components/models/types.ts +43 -0
  50. package/src/components/overlays/EdgeGlowFrame.tsx +105 -0
  51. package/src/components/overlays/index.ts +4 -0
  52. package/src/components/preview/PreviewHeroCard.tsx +58 -0
  53. package/src/components/preview/PreviewImage.tsx +22 -0
  54. package/src/components/preview/PreviewMetaRow.tsx +70 -0
  55. package/src/components/preview/PreviewPage.tsx +36 -0
  56. package/src/components/preview/PreviewPlaceholder.tsx +72 -0
  57. package/src/components/preview/PreviewStatusBadge.tsx +63 -0
  58. package/src/components/preview/StatsBar.tsx +109 -0
  59. package/src/components/preview/index.ts +22 -0
  60. package/src/components/primitives/Avatar.tsx +68 -0
  61. package/src/components/primitives/Button.tsx +102 -0
  62. package/src/components/primitives/Card.tsx +30 -0
  63. package/src/components/primitives/Divider.tsx +17 -0
  64. package/src/components/primitives/Icon.tsx +40 -0
  65. package/src/components/primitives/MarkdownText.tsx +72 -0
  66. package/src/components/primitives/Modal.tsx +53 -0
  67. package/src/components/primitives/Surface.tsx +42 -0
  68. package/src/components/primitives/Text.tsx +83 -0
  69. package/src/components/primitives/index.ts +35 -0
  70. package/src/components/primitives/types.ts +30 -0
  71. package/src/components/studio-sheet/StudioBottomSheet.tsx +114 -0
  72. package/src/components/studio-sheet/StudioSheetBackground.tsx +63 -0
  73. package/src/components/studio-sheet/StudioSheetHeader.tsx +35 -0
  74. package/src/components/studio-sheet/StudioSheetHeaderIconButton.tsx +109 -0
  75. package/src/components/studio-sheet/StudioSheetPager.tsx +66 -0
  76. package/src/components/studio-sheet/index.ts +18 -0
  77. package/src/components/studio-sheet/types.ts +5 -0
  78. package/src/components/utils/color.ts +25 -0
  79. package/src/components/utils/formatTimeAgo.ts +19 -0
  80. package/src/core/logger.ts +42 -0
  81. package/src/core/services/http/baseUrl.ts +3 -0
  82. package/src/core/services/http/index.ts +128 -0
  83. package/src/core/services/http/public.ts +14 -0
  84. package/src/core/services/supabase/auth.ts +41 -0
  85. package/src/core/services/supabase/client.ts +43 -0
  86. package/src/core/services/supabase/index.ts +7 -0
  87. package/src/data/agent/remote.ts +30 -0
  88. package/src/data/agent/repository.ts +34 -0
  89. package/src/data/agent/types.ts +28 -0
  90. package/src/data/apps/bundles/remote.ts +47 -0
  91. package/src/data/apps/bundles/repository.ts +35 -0
  92. package/src/data/apps/bundles/types.ts +27 -0
  93. package/src/data/apps/images/remote.ts +61 -0
  94. package/src/data/apps/images/repository.ts +47 -0
  95. package/src/data/apps/remote.ts +97 -0
  96. package/src/data/apps/repository.ts +185 -0
  97. package/src/data/apps/types.ts +206 -0
  98. package/src/data/attachment/remote.ts +32 -0
  99. package/src/data/attachment/repository.ts +40 -0
  100. package/src/data/attachment/types.ts +42 -0
  101. package/src/data/base-remote.ts +3 -0
  102. package/src/data/base-repository.ts +11 -0
  103. package/src/data/comments/likes/remote.ts +87 -0
  104. package/src/data/comments/likes/repository.ts +61 -0
  105. package/src/data/comments/likes/types.ts +47 -0
  106. package/src/data/comments/remote.ts +71 -0
  107. package/src/data/comments/repository.ts +53 -0
  108. package/src/data/comments/types.ts +60 -0
  109. package/src/data/github/remote.ts +23 -0
  110. package/src/data/github/repository.ts +35 -0
  111. package/src/data/github/types.ts +23 -0
  112. package/src/data/home/remote.ts +24 -0
  113. package/src/data/home/repository.ts +28 -0
  114. package/src/data/home/types.ts +70 -0
  115. package/src/data/index.ts +3 -0
  116. package/src/data/likes/remote.ts +57 -0
  117. package/src/data/likes/repository.ts +47 -0
  118. package/src/data/likes/types.ts +46 -0
  119. package/src/data/me/remote.ts +28 -0
  120. package/src/data/me/repository.ts +30 -0
  121. package/src/data/me/types.ts +14 -0
  122. package/src/data/merge-requests/remote.ts +76 -0
  123. package/src/data/merge-requests/repository.ts +66 -0
  124. package/src/data/merge-requests/types.ts +33 -0
  125. package/src/data/messages/remote.ts +21 -0
  126. package/src/data/messages/repository.ts +104 -0
  127. package/src/data/messages/types.ts +20 -0
  128. package/src/data/public/studio-config/remote.ts +19 -0
  129. package/src/data/public/studio-config/repository.ts +23 -0
  130. package/src/data/public/studio-config/types.ts +6 -0
  131. package/src/data/ratings/remote.ts +76 -0
  132. package/src/data/ratings/repository.ts +63 -0
  133. package/src/data/ratings/types.ts +57 -0
  134. package/src/data/threads/remote.ts +40 -0
  135. package/src/data/threads/repository.ts +41 -0
  136. package/src/data/threads/types.ts +25 -0
  137. package/src/data/types.ts +8 -0
  138. package/src/data/users/remote.ts +31 -0
  139. package/src/data/users/repository.ts +45 -0
  140. package/src/data/users/types.ts +15 -0
  141. package/src/index.ts +6 -0
  142. package/src/studio/ComergeStudio.tsx +246 -0
  143. package/src/studio/bootstrap/StudioBootstrap.tsx +45 -0
  144. package/src/studio/bootstrap/useStudioBootstrap.ts +51 -0
  145. package/src/studio/hooks/useApp.ts +83 -0
  146. package/src/studio/hooks/useAppStats.ts +111 -0
  147. package/src/studio/hooks/useAttachmentUpload.ts +59 -0
  148. package/src/studio/hooks/useBundleManager.ts +389 -0
  149. package/src/studio/hooks/useMergeRequests.ts +173 -0
  150. package/src/studio/hooks/useStudioActions.ts +96 -0
  151. package/src/studio/hooks/useThreadMessages.ts +85 -0
  152. package/src/studio/lib/chat.ts +34 -0
  153. package/src/studio/ui/ChatPanel.tsx +154 -0
  154. package/src/studio/ui/ConfirmMergeFlow.tsx +55 -0
  155. package/src/studio/ui/PreviewPanel.tsx +131 -0
  156. package/src/studio/ui/RuntimeRenderer.tsx +40 -0
  157. package/src/studio/ui/StudioOverlay.tsx +257 -0
  158. package/src/studio/ui/preview-panel/PressableCardRow.tsx +49 -0
  159. package/src/studio/ui/preview-panel/PreviewCollaborateSection.tsx +174 -0
  160. package/src/studio/ui/preview-panel/PreviewCustomizeSection.tsx +160 -0
  161. package/src/studio/ui/preview-panel/PreviewHeroSection.tsx +56 -0
  162. package/src/studio/ui/preview-panel/PreviewMetaSection.tsx +67 -0
  163. package/src/studio/ui/preview-panel/PreviewPanelHeader.tsx +48 -0
  164. package/src/studio/ui/preview-panel/SectionTitle.tsx +31 -0
  165. package/src/studio/ui/preview-panel/usePreviewPanelData.ts +132 -0
  166. package/src/studio/ui/preview-panel/utils.ts +29 -0
  167. package/src/theme/index.ts +5 -0
  168. package/src/theme/tokens.ts +118 -0
  169. package/src/theme/types.ts +90 -0
  170. package/src/theme/useTheme.ts +11 -0
  171. package/dist/assets/images/merge.svg +0 -3
  172. 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,3 @@
1
+ export abstract class BaseRemote {}
2
+
3
+
@@ -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
+