@tuturuuu/utils 0.20.0 → 0.21.0
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 +12 -0
- package/package.json +3 -3
- package/src/__tests__/app-url.test.ts +1 -0
- package/src/__tests__/permissions.test.tsx +3 -0
- package/src/__tests__/workspace-helper.test.ts +137 -8
- package/src/internal-domains.ts +12 -0
- package/src/launchable-apps.test.ts +6 -0
- package/src/launchable-apps.ts +12 -0
- package/src/permissions.tsx +11 -0
- package/src/platform-release.test.ts +1 -1
- package/src/platform-release.ts +1 -1
- package/src/portless.ts +1 -0
- package/src/workspace-helper.ts +59 -42
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.21.0](https://github.com/tutur3u/platform/compare/utils-v0.20.0...utils-v0.21.0) (2026-07-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **git:** add fast repository satellite ([51982ae](https://github.com/tutur3u/platform/commit/51982ae8618bb7463e30c97f6e731551ec673660))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **workspaces:** preserve creator administration access ([cea32ea](https://github.com/tutur3u/platform/commit/cea32ea22acaae6be6e39552be4af47e6f4dcdc7))
|
|
14
|
+
|
|
3
15
|
## [0.20.0](https://github.com/tutur3u/platform/compare/utils-v0.19.0...utils-v0.20.0) (2026-07-27)
|
|
4
16
|
|
|
5
17
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuturuuu/utils",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.21.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/tutur3u/platform",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@tiptap/react": "3.28.0",
|
|
25
25
|
"@tuturuuu/google": "0.1.0",
|
|
26
26
|
"@tuturuuu/icons": "0.1.0",
|
|
27
|
-
"@tuturuuu/internal-api": "0.
|
|
27
|
+
"@tuturuuu/internal-api": "0.25.0",
|
|
28
28
|
"@tuturuuu/supabase": "0.5.0",
|
|
29
29
|
"@upstash/ratelimit": "^2.0.8",
|
|
30
30
|
"@upstash/redis": "^1.38.0",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"zod": "^4.4.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@tuturuuu/types": "0.
|
|
48
|
+
"@tuturuuu/types": "0.24.0",
|
|
49
49
|
"@tuturuuu/typescript-config": "0.1.1",
|
|
50
50
|
"@types/diff": "^8.0.0",
|
|
51
51
|
"@types/node": "^26.1.1",
|
|
@@ -298,6 +298,7 @@ describe('Portless app origin registry', () => {
|
|
|
298
298
|
'https://external.tuturuuu.localhost',
|
|
299
299
|
'https://finance.tuturuuu.localhost',
|
|
300
300
|
'https://forms.tuturuuu.localhost',
|
|
301
|
+
'https://git.tuturuuu.localhost',
|
|
301
302
|
'https://hive.tuturuuu.localhost',
|
|
302
303
|
'https://realtime.hive.tuturuuu.localhost',
|
|
303
304
|
'https://infra.tuturuuu.localhost',
|
|
@@ -32,6 +32,7 @@ describe('workspace permission catalog', () => {
|
|
|
32
32
|
expect(permissionIds).not.toContain('view_infrastructure');
|
|
33
33
|
expect(permissionIds).not.toContain('manage_infrastructure_stress_tests');
|
|
34
34
|
expect(permissionIds).not.toContain('manage_internal_accounts');
|
|
35
|
+
expect(permissionIds).not.toContain('manage_git_repositories');
|
|
35
36
|
expect(permissionIds).not.toContain('manage_workspace_secrets');
|
|
36
37
|
});
|
|
37
38
|
|
|
@@ -42,6 +43,7 @@ describe('workspace permission catalog', () => {
|
|
|
42
43
|
}).map((permission) => permission.id);
|
|
43
44
|
|
|
44
45
|
expect(rootPermissionIds).toContain('manage_internal_accounts');
|
|
46
|
+
expect(rootPermissionIds).toContain('manage_git_repositories');
|
|
45
47
|
});
|
|
46
48
|
|
|
47
49
|
it('keeps internal account permission translations in shared role editors', () => {
|
|
@@ -75,6 +77,7 @@ describe('workspace permission catalog', () => {
|
|
|
75
77
|
expect(permissionIds).toContain('view_infrastructure');
|
|
76
78
|
expect(permissionIds).toContain('manage_infrastructure_stress_tests');
|
|
77
79
|
expect(permissionIds).toContain('manage_internal_accounts');
|
|
80
|
+
expect(permissionIds).toContain('manage_git_repositories');
|
|
78
81
|
expect(permissionIds).toContain('manage_external_migrations');
|
|
79
82
|
expect(permissionIds).toContain('manage_workspace_secrets');
|
|
80
83
|
expect(new Set(permissionIds).size).toBe(permissionIds.length);
|
|
@@ -560,24 +560,153 @@ describe('getPermissions membership type gate', () => {
|
|
|
560
560
|
expect(adminAuthGetUser).not.toHaveBeenCalled();
|
|
561
561
|
expect(personalWorkspaceQuery.eq).toHaveBeenCalledWith('personal', true);
|
|
562
562
|
expect(personalWorkspaceQuery.eq).toHaveBeenCalledWith(
|
|
563
|
-
'
|
|
563
|
+
'creator_id',
|
|
564
564
|
'user-1'
|
|
565
565
|
);
|
|
566
566
|
expect(membershipQuery.eq).toHaveBeenCalledWith(
|
|
567
567
|
'ws_id',
|
|
568
568
|
personalWorkspaceId
|
|
569
569
|
);
|
|
570
|
-
expect(roleMembersQuery.eq).toHaveBeenCalledWith(
|
|
571
|
-
'workspace_roles.ws_id',
|
|
572
|
-
personalWorkspaceId
|
|
573
|
-
);
|
|
574
570
|
expect(workspaceQuery.eq).toHaveBeenCalledWith('id', personalWorkspaceId);
|
|
575
|
-
expect(
|
|
576
|
-
|
|
577
|
-
|
|
571
|
+
expect(roleMembersQuery.select).not.toHaveBeenCalled();
|
|
572
|
+
expect(defaultPermissionsQuery.select).not.toHaveBeenCalled();
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('grants full member permissions to a creator with a missing membership row', async () => {
|
|
576
|
+
const workspaceId = '11111111-1111-4111-8111-111111111111';
|
|
577
|
+
const membershipQuery = {
|
|
578
|
+
select: vi.fn(),
|
|
579
|
+
eq: vi.fn(),
|
|
580
|
+
maybeSingle: vi.fn(),
|
|
581
|
+
};
|
|
582
|
+
membershipQuery.select.mockReturnValue(membershipQuery);
|
|
583
|
+
membershipQuery.eq.mockReturnValue(membershipQuery);
|
|
584
|
+
membershipQuery.maybeSingle.mockResolvedValue({
|
|
585
|
+
data: null,
|
|
586
|
+
error: null,
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const workspaceQuery = {
|
|
590
|
+
select: vi.fn(),
|
|
591
|
+
eq: vi.fn(),
|
|
592
|
+
single: vi.fn(),
|
|
593
|
+
};
|
|
594
|
+
workspaceQuery.select.mockReturnValue(workspaceQuery);
|
|
595
|
+
workspaceQuery.eq.mockReturnValue(workspaceQuery);
|
|
596
|
+
workspaceQuery.single.mockResolvedValue({
|
|
597
|
+
data: { creator_id: 'user-1' },
|
|
598
|
+
error: null,
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
mockCreateAdminClient.mockResolvedValue({
|
|
602
|
+
from: vi.fn((table: string) => {
|
|
603
|
+
if (table === 'workspace_members') return membershipQuery;
|
|
604
|
+
if (table === 'workspaces') return workspaceQuery;
|
|
605
|
+
throw new Error(`Unexpected admin table lookup: ${table}`);
|
|
606
|
+
}),
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const permissions = await getPermissions({
|
|
610
|
+
user: { id: 'user-1', email: 'creator@example.com' },
|
|
611
|
+
wsId: workspaceId,
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
expect(permissions?.membershipType).toBe('MEMBER');
|
|
615
|
+
expect(permissions?.containsPermission('manage_workspace_settings')).toBe(
|
|
616
|
+
true
|
|
617
|
+
);
|
|
618
|
+
expect(permissions?.containsPermission('manage_workspace_members')).toBe(
|
|
619
|
+
true
|
|
620
|
+
);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it('normalizes a creator GUEST membership to effective MEMBER permissions', async () => {
|
|
624
|
+
const workspaceId = '11111111-1111-4111-8111-111111111111';
|
|
625
|
+
const membershipQuery = {
|
|
626
|
+
select: vi.fn(),
|
|
627
|
+
eq: vi.fn(),
|
|
628
|
+
maybeSingle: vi.fn(),
|
|
629
|
+
};
|
|
630
|
+
membershipQuery.select.mockReturnValue(membershipQuery);
|
|
631
|
+
membershipQuery.eq.mockReturnValue(membershipQuery);
|
|
632
|
+
membershipQuery.maybeSingle.mockResolvedValue({
|
|
633
|
+
data: { type: 'GUEST' },
|
|
634
|
+
error: null,
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
const workspaceQuery = {
|
|
638
|
+
select: vi.fn(),
|
|
639
|
+
eq: vi.fn(),
|
|
640
|
+
single: vi.fn(),
|
|
641
|
+
};
|
|
642
|
+
workspaceQuery.select.mockReturnValue(workspaceQuery);
|
|
643
|
+
workspaceQuery.eq.mockReturnValue(workspaceQuery);
|
|
644
|
+
workspaceQuery.single.mockResolvedValue({
|
|
645
|
+
data: { creator_id: 'user-1' },
|
|
646
|
+
error: null,
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
mockCreateAdminClient.mockResolvedValue({
|
|
650
|
+
from: vi.fn((table: string) => {
|
|
651
|
+
if (table === 'workspace_members') return membershipQuery;
|
|
652
|
+
if (table === 'workspaces') return workspaceQuery;
|
|
653
|
+
throw new Error(`Unexpected admin table lookup: ${table}`);
|
|
654
|
+
}),
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
const permissions = await getPermissions({
|
|
658
|
+
user: { id: 'user-1', email: 'creator@example.com' },
|
|
659
|
+
wsId: workspaceId,
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
expect(permissions?.membershipType).toBe('MEMBER');
|
|
663
|
+
expect(permissions?.containsPermission('manage_workspace_roles')).toBe(
|
|
664
|
+
true
|
|
578
665
|
);
|
|
579
666
|
});
|
|
580
667
|
|
|
668
|
+
it('denies an unrelated user when the membership row is missing', async () => {
|
|
669
|
+
const workspaceId = '11111111-1111-4111-8111-111111111111';
|
|
670
|
+
const membershipQuery = {
|
|
671
|
+
select: vi.fn(),
|
|
672
|
+
eq: vi.fn(),
|
|
673
|
+
maybeSingle: vi.fn(),
|
|
674
|
+
};
|
|
675
|
+
membershipQuery.select.mockReturnValue(membershipQuery);
|
|
676
|
+
membershipQuery.eq.mockReturnValue(membershipQuery);
|
|
677
|
+
membershipQuery.maybeSingle.mockResolvedValue({
|
|
678
|
+
data: null,
|
|
679
|
+
error: null,
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const workspaceQuery = {
|
|
683
|
+
select: vi.fn(),
|
|
684
|
+
eq: vi.fn(),
|
|
685
|
+
single: vi.fn(),
|
|
686
|
+
};
|
|
687
|
+
workspaceQuery.select.mockReturnValue(workspaceQuery);
|
|
688
|
+
workspaceQuery.eq.mockReturnValue(workspaceQuery);
|
|
689
|
+
workspaceQuery.single.mockResolvedValue({
|
|
690
|
+
data: { creator_id: 'owner-1' },
|
|
691
|
+
error: null,
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
mockCreateAdminClient.mockResolvedValue({
|
|
695
|
+
from: vi.fn((table: string) => {
|
|
696
|
+
if (table === 'workspace_members') return membershipQuery;
|
|
697
|
+
if (table === 'workspaces') return workspaceQuery;
|
|
698
|
+
throw new Error(`Unexpected admin table lookup: ${table}`);
|
|
699
|
+
}),
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
const permissions = await getPermissions({
|
|
703
|
+
user: { id: 'user-1', email: 'unrelated@example.com' },
|
|
704
|
+
wsId: workspaceId,
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
expect(permissions).toBeNull();
|
|
708
|
+
});
|
|
709
|
+
|
|
581
710
|
it('returns a read-only permission context for a guest without defaults', async () => {
|
|
582
711
|
const membershipQuery = {
|
|
583
712
|
select: vi.fn(),
|
package/src/internal-domains.ts
CHANGED
|
@@ -116,6 +116,10 @@ export const PRODUCTION_INTERNAL_APP_DOMAINS = [
|
|
|
116
116
|
name: 'forms',
|
|
117
117
|
url: 'https://forms.tuturuuu.com',
|
|
118
118
|
},
|
|
119
|
+
{
|
|
120
|
+
name: 'git',
|
|
121
|
+
url: 'https://git.tuturuuu.com',
|
|
122
|
+
},
|
|
119
123
|
{
|
|
120
124
|
name: 'hive',
|
|
121
125
|
url: 'https://hive.tuturuuu.com',
|
|
@@ -231,6 +235,10 @@ export const PORTLESS_INTERNAL_APP_DOMAINS = [
|
|
|
231
235
|
name: 'forms',
|
|
232
236
|
url: getTuturuuuPortlessAppOrigin('forms'),
|
|
233
237
|
},
|
|
238
|
+
{
|
|
239
|
+
name: 'git',
|
|
240
|
+
url: getTuturuuuPortlessAppOrigin('git'),
|
|
241
|
+
},
|
|
234
242
|
{
|
|
235
243
|
name: 'hive',
|
|
236
244
|
url: getTuturuuuPortlessAppOrigin('hive'),
|
|
@@ -342,6 +350,10 @@ export const LOCALHOST_INTERNAL_APP_DOMAINS = [
|
|
|
342
350
|
name: 'forms',
|
|
343
351
|
url: 'http://localhost:7828',
|
|
344
352
|
},
|
|
353
|
+
{
|
|
354
|
+
name: 'git',
|
|
355
|
+
url: 'http://localhost:7830',
|
|
356
|
+
},
|
|
345
357
|
{
|
|
346
358
|
name: 'hive',
|
|
347
359
|
url: 'http://localhost:7814',
|
|
@@ -32,6 +32,7 @@ describe('launchable apps', () => {
|
|
|
32
32
|
});
|
|
33
33
|
expect(getLaunchableAppByTitle('Tuturuuu')?.slug).toBe('platform');
|
|
34
34
|
expect(getLaunchableAppByTitle('questionnaires')?.slug).toBe('forms');
|
|
35
|
+
expect(getLaunchableAppByTitle('repositories')?.slug).toBe('git');
|
|
35
36
|
expect(getLaunchableAppByTitle('apps gateway')?.slug).toBe('apps');
|
|
36
37
|
});
|
|
37
38
|
|
|
@@ -45,6 +46,11 @@ describe('launchable apps', () => {
|
|
|
45
46
|
expect(getLaunchableApp('external')).toBeNull();
|
|
46
47
|
expect(getLaunchableApp('playground')).toBeNull();
|
|
47
48
|
expect(getLaunchableApp('platform')?.category).toBe('productivity');
|
|
49
|
+
expect(getLaunchableApp('git')).toMatchObject({
|
|
50
|
+
defaultPath: '/tutur3u/platform',
|
|
51
|
+
localhostOrigin: 'http://localhost:7830',
|
|
52
|
+
productionUrl: 'https://git.tuturuuu.com',
|
|
53
|
+
});
|
|
48
54
|
expect(getLaunchableApp('apps')?.category).toBe('miscellaneous');
|
|
49
55
|
expect(getLaunchableApp('cms')?.category).toBe('operations');
|
|
50
56
|
expect(getLaunchableApp('rewise')?.category).toBe('ai');
|
package/src/launchable-apps.ts
CHANGED
|
@@ -113,6 +113,18 @@ export const LAUNCHABLE_APPS = [
|
|
|
113
113
|
title: 'Drive',
|
|
114
114
|
workspacePathResolver: workspaceRootPath,
|
|
115
115
|
},
|
|
116
|
+
{
|
|
117
|
+
aliases: ['Source', 'Repositories', 'GitHub', 'Code'],
|
|
118
|
+
appRoot: 'apps/git',
|
|
119
|
+
category: 'productivity',
|
|
120
|
+
defaultPath: '/tutur3u/platform',
|
|
121
|
+
localhostOrigin: 'http://localhost:7830',
|
|
122
|
+
packageName: '@tuturuuu/git',
|
|
123
|
+
portlessApp: 'git',
|
|
124
|
+
productionUrl: 'https://git.tuturuuu.com',
|
|
125
|
+
slug: 'git',
|
|
126
|
+
title: 'Git',
|
|
127
|
+
},
|
|
116
128
|
{
|
|
117
129
|
aliases: ['App Launcher', 'Gateway', 'Apps Gateway'],
|
|
118
130
|
appRoot: 'apps/apps',
|
package/src/permissions.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
FileKey2,
|
|
20
20
|
FileText,
|
|
21
21
|
FlaskConical,
|
|
22
|
+
GitBranch,
|
|
22
23
|
HandCoins,
|
|
23
24
|
HardDrive,
|
|
24
25
|
House,
|
|
@@ -131,6 +132,16 @@ export const permissionGroups = ({
|
|
|
131
132
|
disableOnProduction: false,
|
|
132
133
|
disabled: false,
|
|
133
134
|
},
|
|
135
|
+
{
|
|
136
|
+
id: 'manage_git_repositories' as PermissionId,
|
|
137
|
+
icon: <GitBranch />,
|
|
138
|
+
title: t('ws-roles.manage_git_repositories'),
|
|
139
|
+
description: t(
|
|
140
|
+
'ws-roles.manage_git_repositories_description'
|
|
141
|
+
),
|
|
142
|
+
disableOnProduction: false,
|
|
143
|
+
disabled: false,
|
|
144
|
+
},
|
|
134
145
|
{
|
|
135
146
|
id: 'manage_mobile_deployment_vault' as PermissionId,
|
|
136
147
|
icon: <FileKey2 />,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
describe('platform release metadata', () => {
|
|
9
9
|
it('uses the centralized shared browser app version', () => {
|
|
10
|
-
expect(TUTURUUU_PLATFORM_VERSION).toBe('0.
|
|
10
|
+
expect(TUTURUUU_PLATFORM_VERSION).toBe('0.30.0'); // x-release-please-version
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
it('preserves the source timestamp in builtAt and derives a short hash', () => {
|
package/src/platform-release.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PLATFORM_BUILD_METADATA } from './generated/platform-build-metadata';
|
|
2
2
|
|
|
3
|
-
export const TUTURUUU_PLATFORM_VERSION = '0.
|
|
3
|
+
export const TUTURUUU_PLATFORM_VERSION = '0.30.0'; // x-release-please-version
|
|
4
4
|
|
|
5
5
|
export type PlatformBuildMetadataInput = {
|
|
6
6
|
/** ISO timestamp of the source commit used for the build, not deployment time. */
|
package/src/portless.ts
CHANGED
|
@@ -17,6 +17,7 @@ export const TUTURUUU_PORTLESS_APP_ORIGINS = {
|
|
|
17
17
|
external: `https://external.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
18
18
|
finance: `https://finance.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
19
19
|
forms: `https://forms.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
20
|
+
git: `https://git.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
20
21
|
hive: `https://hive.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
21
22
|
'hive-realtime': `https://realtime.hive.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
22
23
|
infra: `https://infra.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
package/src/workspace-helper.ts
CHANGED
|
@@ -689,19 +689,32 @@ async function resolveWorkspaceIdForPermissions({
|
|
|
689
689
|
return normalizeWorkspaceId(wsId, authorizationClient);
|
|
690
690
|
}
|
|
691
691
|
|
|
692
|
-
const { data:
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
692
|
+
const { data: creatorWorkspace, error: creatorWorkspaceError } =
|
|
693
|
+
await authorizationClient
|
|
694
|
+
.from('workspaces')
|
|
695
|
+
.select('id')
|
|
696
|
+
.eq('personal', true)
|
|
697
|
+
.eq('creator_id', principal.id)
|
|
698
|
+
.maybeSingle();
|
|
699
|
+
|
|
700
|
+
if (!creatorWorkspaceError && creatorWorkspace?.id) {
|
|
701
|
+
return creatorWorkspace.id;
|
|
702
|
+
}
|
|
699
703
|
|
|
700
|
-
|
|
704
|
+
const { data: memberWorkspace, error: memberWorkspaceError } =
|
|
705
|
+
await authorizationClient
|
|
706
|
+
.from('workspaces')
|
|
707
|
+
.select('id, workspace_members!inner(user_id, type)')
|
|
708
|
+
.eq('personal', true)
|
|
709
|
+
.eq('workspace_members.user_id', principal.id)
|
|
710
|
+
.eq('workspace_members.type', 'MEMBER')
|
|
711
|
+
.maybeSingle();
|
|
712
|
+
|
|
713
|
+
if (memberWorkspaceError || !memberWorkspace?.id) {
|
|
701
714
|
throw new Error('Personal workspace not found');
|
|
702
715
|
}
|
|
703
716
|
|
|
704
|
-
return
|
|
717
|
+
return memberWorkspace.id;
|
|
705
718
|
}
|
|
706
719
|
|
|
707
720
|
/**
|
|
@@ -761,7 +774,7 @@ async function getPermissionsImpl({
|
|
|
761
774
|
let resolvedWorkspaceId: string;
|
|
762
775
|
try {
|
|
763
776
|
resolvedWorkspaceId = await resolveWorkspaceIdForPermissions({
|
|
764
|
-
authorizationClient:
|
|
777
|
+
authorizationClient: sbAdmin,
|
|
765
778
|
principal,
|
|
766
779
|
wsId,
|
|
767
780
|
});
|
|
@@ -769,21 +782,39 @@ async function getPermissionsImpl({
|
|
|
769
782
|
return null;
|
|
770
783
|
}
|
|
771
784
|
|
|
772
|
-
const membership = await
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
785
|
+
const [membership, workspaceRes] = await Promise.all([
|
|
786
|
+
verifyWorkspaceMembershipType({
|
|
787
|
+
wsId: resolvedWorkspaceId,
|
|
788
|
+
userId,
|
|
789
|
+
supabase: authorizationClient as TypedSupabaseClient,
|
|
790
|
+
requiredType: 'ANY',
|
|
791
|
+
}),
|
|
792
|
+
sbAdmin
|
|
793
|
+
.from('workspaces')
|
|
794
|
+
.select('creator_id')
|
|
795
|
+
.eq('id', resolvedWorkspaceId)
|
|
796
|
+
.single(),
|
|
797
|
+
]);
|
|
778
798
|
|
|
779
|
-
|
|
799
|
+
const { data: workspaceData, error: workspaceError } = workspaceRes;
|
|
800
|
+
|
|
801
|
+
if (workspaceError || !workspaceData) {
|
|
802
|
+
console.info('Workspace not found in getPermissions', resolvedWorkspaceId);
|
|
780
803
|
return null;
|
|
781
804
|
}
|
|
782
805
|
|
|
783
|
-
const
|
|
806
|
+
const isCreator = workspaceData.creator_id === userId;
|
|
807
|
+
|
|
808
|
+
if (!isCreator && !membership.ok) {
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const membershipType = isCreator
|
|
813
|
+
? 'MEMBER'
|
|
814
|
+
: (membership.membershipType ?? 'MEMBER');
|
|
784
815
|
|
|
785
816
|
const permissionsQuery =
|
|
786
|
-
membershipType === 'MEMBER'
|
|
817
|
+
membershipType === 'MEMBER' && !isCreator
|
|
787
818
|
? sbAdmin
|
|
788
819
|
.from('workspace_role_members')
|
|
789
820
|
.select(
|
|
@@ -794,40 +825,26 @@ async function getPermissionsImpl({
|
|
|
794
825
|
.eq('workspace_roles.workspace_role_permissions.enabled', true)
|
|
795
826
|
: Promise.resolve({ data: [], error: null });
|
|
796
827
|
|
|
797
|
-
const
|
|
798
|
-
.
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
828
|
+
const defaultQuery = isCreator
|
|
829
|
+
? Promise.resolve({ data: [], error: null })
|
|
830
|
+
: sbAdmin
|
|
831
|
+
.from('workspace_default_permissions')
|
|
832
|
+
.select('permission')
|
|
833
|
+
.eq('ws_id', resolvedWorkspaceId)
|
|
834
|
+
.eq('member_type', membershipType)
|
|
835
|
+
.eq('enabled', true);
|
|
802
836
|
|
|
803
|
-
const
|
|
804
|
-
.from('workspace_default_permissions')
|
|
805
|
-
.select('permission')
|
|
806
|
-
.eq('ws_id', resolvedWorkspaceId)
|
|
807
|
-
.eq('member_type', membershipType)
|
|
808
|
-
.eq('enabled', true);
|
|
809
|
-
|
|
810
|
-
const [permissionsRes, workspaceRes, defaultRes] = await Promise.all([
|
|
837
|
+
const [permissionsRes, defaultRes] = await Promise.all([
|
|
811
838
|
permissionsQuery,
|
|
812
|
-
workspaceQuery,
|
|
813
839
|
defaultQuery,
|
|
814
840
|
]);
|
|
815
841
|
|
|
816
842
|
const { data: permissionsData, error: permissionsError } = permissionsRes;
|
|
817
|
-
const { data: workspaceData, error: workspaceError } = workspaceRes;
|
|
818
843
|
const { data: defaultData, error: defaultError } = defaultRes;
|
|
819
844
|
|
|
820
|
-
if (!workspaceData) {
|
|
821
|
-
console.info('Workspace not found in getPermissions', resolvedWorkspaceId);
|
|
822
|
-
return null;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
845
|
if (permissionsError) return null;
|
|
826
|
-
if (workspaceError) return null;
|
|
827
846
|
if (defaultError) return null;
|
|
828
847
|
|
|
829
|
-
const isCreator =
|
|
830
|
-
membershipType === 'MEMBER' && workspaceData.creator_id === userId;
|
|
831
848
|
// if (DEV_MODE) {
|
|
832
849
|
// console.log('--------------------');
|
|
833
850
|
// console.log('Is creator', isCreator);
|