@tuturuuu/utils 0.33.3 → 0.35.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 +55 -0
- package/package.json +6 -4
- package/src/__tests__/api-proxy-bearer-trust.test.ts +3 -0
- package/src/__tests__/api-proxy-guard.test.ts +6 -13
- package/src/__tests__/api-proxy-offline-response.test.ts +48 -0
- package/src/__tests__/api-proxy-test-request.ts +19 -0
- package/src/__tests__/calendar-provider-readonly.test.ts +45 -0
- package/src/__tests__/calendar-recurrence-edit.test.ts +140 -0
- package/src/__tests__/calendar-recurrence.test.ts +479 -0
- package/src/__tests__/daily-operation-download-isolation.test.ts +137 -0
- package/src/__tests__/offline-download-guard.test.ts +340 -0
- package/src/__tests__/permissions.test.tsx +21 -0
- package/src/__tests__/request-workspace.test.ts +63 -0
- package/src/__tests__/tutoring-teacher-hours.test.ts +274 -0
- package/src/__tests__/username-policy.test.ts +70 -0
- package/src/account-benefits-server.ts +43 -0
- package/src/account-benefits.ts +45 -0
- package/src/api-proxy-guard.ts +8 -42
- package/src/api-proxy-read-policy.ts +43 -0
- package/src/calendar-provider-readonly.ts +49 -0
- package/src/calendar-recurrence-edit.ts +148 -0
- package/src/calendar-recurrence-expansion.ts +131 -0
- package/src/calendar-recurrence-local.ts +45 -0
- package/src/calendar-recurrence-pattern.ts +54 -0
- package/src/calendar-recurrence-providers.ts +361 -0
- package/src/calendar-recurrence-validation.ts +180 -0
- package/src/calendar-recurrence.ts +5 -0
- package/src/calendar-utils.ts +41 -3
- package/src/common/metadata.test.ts +0 -6
- package/src/constants.ts +3 -0
- package/src/deployment-vault-permissions.tsx +26 -0
- package/src/desktop-signing-profiles.ts +20 -0
- package/src/google-calendar-import-fence.test.ts +182 -0
- package/src/google-calendar-import-fence.ts +166 -0
- package/src/next-config.ts +8 -1
- package/src/offline-download-guard.ts +160 -0
- package/src/permissions.tsx +2 -10
- package/src/platform-release.test.ts +1 -1
- package/src/platform-release.ts +1 -1
- package/src/playground-schema.test.ts +119 -0
- package/src/playground-schema.ts +173 -0
- package/src/playground-templates.ts +65 -0
- package/src/release-note-policy-core.cjs +28 -0
- package/src/release-note-policy-core.d.cts +5 -0
- package/src/release-note-policy.json +9 -0
- package/src/release-note-policy.ts +4 -0
- package/src/request-workspace.ts +29 -0
- package/src/seo-policy.json +196 -0
- package/src/seo.test.ts +168 -0
- package/src/seo.ts +49 -0
- package/src/tutoring-teacher-hours.ts +220 -0
- package/src/username-policy.json +87 -0
- package/src/username-policy.ts +18 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
PlaygroundDelta,
|
|
4
|
+
PlaygroundFiles,
|
|
5
|
+
PlaygroundJob,
|
|
6
|
+
PlaygroundRunnerExport,
|
|
7
|
+
} from './playground-schema';
|
|
8
|
+
|
|
9
|
+
it.each([
|
|
10
|
+
'../secret',
|
|
11
|
+
'a/../../secret',
|
|
12
|
+
'/absolute',
|
|
13
|
+
'node_modules/pkg.js',
|
|
14
|
+
'a/.git/config',
|
|
15
|
+
'a//b',
|
|
16
|
+
'a\\b',
|
|
17
|
+
])('rejects unsafe path %s', (path) =>
|
|
18
|
+
expect(PlaygroundFiles.safeParse([{ path, content: 'x' }]).success).toBe(
|
|
19
|
+
false
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
it('enforces UTF-8 byte limits rather than only character counts', () =>
|
|
23
|
+
expect(
|
|
24
|
+
PlaygroundFiles.safeParse([{ path: 'a', content: 'ấ'.repeat(100000) }])
|
|
25
|
+
.success
|
|
26
|
+
).toBe(false));
|
|
27
|
+
it('rejects inconsistent delta inventories', () => {
|
|
28
|
+
expect(
|
|
29
|
+
PlaygroundDelta.safeParse({
|
|
30
|
+
revision: 1,
|
|
31
|
+
command: 'run',
|
|
32
|
+
paths: ['a', 'a/b'],
|
|
33
|
+
files: [],
|
|
34
|
+
}).success
|
|
35
|
+
).toBe(false);
|
|
36
|
+
expect(
|
|
37
|
+
PlaygroundDelta.safeParse({
|
|
38
|
+
revision: 1,
|
|
39
|
+
command: 'run',
|
|
40
|
+
paths: ['a'],
|
|
41
|
+
files: [{ path: 'b', content: 'x' }],
|
|
42
|
+
}).success
|
|
43
|
+
).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
it('bounds preview requests to approved ports and paths', () => {
|
|
46
|
+
const job = {
|
|
47
|
+
projectId: '00000000-0000-4000-8000-000000000001',
|
|
48
|
+
revision: 0,
|
|
49
|
+
language: 'python',
|
|
50
|
+
operation: 'preview',
|
|
51
|
+
port: 22,
|
|
52
|
+
path: '/',
|
|
53
|
+
};
|
|
54
|
+
expect(PlaygroundJob.safeParse(job).success).toBe(false);
|
|
55
|
+
expect(
|
|
56
|
+
PlaygroundJob.safeParse({ ...job, port: 3000, path: '/\r\nHost:metadata' })
|
|
57
|
+
.success
|
|
58
|
+
).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('rejects file/directory collisions with intervening sorted siblings', () => {
|
|
62
|
+
const paths = ['a', 'a-b', 'a/b'];
|
|
63
|
+
expect(
|
|
64
|
+
PlaygroundFiles.safeParse(paths.map((path) => ({ path, content: 'x' })))
|
|
65
|
+
.success
|
|
66
|
+
).toBe(false);
|
|
67
|
+
expect(
|
|
68
|
+
PlaygroundDelta.safeParse({ revision: 1, command: 'run', paths, files: [] })
|
|
69
|
+
.success
|
|
70
|
+
).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('rejects conflicting runner inventory ancestors', () => {
|
|
74
|
+
expect(
|
|
75
|
+
PlaygroundRunnerExport.safeParse({
|
|
76
|
+
revision: 1,
|
|
77
|
+
paths: ['a', 'a-b', 'a/b'],
|
|
78
|
+
files: [],
|
|
79
|
+
baseline: {},
|
|
80
|
+
}).success
|
|
81
|
+
).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it.each([
|
|
85
|
+
'/../secret',
|
|
86
|
+
'/a/./b',
|
|
87
|
+
'/a//b',
|
|
88
|
+
'//other-host',
|
|
89
|
+
'/%2e%2e/secret',
|
|
90
|
+
'/a%2f../secret',
|
|
91
|
+
'/a\\b',
|
|
92
|
+
'/%',
|
|
93
|
+
])('rejects unsafe preview path %s', (path) => {
|
|
94
|
+
expect(
|
|
95
|
+
PlaygroundJob.safeParse({
|
|
96
|
+
projectId: '00000000-0000-4000-8000-000000000001',
|
|
97
|
+
revision: 0,
|
|
98
|
+
language: 'python',
|
|
99
|
+
operation: 'preview',
|
|
100
|
+
port: 3000,
|
|
101
|
+
path,
|
|
102
|
+
}).success
|
|
103
|
+
).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
it.each(['/', '/index.html', '/assets/app.js?version=1'])(
|
|
106
|
+
'accepts ordinary preview path %s',
|
|
107
|
+
(path) => {
|
|
108
|
+
expect(
|
|
109
|
+
PlaygroundJob.safeParse({
|
|
110
|
+
projectId: '00000000-0000-4000-8000-000000000001',
|
|
111
|
+
revision: 0,
|
|
112
|
+
language: 'python',
|
|
113
|
+
operation: 'preview',
|
|
114
|
+
port: 3000,
|
|
115
|
+
path,
|
|
116
|
+
}).success
|
|
117
|
+
).toBe(true);
|
|
118
|
+
}
|
|
119
|
+
);
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { PLAYGROUND_LANGUAGES } from '@tuturuuu/types/primitives/playgrounds';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
function hasDirectoryConflict(paths: string[]) {
|
|
5
|
+
const inventory = new Set(paths);
|
|
6
|
+
return paths.some((path) => {
|
|
7
|
+
const pieces = path.split('/');
|
|
8
|
+
return pieces
|
|
9
|
+
.slice(0, -1)
|
|
10
|
+
.some((_, index) => inventory.has(pieces.slice(0, index + 1).join('/')));
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function validPreviewPath(path: string) {
|
|
15
|
+
if (!path.startsWith('/') || /[\r\n\0\\]/.test(path)) return false;
|
|
16
|
+
try {
|
|
17
|
+
const pathname = decodeURIComponent(path.split(/[?#]/, 1)[0]!);
|
|
18
|
+
return (
|
|
19
|
+
pathname === '/' ||
|
|
20
|
+
(!/[\r\n\0\\]/.test(pathname) &&
|
|
21
|
+
pathname
|
|
22
|
+
.slice(1)
|
|
23
|
+
.split('/')
|
|
24
|
+
.every((part) => part !== '' && part !== '.' && part !== '..'))
|
|
25
|
+
);
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const PLAYGROUND_BYTES = 2 * 1024 * 1024;
|
|
32
|
+
export const PlaygroundPath = z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1)
|
|
35
|
+
.max(240)
|
|
36
|
+
.refine(
|
|
37
|
+
(path) =>
|
|
38
|
+
path
|
|
39
|
+
.split('/')
|
|
40
|
+
.every(
|
|
41
|
+
(part) =>
|
|
42
|
+
/^[a-zA-Z0-9_.@+-]+$/.test(part) && part !== '.' && part !== '..'
|
|
43
|
+
) &&
|
|
44
|
+
!path
|
|
45
|
+
.split('/')
|
|
46
|
+
.some((part) =>
|
|
47
|
+
['node_modules', '.git', '__pycache__', 'target', '.venv'].includes(
|
|
48
|
+
part
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
export const PlaygroundFiles = z
|
|
53
|
+
.array(
|
|
54
|
+
z
|
|
55
|
+
.object({
|
|
56
|
+
path: PlaygroundPath,
|
|
57
|
+
content: z
|
|
58
|
+
.string()
|
|
59
|
+
.max(256 * 1024)
|
|
60
|
+
.refine(
|
|
61
|
+
(value) =>
|
|
62
|
+
!value.includes('\0') &&
|
|
63
|
+
new TextEncoder().encode(value).byteLength <= 256 * 1024
|
|
64
|
+
),
|
|
65
|
+
})
|
|
66
|
+
.strict()
|
|
67
|
+
)
|
|
68
|
+
.max(128)
|
|
69
|
+
.superRefine((files, ctx) => {
|
|
70
|
+
if (new Set(files.map((file) => file.path)).size !== files.length)
|
|
71
|
+
ctx.addIssue({ code: 'custom', message: 'Duplicate file paths' });
|
|
72
|
+
if (
|
|
73
|
+
files.reduce(
|
|
74
|
+
(size, file) => size + new TextEncoder().encode(file.content).length,
|
|
75
|
+
0
|
|
76
|
+
) > PLAYGROUND_BYTES
|
|
77
|
+
)
|
|
78
|
+
ctx.addIssue({
|
|
79
|
+
code: 'custom',
|
|
80
|
+
message: 'Project exceeds storage limit',
|
|
81
|
+
});
|
|
82
|
+
const paths = files.map((file) => file.path).sort();
|
|
83
|
+
if (hasDirectoryConflict(paths))
|
|
84
|
+
ctx.addIssue({
|
|
85
|
+
code: 'custom',
|
|
86
|
+
message: 'File conflicts with directory',
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
export const PlaygroundCreate = z
|
|
90
|
+
.object({
|
|
91
|
+
name: z.string().trim().min(1).max(80),
|
|
92
|
+
language: z.enum(PLAYGROUND_LANGUAGES),
|
|
93
|
+
})
|
|
94
|
+
.strict();
|
|
95
|
+
export const PlaygroundSave = z
|
|
96
|
+
.object({
|
|
97
|
+
revision: z.number().int().nonnegative(),
|
|
98
|
+
files: PlaygroundFiles,
|
|
99
|
+
command: z
|
|
100
|
+
.string()
|
|
101
|
+
.min(1)
|
|
102
|
+
.max(4096)
|
|
103
|
+
.refine((value) => !value.includes('\0')),
|
|
104
|
+
})
|
|
105
|
+
.strict();
|
|
106
|
+
export const PlaygroundJob = z
|
|
107
|
+
.object({
|
|
108
|
+
projectId: z.guid(),
|
|
109
|
+
revision: z.number().int().nonnegative(),
|
|
110
|
+
language: z.enum(PLAYGROUND_LANGUAGES),
|
|
111
|
+
operation: z.enum(['run', 'stop', 'preview']),
|
|
112
|
+
files: PlaygroundFiles.optional(),
|
|
113
|
+
command: z.string().max(4096).optional(),
|
|
114
|
+
stdin: z.string().max(65536).optional(),
|
|
115
|
+
port: z.number().int().min(1024).max(65535).optional(),
|
|
116
|
+
path: z.string().max(2048).optional(),
|
|
117
|
+
})
|
|
118
|
+
.strict()
|
|
119
|
+
.superRefine((job, ctx) => {
|
|
120
|
+
if (
|
|
121
|
+
job.operation === 'run' &&
|
|
122
|
+
(!job.files || !job.command || job.command.includes('\0'))
|
|
123
|
+
)
|
|
124
|
+
ctx.addIssue({ code: 'custom', message: 'Missing run payload' });
|
|
125
|
+
if (
|
|
126
|
+
job.operation === 'preview' &&
|
|
127
|
+
(!job.port || !job.path || !validPreviewPath(job.path))
|
|
128
|
+
)
|
|
129
|
+
ctx.addIssue({ code: 'custom', message: 'Invalid preview request' });
|
|
130
|
+
});
|
|
131
|
+
export type PlaygroundJobPayload = z.infer<typeof PlaygroundJob>;
|
|
132
|
+
|
|
133
|
+
/** Full path inventory plus changed text; unchanged file bytes never cross the callback. */
|
|
134
|
+
export const PlaygroundDelta = PlaygroundSave.extend({
|
|
135
|
+
paths: z.array(PlaygroundPath).max(128),
|
|
136
|
+
}).superRefine((value, ctx) => {
|
|
137
|
+
const paths = [...value.paths].sort();
|
|
138
|
+
if (
|
|
139
|
+
new Set(paths).size !== paths.length ||
|
|
140
|
+
hasDirectoryConflict(paths) ||
|
|
141
|
+
value.files.some((file) => !paths.includes(file.path))
|
|
142
|
+
)
|
|
143
|
+
ctx.addIssue({ code: 'custom', message: 'Invalid delta inventory' });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/** Authenticated runner export merged into the room before Drive persistence. */
|
|
147
|
+
export const PlaygroundRunnerExport = z
|
|
148
|
+
.object({
|
|
149
|
+
revision: z.number().int().nonnegative(),
|
|
150
|
+
files: PlaygroundFiles,
|
|
151
|
+
paths: z.array(PlaygroundPath).max(128),
|
|
152
|
+
baseline: z.record(PlaygroundPath, z.string().regex(/^[a-f0-9]{64}$/)),
|
|
153
|
+
})
|
|
154
|
+
.strict()
|
|
155
|
+
.refine(
|
|
156
|
+
(value) =>
|
|
157
|
+
new Set(value.paths).size === value.paths.length &&
|
|
158
|
+
!hasDirectoryConflict(value.paths) &&
|
|
159
|
+
value.files.every((file) => value.paths.includes(file.path)),
|
|
160
|
+
'Invalid runner inventory'
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
export const PlaygroundPreviewCapability = z
|
|
164
|
+
.object({
|
|
165
|
+
aud: z.literal('tuturuuu.playground-preview'),
|
|
166
|
+
ownerId: z.guid(),
|
|
167
|
+
projectId: z.guid(),
|
|
168
|
+
port: z.number().int().min(1024).max(65535),
|
|
169
|
+
meetingId: z.guid().optional(),
|
|
170
|
+
roomId: z.guid().optional(),
|
|
171
|
+
exp: z.number().int().positive(),
|
|
172
|
+
})
|
|
173
|
+
.strict();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { PlaygroundLanguage } from '@tuturuuu/types/primitives/playgrounds';
|
|
2
|
+
export const playgroundTemplates: Record<
|
|
3
|
+
PlaygroundLanguage,
|
|
4
|
+
{ path: string; content: string; command: string }
|
|
5
|
+
> = {
|
|
6
|
+
python: {
|
|
7
|
+
path: 'main.py',
|
|
8
|
+
content: 'print("Hello, playground!")\n',
|
|
9
|
+
command: 'python3 main.py',
|
|
10
|
+
},
|
|
11
|
+
javascript: {
|
|
12
|
+
path: 'main.js',
|
|
13
|
+
content: 'console.log("Hello, playground!");\n',
|
|
14
|
+
command: 'node main.js',
|
|
15
|
+
},
|
|
16
|
+
typescript: {
|
|
17
|
+
path: 'main.ts',
|
|
18
|
+
content: 'console.log("Hello, playground!");\n',
|
|
19
|
+
command: 'bun main.ts',
|
|
20
|
+
},
|
|
21
|
+
c: {
|
|
22
|
+
path: 'main.c',
|
|
23
|
+
content:
|
|
24
|
+
'#include <stdio.h>\nint main(void) { puts("Hello, playground!"); }\n',
|
|
25
|
+
command: 'gcc main.c -o /tmp/main && /tmp/main',
|
|
26
|
+
},
|
|
27
|
+
cpp: {
|
|
28
|
+
path: 'main.cpp',
|
|
29
|
+
content:
|
|
30
|
+
'#include <iostream>\nint main() { std::cout << "Hello, playground!\\n"; }\n',
|
|
31
|
+
command: 'g++ main.cpp -o /tmp/main && /tmp/main',
|
|
32
|
+
},
|
|
33
|
+
java: {
|
|
34
|
+
path: 'Main.java',
|
|
35
|
+
content:
|
|
36
|
+
'class Main { public static void main(String[] args) { System.out.println("Hello, playground!"); } }\n',
|
|
37
|
+
command: 'javac Main.java && java Main',
|
|
38
|
+
},
|
|
39
|
+
rust: {
|
|
40
|
+
path: 'main.rs',
|
|
41
|
+
content: 'fn main() { println!("Hello, playground!"); }\n',
|
|
42
|
+
command: 'rustc main.rs -o /tmp/main && /tmp/main',
|
|
43
|
+
},
|
|
44
|
+
go: {
|
|
45
|
+
path: 'main.go',
|
|
46
|
+
content:
|
|
47
|
+
'package main\nimport "fmt"\nfunc main() { fmt.Println("Hello, playground!") }\n',
|
|
48
|
+
command: 'go run main.go',
|
|
49
|
+
},
|
|
50
|
+
ruby: {
|
|
51
|
+
path: 'main.rb',
|
|
52
|
+
content: 'puts "Hello, playground!"\n',
|
|
53
|
+
command: 'ruby main.rb',
|
|
54
|
+
},
|
|
55
|
+
php: {
|
|
56
|
+
path: 'main.php',
|
|
57
|
+
content: '<?php echo "Hello, playground!\\n";\n',
|
|
58
|
+
command: 'php main.php',
|
|
59
|
+
},
|
|
60
|
+
shell: {
|
|
61
|
+
path: 'main.sh',
|
|
62
|
+
content: 'printf "Hello, playground!\\n"\n',
|
|
63
|
+
command: 'sh main.sh',
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const { patterns } = require('./release-note-policy.json');
|
|
2
|
+
const matchers = patterns.map((pattern) => new RegExp(pattern, 'iu'));
|
|
3
|
+
|
|
4
|
+
/** Omit known Git bookkeeping, never arbitrary product text containing merge. */
|
|
5
|
+
function isReleaseBookkeeping(subject) {
|
|
6
|
+
const description = subject
|
|
7
|
+
.replace(/^\w+(?:\([^)]*\))?!?:\s*/u, '')
|
|
8
|
+
.replace(/^\*\*[^*]+:\*\*\s*/u, '')
|
|
9
|
+
.replace(/\s*\(#\d+\)$/u, '')
|
|
10
|
+
.replace(
|
|
11
|
+
/\s*\(\[[^\]]+\]\(https:\/\/github\.com\/tutur3u\/platform\/(?:commit|issues|pull)\/[^)]+\)\)/gu,
|
|
12
|
+
''
|
|
13
|
+
)
|
|
14
|
+
.trim();
|
|
15
|
+
return matchers.some((pattern) => pattern.test(description));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function filterReleaseBookkeeping(markdown) {
|
|
19
|
+
return markdown
|
|
20
|
+
.split('\n')
|
|
21
|
+
.filter((line) => {
|
|
22
|
+
const bullet = /^\s*[-*]\s+(.+)$/u.exec(line);
|
|
23
|
+
return !bullet || !isReleaseBookkeeping(bullet[1]);
|
|
24
|
+
})
|
|
25
|
+
.join('\n');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { isReleaseBookkeeping, filterReleaseBookkeeping };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"patterns": [
|
|
3
|
+
"^merge (?:remote-tracking )?(?:branch(?:es)?|tag|commit) ['\"][^'\"\\n]+['\"](?: (?:and ['\"][^'\"\\n]+['\"]|of \\S+|into (?:['\"][^'\"\\n]+['\"]|\\S+)))*$",
|
|
4
|
+
"^merge pull request #\\d+ from \\S+$",
|
|
5
|
+
"^merge (?:current |latest )?(?:main|production|origin/(?:main|production|release-please--branches--\\S+))(?: (?:into|to) (?:main|production))?$",
|
|
6
|
+
"^(?:sync|synchronize) (?:current |latest )?(?:main|production)(?: (?:to|into|with|from) (?:main|production))$",
|
|
7
|
+
"^address review and integrate current main$"
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { cache } from 'react';
|
|
2
|
+
import { getWorkspace } from './workspace-helper';
|
|
3
|
+
|
|
4
|
+
// React cache is scoped to a server render/request, never a shared authorization
|
|
5
|
+
// cache. Primitive actor keys let sibling layouts/pages reuse the same read.
|
|
6
|
+
const readWorkspace = cache(
|
|
7
|
+
(
|
|
8
|
+
id: string,
|
|
9
|
+
useAdmin: boolean,
|
|
10
|
+
actorId: string | null,
|
|
11
|
+
email: string | null
|
|
12
|
+
) =>
|
|
13
|
+
getWorkspace(id, {
|
|
14
|
+
useAdmin,
|
|
15
|
+
...(actorId ? { user: { id: actorId, email } } : {}),
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export function getRequestWorkspace(
|
|
20
|
+
id: string,
|
|
21
|
+
options: Parameters<typeof getWorkspace>[1] = {}
|
|
22
|
+
) {
|
|
23
|
+
return readWorkspace(
|
|
24
|
+
id,
|
|
25
|
+
options.useAdmin ?? false,
|
|
26
|
+
options.user?.id ?? null,
|
|
27
|
+
options.user?.email ?? null
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ai": {
|
|
3
|
+
"origin": "https://ai.tuturuuu.com",
|
|
4
|
+
"localePrefix": "never",
|
|
5
|
+
"publicPaths": [],
|
|
6
|
+
"publicPatterns": []
|
|
7
|
+
},
|
|
8
|
+
"apps": {
|
|
9
|
+
"origin": "https://apps.tuturuuu.com",
|
|
10
|
+
"localePrefix": "never",
|
|
11
|
+
"publicPaths": [""],
|
|
12
|
+
"publicPatterns": []
|
|
13
|
+
},
|
|
14
|
+
"calendar": {
|
|
15
|
+
"origin": "https://calendar.tuturuuu.com",
|
|
16
|
+
"localePrefix": "never",
|
|
17
|
+
"publicPaths": [],
|
|
18
|
+
"publicPatterns": []
|
|
19
|
+
},
|
|
20
|
+
"chat": {
|
|
21
|
+
"origin": "https://chat.tuturuuu.com",
|
|
22
|
+
"localePrefix": "never",
|
|
23
|
+
"publicPaths": [],
|
|
24
|
+
"publicPatterns": []
|
|
25
|
+
},
|
|
26
|
+
"cms": {
|
|
27
|
+
"origin": "https://cms.tuturuuu.com",
|
|
28
|
+
"localePrefix": "never",
|
|
29
|
+
"publicPaths": [],
|
|
30
|
+
"publicPatterns": []
|
|
31
|
+
},
|
|
32
|
+
"contacts": {
|
|
33
|
+
"origin": "https://contacts.tuturuuu.com",
|
|
34
|
+
"localePrefix": "never",
|
|
35
|
+
"publicPaths": [],
|
|
36
|
+
"publicPatterns": []
|
|
37
|
+
},
|
|
38
|
+
"drive": {
|
|
39
|
+
"origin": "https://drive.tuturuuu.com",
|
|
40
|
+
"localePrefix": "never",
|
|
41
|
+
"publicPaths": [],
|
|
42
|
+
"publicPatterns": []
|
|
43
|
+
},
|
|
44
|
+
"external": {
|
|
45
|
+
"origin": "https://external.tuturuuu.com",
|
|
46
|
+
"localePrefix": "never",
|
|
47
|
+
"publicPaths": [],
|
|
48
|
+
"publicPatterns": []
|
|
49
|
+
},
|
|
50
|
+
"finance": {
|
|
51
|
+
"origin": "https://finance.tuturuuu.com",
|
|
52
|
+
"localePrefix": "never",
|
|
53
|
+
"publicPaths": [],
|
|
54
|
+
"publicPatterns": []
|
|
55
|
+
},
|
|
56
|
+
"forms": {
|
|
57
|
+
"origin": "https://forms.tuturuuu.com",
|
|
58
|
+
"localePrefix": "never",
|
|
59
|
+
"publicPaths": [""],
|
|
60
|
+
"publicPatterns": ["f/[^/]+"]
|
|
61
|
+
},
|
|
62
|
+
"git": {
|
|
63
|
+
"origin": "https://git.tuturuuu.com",
|
|
64
|
+
"localePrefix": "never",
|
|
65
|
+
"publicPaths": ["tutur3u/platform"],
|
|
66
|
+
"publicPatterns": ["", "[^/]+/[^/]+(?:/.*)?"]
|
|
67
|
+
},
|
|
68
|
+
"hive": {
|
|
69
|
+
"origin": "https://hive.tuturuuu.com",
|
|
70
|
+
"localePrefix": "never",
|
|
71
|
+
"publicPaths": [],
|
|
72
|
+
"publicPatterns": []
|
|
73
|
+
},
|
|
74
|
+
"infrastructure": {
|
|
75
|
+
"origin": "https://infrastructure.tuturuuu.com",
|
|
76
|
+
"localePrefix": "never",
|
|
77
|
+
"publicPaths": [],
|
|
78
|
+
"publicPatterns": []
|
|
79
|
+
},
|
|
80
|
+
"inventory": {
|
|
81
|
+
"origin": "https://inventory.tuturuuu.com",
|
|
82
|
+
"localePrefix": "never",
|
|
83
|
+
"publicPaths": [],
|
|
84
|
+
"publicPatterns": []
|
|
85
|
+
},
|
|
86
|
+
"learn": {
|
|
87
|
+
"origin": "https://learn.tuturuuu.com",
|
|
88
|
+
"localePrefix": "never",
|
|
89
|
+
"publicPaths": [""],
|
|
90
|
+
"publicPatterns": []
|
|
91
|
+
},
|
|
92
|
+
"lettin": {
|
|
93
|
+
"origin": "https://lettin.tuturuuu.com",
|
|
94
|
+
"localePrefix": "never",
|
|
95
|
+
"publicPaths": ["", "worlds", "spaces"],
|
|
96
|
+
"publicPatterns": ["worlds/[^/]+", "creators/[^/]+"]
|
|
97
|
+
},
|
|
98
|
+
"mail": {
|
|
99
|
+
"origin": "https://mail.tuturuuu.com",
|
|
100
|
+
"localePrefix": "never",
|
|
101
|
+
"publicPaths": [],
|
|
102
|
+
"publicPatterns": []
|
|
103
|
+
},
|
|
104
|
+
"meet": {
|
|
105
|
+
"origin": "https://meet.tuturuuu.com",
|
|
106
|
+
"localePrefix": "never",
|
|
107
|
+
"publicPaths": [],
|
|
108
|
+
"publicPatterns": []
|
|
109
|
+
},
|
|
110
|
+
"mind": {
|
|
111
|
+
"origin": "https://mind.tuturuuu.com",
|
|
112
|
+
"localePrefix": "never",
|
|
113
|
+
"publicPaths": [],
|
|
114
|
+
"publicPatterns": []
|
|
115
|
+
},
|
|
116
|
+
"nova": {
|
|
117
|
+
"origin": "https://nova.tuturuuu.com",
|
|
118
|
+
"localePrefix": "as-needed",
|
|
119
|
+
"publicPaths": [
|
|
120
|
+
"",
|
|
121
|
+
"learn",
|
|
122
|
+
"learn/introduction",
|
|
123
|
+
"learn/basic-techniques",
|
|
124
|
+
"learn/advanced-strategies",
|
|
125
|
+
"learn/best-practices",
|
|
126
|
+
"competitions/neo-league/prompt-the-future/about"
|
|
127
|
+
],
|
|
128
|
+
"publicPatterns": []
|
|
129
|
+
},
|
|
130
|
+
"parley": {
|
|
131
|
+
"origin": "https://parley.tuturuuu.com",
|
|
132
|
+
"localePrefix": "never",
|
|
133
|
+
"publicPaths": [],
|
|
134
|
+
"publicPatterns": []
|
|
135
|
+
},
|
|
136
|
+
"pay": {
|
|
137
|
+
"origin": "https://pay.tuturuuu.com",
|
|
138
|
+
"localePrefix": "never",
|
|
139
|
+
"publicPaths": [],
|
|
140
|
+
"publicPatterns": []
|
|
141
|
+
},
|
|
142
|
+
"playground": {
|
|
143
|
+
"origin": "https://playground.tuturuuu.com",
|
|
144
|
+
"localePrefix": "never",
|
|
145
|
+
"publicPaths": [],
|
|
146
|
+
"publicPatterns": []
|
|
147
|
+
},
|
|
148
|
+
"rewise": {
|
|
149
|
+
"origin": "https://rewise.tuturuuu.com",
|
|
150
|
+
"localePrefix": "never",
|
|
151
|
+
"publicPaths": [],
|
|
152
|
+
"publicPatterns": []
|
|
153
|
+
},
|
|
154
|
+
"shortener": {
|
|
155
|
+
"origin": "https://shortener.tuturuuu.com",
|
|
156
|
+
"localePrefix": "never",
|
|
157
|
+
"publicPaths": [],
|
|
158
|
+
"publicPatterns": []
|
|
159
|
+
},
|
|
160
|
+
"storefront": {
|
|
161
|
+
"origin": "https://storefront.tuturuuu.com",
|
|
162
|
+
"localePrefix": "never",
|
|
163
|
+
"publicPaths": [],
|
|
164
|
+
"publicPatterns": ["[^/]+", "[^/]+/products/[^/]+"]
|
|
165
|
+
},
|
|
166
|
+
"tasks": {
|
|
167
|
+
"origin": "https://tasks.tuturuuu.com",
|
|
168
|
+
"localePrefix": "never",
|
|
169
|
+
"publicPaths": [],
|
|
170
|
+
"publicPatterns": []
|
|
171
|
+
},
|
|
172
|
+
"teach": {
|
|
173
|
+
"origin": "https://teach.tuturuuu.com",
|
|
174
|
+
"localePrefix": "never",
|
|
175
|
+
"publicPaths": [""],
|
|
176
|
+
"publicPatterns": []
|
|
177
|
+
},
|
|
178
|
+
"tools": {
|
|
179
|
+
"origin": "https://tools.tuturuuu.com",
|
|
180
|
+
"localePrefix": "never",
|
|
181
|
+
"publicPaths": ["", "qr", "random"],
|
|
182
|
+
"publicPatterns": []
|
|
183
|
+
},
|
|
184
|
+
"track": {
|
|
185
|
+
"origin": "https://track.tuturuuu.com",
|
|
186
|
+
"localePrefix": "never",
|
|
187
|
+
"publicPaths": [],
|
|
188
|
+
"publicPatterns": []
|
|
189
|
+
},
|
|
190
|
+
"web": {
|
|
191
|
+
"origin": "https://tuturuuu.com",
|
|
192
|
+
"localePrefix": "as-needed",
|
|
193
|
+
"publicPaths": [],
|
|
194
|
+
"publicPatterns": []
|
|
195
|
+
}
|
|
196
|
+
}
|