@tuturuuu/utils 0.0.3 → 0.6.1
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/CHANGELOG.md +313 -0
- package/biome.json +5 -0
- package/jsr.json +8 -8
- package/package.json +63 -32
- package/src/__tests__/ai-temp-auth.test.ts +309 -0
- package/src/__tests__/api-proxy-guard.test.ts +1451 -0
- package/src/__tests__/app-url.test.ts +270 -0
- package/src/__tests__/avatar-url.test.ts +97 -0
- package/src/__tests__/color-helper.test.ts +179 -0
- package/src/__tests__/constants.test.ts +351 -0
- package/src/__tests__/crypto.test.ts +107 -0
- package/src/__tests__/date-helper.test.ts +408 -0
- package/src/__tests__/fixtures/task-description-full-featured.json +456 -0
- package/src/__tests__/format.test.ts +317 -0
- package/src/__tests__/html-sanitizer.test.ts +360 -0
- package/src/__tests__/interest-calculator.test.ts +336 -0
- package/src/__tests__/interest-detector.test.ts +222 -0
- package/src/__tests__/label-colors.test.ts +241 -0
- package/src/__tests__/name-helper.test.ts +158 -0
- package/src/__tests__/node-diff.test.ts +576 -0
- package/src/__tests__/notification-service.test.ts +210 -0
- package/src/__tests__/onboarding-helper.test.ts +331 -0
- package/src/__tests__/path-helper.test.ts +152 -0
- package/src/__tests__/permissions.test.tsx +81 -0
- package/src/__tests__/request-emoji-limit.test.ts +172 -0
- package/src/__tests__/search-helper.test.ts +51 -0
- package/src/__tests__/storage-display-name.test.ts +37 -0
- package/src/__tests__/storage-path.test.ts +238 -0
- package/src/__tests__/tag-utils.test.ts +205 -0
- package/src/__tests__/task-description-yjs-state.test.ts +581 -0
- package/src/__tests__/task-helper-board-api-routing.test.ts +94 -0
- package/src/__tests__/task-helper-create-task.test.ts +129 -0
- package/src/__tests__/task-helpers.test.ts +464 -0
- package/src/__tests__/task-overrides.test.ts +305 -0
- package/src/__tests__/task-reorder-cache.test.ts +74 -0
- package/src/__tests__/task-sort-keys.test.ts +36 -0
- package/src/__tests__/task-transformers.test.ts +62 -0
- package/src/__tests__/text-helper.test.ts +776 -0
- package/src/__tests__/time-helper.test.ts +70 -0
- package/src/__tests__/time-tracker-period.test.ts +55 -0
- package/src/__tests__/timezone.test.ts +117 -0
- package/src/__tests__/upstash-rest.test.ts +77 -0
- package/src/__tests__/uuid-helper.test.ts +133 -0
- package/src/__tests__/workspace-helper.test.ts +859 -0
- package/src/__tests__/workspace-limits.test.ts +255 -0
- package/src/__tests__/yjs-helper.test.ts +581 -0
- package/src/abuse-protection/__tests__/backend-rate-limit.test.ts +113 -0
- package/src/abuse-protection/__tests__/edge.test.ts +136 -0
- package/src/abuse-protection/__tests__/index.test.ts +562 -0
- package/src/abuse-protection/__tests__/reputation.test.ts +192 -0
- package/src/abuse-protection/backend-rate-limit.ts +44 -0
- package/src/abuse-protection/constants.ts +117 -0
- package/src/abuse-protection/edge.ts +223 -0
- package/src/abuse-protection/index.ts +1545 -0
- package/src/abuse-protection/reputation.ts +587 -0
- package/src/abuse-protection/types.ts +97 -0
- package/src/abuse-protection/user-agent.ts +124 -0
- package/src/abuse-protection/user-suspension.ts +231 -0
- package/src/ai-temp-auth.ts +315 -0
- package/src/api-proxy-guard.ts +965 -0
- package/src/app-url.ts +96 -0
- package/src/avatar-url.ts +64 -0
- package/src/break-duration.ts +84 -0
- package/src/calendar-auth-token.test.ts +37 -0
- package/src/calendar-auth-token.ts +19 -0
- package/src/calendar-sync-coordination.md +197 -0
- package/src/calendar-utils.test.ts +169 -0
- package/src/calendar-utils.ts +91 -0
- package/src/color-helper.ts +110 -0
- package/src/common/nextjs.tsx +99 -0
- package/src/common/scan.tsx +15 -0
- package/src/configs/reports.ts +160 -0
- package/src/constants.ts +85 -0
- package/src/crypto.ts +21 -0
- package/src/currencies.ts +97 -0
- package/src/date-helper.ts +313 -0
- package/src/editor/convert-to-task.ts +264 -0
- package/src/editor/index.ts +5 -0
- package/src/email/__tests__/client.test.ts +141 -0
- package/src/email/__tests__/validation.test.ts +46 -0
- package/src/email/client.ts +92 -0
- package/src/email/server.ts +128 -0
- package/src/email/validation.ts +11 -0
- package/src/encryption/__tests__/calendar-events.test.ts +411 -0
- package/src/encryption/__tests__/configuration.test.ts +114 -0
- package/src/encryption/__tests__/field-encryption.test.ts +232 -0
- package/src/encryption/__tests__/key-generation.test.ts +30 -0
- package/src/encryption/__tests__/performance-edge-cases.test.ts +187 -0
- package/src/encryption/__tests__/test-helpers.ts +22 -0
- package/src/encryption/__tests__/workspace-key-encryption.test.ts +129 -0
- package/src/encryption/encryption-service.ts +343 -0
- package/src/encryption/index.ts +25 -0
- package/src/encryption/types.ts +57 -0
- package/src/exchange-rates.ts +49 -0
- package/src/feature-flags/__tests__/feature-flags.test.ts +302 -0
- package/src/feature-flags/core.ts +322 -0
- package/src/feature-flags/data.ts +16 -0
- package/src/feature-flags/default.ts +18 -0
- package/src/feature-flags/index.ts +7 -0
- package/src/feature-flags/requestable-features.ts +79 -0
- package/src/feature-flags/types.ts +4 -0
- package/src/fetcher.ts +2 -0
- package/src/finance/index.ts +4 -0
- package/src/finance/interest-calculator.ts +456 -0
- package/src/finance/interest-detector.ts +141 -0
- package/src/finance/transform-invoice-results.ts +219 -0
- package/src/finance/wallet-permissions.test.ts +169 -0
- package/src/finance/wallet-permissions.ts +82 -0
- package/src/format.ts +120 -1
- package/src/generated/platform-build-metadata.ts +11 -0
- package/src/hooks/use-platform.ts +64 -0
- package/src/html-sanitizer.ts +155 -0
- package/src/internal-domains.ts +497 -0
- package/src/keyboard-preset.ts +109 -0
- package/src/label-colors.ts +213 -0
- package/src/launchable-apps.test.ts +126 -0
- package/src/launchable-apps.ts +490 -0
- package/src/name-helper.ts +269 -0
- package/src/next-config.test.ts +234 -0
- package/src/next-config.ts +203 -0
- package/src/node-diff.ts +375 -0
- package/src/notification-service.ts +379 -0
- package/src/nova/scores/__tests__/calculate.test.ts +254 -0
- package/src/nova/scores/calculate.ts +132 -0
- package/src/nova/submissions/check-permission.ts +132 -0
- package/src/onboarding-helper.ts +213 -0
- package/src/path-helper.ts +93 -0
- package/src/permissions.tsx +1170 -0
- package/src/plan-helpers.test.ts +188 -0
- package/src/plan-helpers.ts +80 -0
- package/src/platform-release.test.ts +74 -0
- package/src/platform-release.ts +155 -0
- package/src/portless.ts +124 -0
- package/src/priority-styles.ts +42 -0
- package/src/request-emoji-limit.ts +335 -0
- package/src/search-helper.ts +18 -0
- package/src/search.test.ts +89 -0
- package/src/search.ts +355 -0
- package/src/storage-display-name.ts +30 -0
- package/src/storage-path.ts +147 -0
- package/src/tag-utils.ts +159 -0
- package/src/task/reorder.ts +245 -0
- package/src/task/transformers.ts +149 -0
- package/src/task-date-timezone.ts +133 -0
- package/src/task-description-content.ts +240 -0
- package/src/task-helper/board.ts +193 -0
- package/src/task-helper/bulk-actions.ts +564 -0
- package/src/task-helper/personal-external-staging.ts +21 -0
- package/src/task-helper/recycle-bin.ts +202 -0
- package/src/task-helper/relationships.ts +346 -0
- package/src/task-helper/shared.ts +109 -0
- package/src/task-helper/sort-keys.ts +337 -0
- package/src/task-helper/task-hooks-basic.ts +342 -0
- package/src/task-helper/task-hooks-move.ts +264 -0
- package/src/task-helper/task-operations.ts +278 -0
- package/src/task-helper.ts +12 -0
- package/src/task-helpers.ts +241 -0
- package/src/task-list-status.ts +62 -0
- package/src/task-overrides.ts +82 -0
- package/src/task-snapshot.ts +374 -0
- package/src/text-diff.ts +81 -0
- package/src/text-helper.ts +537 -0
- package/src/time-helper.ts +63 -0
- package/src/time-tracker-period.ts +73 -0
- package/src/timeblock-helper.ts +418 -0
- package/src/timezone.ts +190 -0
- package/src/timezones.json +1271 -0
- package/src/upstash-rest.ts +56 -0
- package/src/user-helper.ts +296 -0
- package/src/uuid-helper.ts +11 -0
- package/src/workspace-handle.ts +10 -0
- package/src/workspace-helper.ts +1408 -0
- package/src/workspace-limits.ts +68 -0
- package/src/yjs-helper.ts +217 -0
- package/src/yjs-task-description.ts +81 -0
- package/tsconfig.json +3 -5
- package/tsconfig.typecheck.json +33 -0
- package/vitest.config.ts +36 -0
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -2
- package/dist/index.mjs.map +0 -1
- package/eslint.config.mjs +0 -20
- package/rollup.config.js +0 -41
- package/src/index.ts +0 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Redis } from '@upstash/redis';
|
|
2
|
+
|
|
3
|
+
export type UpstashRestRedisClient = Pick<
|
|
4
|
+
Redis,
|
|
5
|
+
'decr' | 'del' | 'expire' | 'get' | 'incr' | 'set' | 'ttl'
|
|
6
|
+
>;
|
|
7
|
+
export type UpstashRatelimitRedisClient = Pick<
|
|
8
|
+
Redis,
|
|
9
|
+
'eval' | 'evalsha' | 'get' | 'set'
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
export function hasUpstashRestEnv(): boolean {
|
|
13
|
+
return Boolean(
|
|
14
|
+
process.env.UPSTASH_REDIS_REST_URL?.trim() &&
|
|
15
|
+
process.env.UPSTASH_REDIS_REST_TOKEN?.trim()
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function getUpstashRestRedisClient(): Promise<UpstashRestRedisClient | null> {
|
|
20
|
+
if (!hasUpstashRestEnv()) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { Redis } = await import('@upstash/redis');
|
|
25
|
+
const client = Redis.fromEnv();
|
|
26
|
+
|
|
27
|
+
const restClient: UpstashRestRedisClient = {
|
|
28
|
+
del: (...keys) => client.del(...keys),
|
|
29
|
+
decr: (key) => client.decr(key),
|
|
30
|
+
expire: (key, seconds) => client.expire(key, seconds),
|
|
31
|
+
get: <T = unknown>(key: string) => client.get<T>(key),
|
|
32
|
+
incr: (key) => client.incr(key),
|
|
33
|
+
set: (key, value, options) => client.set(key, value, options),
|
|
34
|
+
ttl: (key) => client.ttl(key),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return restClient;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function getUpstashRatelimitRedisClient(): Promise<UpstashRatelimitRedisClient | null> {
|
|
41
|
+
if (!hasUpstashRestEnv()) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { Redis } = await import('@upstash/redis');
|
|
46
|
+
const client = Redis.fromEnv();
|
|
47
|
+
|
|
48
|
+
const ratelimitClient: UpstashRatelimitRedisClient = {
|
|
49
|
+
eval: (...args) => client.eval(...args),
|
|
50
|
+
evalsha: (...args) => client.evalsha(...args),
|
|
51
|
+
get: <T = unknown>(key: string) => client.get<T>(key),
|
|
52
|
+
set: (key, value, options) => client.set(key, value, options),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return ratelimitClient;
|
|
56
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import type { TypedSupabaseClient } from '@tuturuuu/supabase/next/client';
|
|
2
|
+
import {
|
|
3
|
+
createAdminClient,
|
|
4
|
+
createClient,
|
|
5
|
+
} from '@tuturuuu/supabase/next/server';
|
|
6
|
+
import type { User, UserPrivateDetails } from '@tuturuuu/types';
|
|
7
|
+
import type { WorkspaceUser } from '@tuturuuu/types/primitives/WorkspaceUser';
|
|
8
|
+
|
|
9
|
+
import { resolveWorkspaceId } from './constants';
|
|
10
|
+
import { verifyWorkspaceMembershipType } from './workspace-helper';
|
|
11
|
+
|
|
12
|
+
async function resolveCurrentUserId(
|
|
13
|
+
supabase: TypedSupabaseClient
|
|
14
|
+
): Promise<string | null> {
|
|
15
|
+
try {
|
|
16
|
+
const { data: claimsData, error: claimsError } =
|
|
17
|
+
await supabase.auth.getClaims();
|
|
18
|
+
|
|
19
|
+
if (!claimsError && claimsData?.claims?.sub) {
|
|
20
|
+
return claimsData.claims.sub;
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
console.warn(
|
|
24
|
+
'[resolveCurrentUserId] getClaims is unavailable, falling back to getUser. This may be expected in testing environments or older Supabase clients.'
|
|
25
|
+
);
|
|
26
|
+
// Fall back to getUser when getClaims is unavailable in mocks/older clients.
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
data: { user },
|
|
31
|
+
} = await supabase.auth.getUser();
|
|
32
|
+
|
|
33
|
+
return user?.id ?? null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function getCurrentSupabaseUser() {
|
|
37
|
+
const supabase = await createClient();
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
data: { user },
|
|
41
|
+
} = await supabase.auth.getUser();
|
|
42
|
+
|
|
43
|
+
return user;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface WorkspaceUserLink {
|
|
47
|
+
platform_user_id: string;
|
|
48
|
+
virtual_user_id: string;
|
|
49
|
+
ws_id: string;
|
|
50
|
+
created_at: string;
|
|
51
|
+
workspace_users?: WorkspaceUser;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface GetCurrentWorkspaceUserOptions {
|
|
55
|
+
/**
|
|
56
|
+
* If true (default), automatically creates a missing workspace_user_linked_users entry
|
|
57
|
+
* when a workspace member doesn't have one. This uses the ensure_workspace_user_link RPC.
|
|
58
|
+
*/
|
|
59
|
+
autoRepair?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Gets the current user's workspace user link for the specified workspace.
|
|
64
|
+
* Optionally auto-repairs missing links (enabled by default).
|
|
65
|
+
*
|
|
66
|
+
* @param wsId - The workspace ID (can be a UUID or special identifier like 'personal')
|
|
67
|
+
* @param options - Configuration options
|
|
68
|
+
* @returns The workspace user link with optional nested workspace_users data, or null if not found
|
|
69
|
+
*/
|
|
70
|
+
export async function getCurrentWorkspaceUser(
|
|
71
|
+
wsId: string,
|
|
72
|
+
options: GetCurrentWorkspaceUserOptions = {}
|
|
73
|
+
): Promise<WorkspaceUserLink | null> {
|
|
74
|
+
const { autoRepair = true } = options;
|
|
75
|
+
|
|
76
|
+
const supabase = await createClient();
|
|
77
|
+
const userId = await resolveCurrentUserId(supabase as TypedSupabaseClient);
|
|
78
|
+
if (!userId) return null;
|
|
79
|
+
|
|
80
|
+
const resolvedWsId = resolveWorkspaceId(wsId);
|
|
81
|
+
|
|
82
|
+
// First attempt to get the workspace user link
|
|
83
|
+
const { data: workspaceUser } = await supabase
|
|
84
|
+
.from('workspace_user_linked_users')
|
|
85
|
+
.select(
|
|
86
|
+
'platform_user_id, virtual_user_id, ws_id, created_at, workspace_users!virtual_user_id(*)'
|
|
87
|
+
)
|
|
88
|
+
.eq('platform_user_id', userId)
|
|
89
|
+
.eq('ws_id', resolvedWsId)
|
|
90
|
+
.limit(1)
|
|
91
|
+
.maybeSingle();
|
|
92
|
+
|
|
93
|
+
// If found, return it
|
|
94
|
+
if (workspaceUser) {
|
|
95
|
+
const linkedData = workspaceUser.workspace_users;
|
|
96
|
+
return {
|
|
97
|
+
platform_user_id: workspaceUser.platform_user_id,
|
|
98
|
+
virtual_user_id: workspaceUser.virtual_user_id,
|
|
99
|
+
ws_id: workspaceUser.ws_id,
|
|
100
|
+
created_at: workspaceUser.created_at,
|
|
101
|
+
...(linkedData ? { workspace_users: linkedData as WorkspaceUser } : {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// If not found and auto-repair is disabled, return null
|
|
106
|
+
if (!autoRepair) return null;
|
|
107
|
+
|
|
108
|
+
const membership = await verifyWorkspaceMembershipType({
|
|
109
|
+
wsId: resolvedWsId,
|
|
110
|
+
userId,
|
|
111
|
+
supabase,
|
|
112
|
+
requiredType: 'MEMBER',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!membership.ok) return null;
|
|
116
|
+
|
|
117
|
+
// Try to repair the missing link using the RPC function
|
|
118
|
+
try {
|
|
119
|
+
const sbAdmin = await createAdminClient();
|
|
120
|
+
// Note: ensure_workspace_user_link is defined in migration 20260112060000
|
|
121
|
+
// Using type assertion since RPC types are generated after migration is applied
|
|
122
|
+
// IMPORTANT: Must use .bind() to preserve the Supabase client's `this` context
|
|
123
|
+
const rpc = sbAdmin.rpc.bind(sbAdmin) as unknown as (
|
|
124
|
+
fn: string,
|
|
125
|
+
args: Record<string, unknown>
|
|
126
|
+
) => Promise<{ error: Error | null }>;
|
|
127
|
+
const { error: repairError } = await rpc('ensure_workspace_user_link', {
|
|
128
|
+
target_user_id: userId,
|
|
129
|
+
target_ws_id: resolvedWsId,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (repairError) {
|
|
133
|
+
console.error(
|
|
134
|
+
'[getCurrentWorkspaceUser] Failed to auto-repair workspace user link:',
|
|
135
|
+
repairError
|
|
136
|
+
);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Fetch the newly created link
|
|
141
|
+
const { data: repairedUser } = await supabase
|
|
142
|
+
.from('workspace_user_linked_users')
|
|
143
|
+
.select(
|
|
144
|
+
'platform_user_id, virtual_user_id, ws_id, created_at, workspace_users!virtual_user_id(*)'
|
|
145
|
+
)
|
|
146
|
+
.eq('platform_user_id', userId)
|
|
147
|
+
.eq('ws_id', resolvedWsId)
|
|
148
|
+
.limit(1)
|
|
149
|
+
.maybeSingle();
|
|
150
|
+
|
|
151
|
+
if (repairedUser) {
|
|
152
|
+
const linkedData = repairedUser.workspace_users;
|
|
153
|
+
return {
|
|
154
|
+
platform_user_id: repairedUser.platform_user_id,
|
|
155
|
+
virtual_user_id: repairedUser.virtual_user_id,
|
|
156
|
+
ws_id: repairedUser.ws_id,
|
|
157
|
+
created_at: repairedUser.created_at,
|
|
158
|
+
...(linkedData ? { workspace_users: linkedData as WorkspaceUser } : {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error('[getCurrentWorkspaceUser] Error during auto-repair:', err);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function getCurrentUser() {
|
|
169
|
+
const supabase = await createClient();
|
|
170
|
+
|
|
171
|
+
const userId = await resolveCurrentUserId(supabase as TypedSupabaseClient);
|
|
172
|
+
|
|
173
|
+
if (!userId) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const { data, error } = await supabase
|
|
178
|
+
.from('users')
|
|
179
|
+
.select(
|
|
180
|
+
'id, display_name, avatar_url, bio, handle, created_at, user_private_details(email, new_email, birthday, full_name, default_workspace_id)'
|
|
181
|
+
)
|
|
182
|
+
.eq('id', userId)
|
|
183
|
+
.single();
|
|
184
|
+
|
|
185
|
+
if (error) {
|
|
186
|
+
console.error('Error getting user:', error);
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const { user_private_details, ...rest } = data;
|
|
191
|
+
return { ...rest, ...user_private_details } as
|
|
192
|
+
| (User & UserPrivateDetails)
|
|
193
|
+
| WorkspaceUser;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function getUserDefaultWorkspace(client?: TypedSupabaseClient) {
|
|
197
|
+
try {
|
|
198
|
+
const supabase = client || (await createClient());
|
|
199
|
+
|
|
200
|
+
const userId = await resolveCurrentUserId(supabase as TypedSupabaseClient);
|
|
201
|
+
|
|
202
|
+
if (!userId) return null;
|
|
203
|
+
|
|
204
|
+
const { data: userData, error: userError } = await supabase
|
|
205
|
+
.from('user_private_details')
|
|
206
|
+
.select('default_workspace_id')
|
|
207
|
+
.eq('user_id', userId)
|
|
208
|
+
.single();
|
|
209
|
+
|
|
210
|
+
if (userError || !userData) return null;
|
|
211
|
+
|
|
212
|
+
const defaultWorkspaceId = userData.default_workspace_id;
|
|
213
|
+
|
|
214
|
+
// If user has a default workspace set, validate it exists and user has access
|
|
215
|
+
if (defaultWorkspaceId) {
|
|
216
|
+
const { data: workspace, error } = await supabase
|
|
217
|
+
.from('workspaces')
|
|
218
|
+
.select('id, name, personal, workspace_members!inner(user_id)')
|
|
219
|
+
.eq('id', defaultWorkspaceId)
|
|
220
|
+
.eq('workspace_members.user_id', userId)
|
|
221
|
+
.single();
|
|
222
|
+
|
|
223
|
+
if (!error && workspace) {
|
|
224
|
+
return workspace;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// If no default workspace or invalid, get the personal workspace
|
|
229
|
+
const { data: personalWorkspace, error } = await supabase
|
|
230
|
+
.from('workspaces')
|
|
231
|
+
.select('id, name, personal, workspace_members!inner(user_id)')
|
|
232
|
+
.eq('workspace_members.user_id', userId)
|
|
233
|
+
.eq('personal', true)
|
|
234
|
+
.limit(1)
|
|
235
|
+
.maybeSingle();
|
|
236
|
+
|
|
237
|
+
if (error || !personalWorkspace) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return personalWorkspace;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error('Error getting user default workspace:', error);
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function updateUserDefaultWorkspace(
|
|
249
|
+
workspaceId: string,
|
|
250
|
+
client?: TypedSupabaseClient
|
|
251
|
+
) {
|
|
252
|
+
const supabase = client || (await createClient());
|
|
253
|
+
|
|
254
|
+
const userId = await resolveCurrentUserId(supabase as TypedSupabaseClient);
|
|
255
|
+
|
|
256
|
+
if (!userId) return { error: 'User not found' };
|
|
257
|
+
|
|
258
|
+
// Verify user has access to the workspace
|
|
259
|
+
const { data: workspace, error: workspaceError } = await supabase
|
|
260
|
+
.from('workspaces')
|
|
261
|
+
.select('id, workspace_members!inner(user_id)')
|
|
262
|
+
.eq('id', workspaceId)
|
|
263
|
+
.eq('workspace_members.user_id', userId)
|
|
264
|
+
.single();
|
|
265
|
+
|
|
266
|
+
if (workspaceError || !workspace) {
|
|
267
|
+
return { error: 'Workspace not found or access denied' };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Update the user's default workspace
|
|
271
|
+
const { error } = await supabase
|
|
272
|
+
.from('user_private_details')
|
|
273
|
+
.update({ default_workspace_id: workspaceId })
|
|
274
|
+
.eq('user_id', userId);
|
|
275
|
+
|
|
276
|
+
if (error) {
|
|
277
|
+
return { error: error.message };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return { success: true };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Function to fetch workspace users
|
|
284
|
+
export async function fetchWorkspaceUsers(
|
|
285
|
+
wsId: string
|
|
286
|
+
): Promise<WorkspaceUser[]> {
|
|
287
|
+
const supabase = await createClient();
|
|
288
|
+
const { data, error } = await supabase
|
|
289
|
+
.from('workspace_users')
|
|
290
|
+
.select('id, full_name, email, avatar_url')
|
|
291
|
+
.eq('ws_id', wsId)
|
|
292
|
+
.order('full_name', { ascending: true });
|
|
293
|
+
|
|
294
|
+
if (error) throw error;
|
|
295
|
+
return data || [];
|
|
296
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { v4 as UUIDv4, v5 as UUIDv5 } from 'uuid';
|
|
2
|
+
|
|
3
|
+
export function generateUUID(...uuids: string[]): string {
|
|
4
|
+
const name = uuids.join('-');
|
|
5
|
+
const namespace = '5b5b7b9f-6432-4c40-b97b-9bd0abb080cf';
|
|
6
|
+
return UUIDv5(name, namespace);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function generateRandomUUID(): string {
|
|
10
|
+
return UUIDv4();
|
|
11
|
+
}
|