apps
lexicons
packages
scripts
···
60
60
61
61
- `place.wisp.v2.domain.claimSubdomain` (procedure / POST, wisp handles)
62
62
- `place.wisp.v2.domain.claim` (procedure / POST)
63
63
+
- `place.wisp.v2.domain.addSite` (procedure / POST)
63
64
- `place.wisp.v2.domain.delete` (procedure / POST)
64
65
- `place.wisp.v2.domain.getList` (query / GET)
65
66
- `place.wisp.v2.domain.getStatus` (query / GET)
67
67
+
- `place.wisp.v2.site.getList` (query / GET)
68
68
+
- `place.wisp.v2.site.delete` (procedure / POST)
66
69
67
70
The server validates **serviceAuth JWTs** on `/xrpc/*`.
68
71
···
1
1
import { SQL } from "bun";
2
2
import { isValidHandle, toDomain } from "./domain-utils";
3
3
+
import { runDatabaseMigrations } from "./migrations";
3
4
4
5
export { isValidHandle, toDomain } from "./domain-utils";
5
6
···
66
67
)
67
68
`;
68
69
69
69
-
// Add columns if they don't exist (for existing databases)
70
70
-
try {
71
71
-
await db`ALTER TABLE domains ADD COLUMN IF NOT EXISTS rkey TEXT`;
72
72
-
} catch (err) {
73
73
-
// Column might already exist, ignore
74
74
-
}
75
75
-
76
76
-
try {
77
77
-
await db`ALTER TABLE oauth_sessions ADD COLUMN IF NOT EXISTS expires_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()) + 2592000`;
78
78
-
} catch (err) {
79
79
-
// Column might already exist, ignore
80
80
-
}
81
81
-
82
82
-
try {
83
83
-
await db`ALTER TABLE oauth_keys ADD COLUMN IF NOT EXISTS created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW())`;
84
84
-
} catch (err) {
85
85
-
// Column might already exist, ignore
86
86
-
}
87
87
-
88
88
-
try {
89
89
-
await db`ALTER TABLE oauth_states ADD COLUMN IF NOT EXISTS expires_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) + 3600`;
90
90
-
} catch (err) {
91
91
-
// Column might already exist, ignore
92
92
-
}
93
93
-
94
94
-
try {
95
95
-
await db`ALTER TABLE service_identity_keys ADD COLUMN IF NOT EXISTS updated_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW())`;
96
96
-
} catch (err) {
97
97
-
// Column might already exist, ignore
98
98
-
}
99
99
-
100
100
-
try {
101
101
-
await db`ALTER TABLE service_identity_keys ADD COLUMN IF NOT EXISTS private_key_multibase TEXT`;
102
102
-
} catch (err) {
103
103
-
// Column might already exist, ignore
104
104
-
}
105
105
-
106
106
-
// Remove the unique constraint on domains.did to allow multiple domains per user
107
107
-
try {
108
108
-
await db`ALTER TABLE domains DROP CONSTRAINT IF EXISTS domains_did_key`;
109
109
-
} catch (err) {
110
110
-
// Constraint might already be removed, ignore
111
111
-
}
112
112
-
113
70
// Custom domains table for BYOD (bring your own domain)
114
71
await db`
115
72
CREATE TABLE IF NOT EXISTS custom_domains (
···
122
79
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW())
123
80
)
124
81
`;
125
125
-
126
126
-
// Migrate existing tables to make rkey nullable and remove default
127
127
-
try {
128
128
-
await db`ALTER TABLE custom_domains ALTER COLUMN rkey DROP NOT NULL`;
129
129
-
} catch (err) {
130
130
-
// Column might already be nullable, ignore
131
131
-
}
132
132
-
try {
133
133
-
await db`ALTER TABLE custom_domains ALTER COLUMN rkey DROP DEFAULT`;
134
134
-
} catch (err) {
135
135
-
// Default might already be removed, ignore
136
136
-
}
137
82
138
83
// Sites table - cache of place.wisp.fs records from PDS
139
84
await db`
···
186
131
)
187
132
`;
188
133
189
189
-
// Insert initial supporter
190
190
-
await db`
191
191
-
INSERT INTO supporter (did)
192
192
-
VALUES ('did:plc:ttdrpj45ibqunmfhdsb4zdwq')
193
193
-
ON CONFLICT (did) DO NOTHING
194
194
-
`;
195
195
-
196
196
-
// Create indexes for common query patterns
197
197
-
await Promise.all([
198
198
-
// oauth_states cleanup queries
199
199
-
db`CREATE INDEX IF NOT EXISTS idx_oauth_states_expires_at ON oauth_states(expires_at)`.catch(err => {
200
200
-
if (!err.message?.includes('already exists')) {
201
201
-
console.error('Failed to create idx_oauth_states_expires_at:', err);
202
202
-
}
203
203
-
}),
204
204
-
205
205
-
// oauth_sessions cleanup queries
206
206
-
db`CREATE INDEX IF NOT EXISTS idx_oauth_sessions_expires_at ON oauth_sessions(expires_at)`.catch(err => {
207
207
-
if (!err.message?.includes('already exists')) {
208
208
-
console.error('Failed to create idx_oauth_sessions_expires_at:', err);
209
209
-
}
210
210
-
}),
211
211
-
212
212
-
// oauth_keys key rotation queries
213
213
-
db`CREATE INDEX IF NOT EXISTS idx_oauth_keys_created_at ON oauth_keys(created_at)`.catch(err => {
214
214
-
if (!err.message?.includes('already exists')) {
215
215
-
console.error('Failed to create idx_oauth_keys_created_at:', err);
216
216
-
}
217
217
-
}),
218
218
-
219
219
-
// domains queries by (did, rkey)
220
220
-
db`CREATE INDEX IF NOT EXISTS idx_domains_did_rkey ON domains(did, rkey)`.catch(err => {
221
221
-
if (!err.message?.includes('already exists')) {
222
222
-
console.error('Failed to create idx_domains_did_rkey:', err);
223
223
-
}
224
224
-
}),
225
225
-
226
226
-
// custom_domains queries by did
227
227
-
db`CREATE INDEX IF NOT EXISTS idx_custom_domains_did ON custom_domains(did)`.catch(err => {
228
228
-
if (!err.message?.includes('already exists')) {
229
229
-
console.error('Failed to create idx_custom_domains_did:', err);
230
230
-
}
231
231
-
}),
232
232
-
233
233
-
// custom_domains queries by (did, rkey)
234
234
-
db`CREATE INDEX IF NOT EXISTS idx_custom_domains_did_rkey ON custom_domains(did, rkey)`.catch(err => {
235
235
-
if (!err.message?.includes('already exists')) {
236
236
-
console.error('Failed to create idx_custom_domains_did_rkey:', err);
237
237
-
}
238
238
-
}),
239
239
-
240
240
-
// custom_domains DNS verification worker queries
241
241
-
db`CREATE INDEX IF NOT EXISTS idx_custom_domains_verified ON custom_domains(verified)`.catch(err => {
242
242
-
if (!err.message?.includes('already exists')) {
243
243
-
console.error('Failed to create idx_custom_domains_verified:', err);
244
244
-
}
245
245
-
}),
246
246
-
247
247
-
// sites queries by did
248
248
-
db`CREATE INDEX IF NOT EXISTS idx_sites_did ON sites(did)`.catch(err => {
249
249
-
if (!err.message?.includes('already exists')) {
250
250
-
console.error('Failed to create idx_sites_did:', err);
251
251
-
}
252
252
-
}),
253
253
-
254
254
-
// site_cache queries by did
255
255
-
db`CREATE INDEX IF NOT EXISTS idx_site_cache_did ON site_cache(did)`.catch(err => {
256
256
-
if (!err.message?.includes('already exists')) {
257
257
-
console.error('Failed to create idx_site_cache_did:', err);
258
258
-
}
259
259
-
}),
260
260
-
261
261
-
// site_cache queries by updated_at (for cleanup/monitoring)
262
262
-
db`CREATE INDEX IF NOT EXISTS idx_site_cache_updated ON site_cache(updated_at)`.catch(err => {
263
263
-
if (!err.message?.includes('already exists')) {
264
264
-
console.error('Failed to create idx_site_cache_updated:', err);
265
265
-
}
266
266
-
})
267
267
-
]);
134
134
+
await runDatabaseMigrations(db);
268
135
269
136
export const getDomainByDid = async (did: string): Promise<string | null> => {
270
137
const rows = await db`SELECT domain FROM domains WHERE did = ${did} ORDER BY created_at ASC LIMIT 1`;
···
1
1
+
import type { SQL } from "bun";
2
2
+
3
3
+
const hasAlreadyExists = (err: unknown): boolean => {
4
4
+
const message = err instanceof Error ? err.message : String(err);
5
5
+
return message.includes("already exists");
6
6
+
};
7
7
+
8
8
+
const runMigration = async (
9
9
+
name: string,
10
10
+
fn: () => Promise<unknown>,
11
11
+
options?: { ignoreAlreadyExists?: boolean; silent?: boolean }
12
12
+
) => {
13
13
+
try {
14
14
+
await fn();
15
15
+
} catch (err) {
16
16
+
if (options?.ignoreAlreadyExists && hasAlreadyExists(err)) {
17
17
+
return;
18
18
+
}
19
19
+
if (!options?.silent) {
20
20
+
console.error(`[DB Migration] ${name} failed:`, err);
21
21
+
}
22
22
+
}
23
23
+
};
24
24
+
25
25
+
export const runDatabaseMigrations = async (db: SQL): Promise<void> => {
26
26
+
// Add columns if they don't exist (for existing databases)
27
27
+
await runMigration("add domains.rkey", async () => {
28
28
+
await db`ALTER TABLE domains ADD COLUMN IF NOT EXISTS rkey TEXT`;
29
29
+
}, { silent: true });
30
30
+
31
31
+
await runMigration("add oauth_sessions.expires_at", async () => {
32
32
+
await db`ALTER TABLE oauth_sessions ADD COLUMN IF NOT EXISTS expires_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()) + 2592000`;
33
33
+
}, { silent: true });
34
34
+
35
35
+
await runMigration("add oauth_keys.created_at", async () => {
36
36
+
await db`ALTER TABLE oauth_keys ADD COLUMN IF NOT EXISTS created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW())`;
37
37
+
}, { silent: true });
38
38
+
39
39
+
await runMigration("add oauth_states.expires_at", async () => {
40
40
+
await db`ALTER TABLE oauth_states ADD COLUMN IF NOT EXISTS expires_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) + 3600`;
41
41
+
}, { silent: true });
42
42
+
43
43
+
await runMigration("add service_identity_keys.updated_at", async () => {
44
44
+
await db`ALTER TABLE service_identity_keys ADD COLUMN IF NOT EXISTS updated_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW())`;
45
45
+
}, { silent: true });
46
46
+
47
47
+
await runMigration("add service_identity_keys.private_key_multibase", async () => {
48
48
+
await db`ALTER TABLE service_identity_keys ADD COLUMN IF NOT EXISTS private_key_multibase TEXT`;
49
49
+
}, { silent: true });
50
50
+
51
51
+
// Remove the unique constraint on domains.did to allow multiple domains per user
52
52
+
await runMigration("drop legacy domains_did_key", async () => {
53
53
+
await db`ALTER TABLE domains DROP CONSTRAINT IF EXISTS domains_did_key`;
54
54
+
}, { silent: true });
55
55
+
56
56
+
// Make custom_domains.rkey nullable and remove default
57
57
+
await runMigration("custom_domains.rkey drop not null", async () => {
58
58
+
await db`ALTER TABLE custom_domains ALTER COLUMN rkey DROP NOT NULL`;
59
59
+
}, { silent: true });
60
60
+
61
61
+
await runMigration("custom_domains.rkey drop default", async () => {
62
62
+
await db`ALTER TABLE custom_domains ALTER COLUMN rkey DROP DEFAULT`;
63
63
+
}, { silent: true });
64
64
+
65
65
+
// Ensure existing domain mappings only point to owned sites before adding FK constraints.
66
66
+
await runMigration("normalize invalid domains.rkey mappings", async () => {
67
67
+
await db`
68
68
+
UPDATE domains d
69
69
+
SET rkey = NULL
70
70
+
WHERE rkey IS NOT NULL
71
71
+
AND NOT EXISTS (
72
72
+
SELECT 1
73
73
+
FROM sites s
74
74
+
WHERE s.did = d.did
75
75
+
AND s.rkey = d.rkey
76
76
+
)
77
77
+
`;
78
78
+
});
79
79
+
80
80
+
await runMigration("normalize invalid custom_domains.rkey mappings", async () => {
81
81
+
await db`
82
82
+
UPDATE custom_domains d
83
83
+
SET rkey = NULL
84
84
+
WHERE rkey IS NOT NULL
85
85
+
AND NOT EXISTS (
86
86
+
SELECT 1
87
87
+
FROM sites s
88
88
+
WHERE s.did = d.did
89
89
+
AND s.rkey = d.rkey
90
90
+
)
91
91
+
`;
92
92
+
});
93
93
+
94
94
+
// Enforce mapped site rkeys belong to same DID as mapped domain.
95
95
+
await runMigration("add fk_domains_site_owner", async () => {
96
96
+
await db`
97
97
+
ALTER TABLE domains
98
98
+
ADD CONSTRAINT fk_domains_site_owner
99
99
+
FOREIGN KEY (did, rkey)
100
100
+
REFERENCES sites(did, rkey)
101
101
+
ON UPDATE CASCADE
102
102
+
ON DELETE SET NULL
103
103
+
`;
104
104
+
}, { ignoreAlreadyExists: true });
105
105
+
106
106
+
await runMigration("add fk_custom_domains_site_owner", async () => {
107
107
+
await db`
108
108
+
ALTER TABLE custom_domains
109
109
+
ADD CONSTRAINT fk_custom_domains_site_owner
110
110
+
FOREIGN KEY (did, rkey)
111
111
+
REFERENCES sites(did, rkey)
112
112
+
ON UPDATE CASCADE
113
113
+
ON DELETE SET NULL
114
114
+
`;
115
115
+
}, { ignoreAlreadyExists: true });
116
116
+
117
117
+
// Seed initial supporter DID
118
118
+
await runMigration("seed initial supporter", async () => {
119
119
+
await db`
120
120
+
INSERT INTO supporter (did)
121
121
+
VALUES ('did:plc:ttdrpj45ibqunmfhdsb4zdwq')
122
122
+
ON CONFLICT (did) DO NOTHING
123
123
+
`;
124
124
+
});
125
125
+
126
126
+
// Create indexes for common query patterns
127
127
+
await Promise.all([
128
128
+
db`CREATE INDEX IF NOT EXISTS idx_oauth_states_expires_at ON oauth_states(expires_at)`.catch((err) => {
129
129
+
if (!hasAlreadyExists(err)) {
130
130
+
console.error("Failed to create idx_oauth_states_expires_at:", err);
131
131
+
}
132
132
+
}),
133
133
+
db`CREATE INDEX IF NOT EXISTS idx_oauth_sessions_expires_at ON oauth_sessions(expires_at)`.catch((err) => {
134
134
+
if (!hasAlreadyExists(err)) {
135
135
+
console.error("Failed to create idx_oauth_sessions_expires_at:", err);
136
136
+
}
137
137
+
}),
138
138
+
db`CREATE INDEX IF NOT EXISTS idx_oauth_keys_created_at ON oauth_keys(created_at)`.catch((err) => {
139
139
+
if (!hasAlreadyExists(err)) {
140
140
+
console.error("Failed to create idx_oauth_keys_created_at:", err);
141
141
+
}
142
142
+
}),
143
143
+
db`CREATE INDEX IF NOT EXISTS idx_domains_did_rkey ON domains(did, rkey)`.catch((err) => {
144
144
+
if (!hasAlreadyExists(err)) {
145
145
+
console.error("Failed to create idx_domains_did_rkey:", err);
146
146
+
}
147
147
+
}),
148
148
+
db`CREATE INDEX IF NOT EXISTS idx_custom_domains_did ON custom_domains(did)`.catch((err) => {
149
149
+
if (!hasAlreadyExists(err)) {
150
150
+
console.error("Failed to create idx_custom_domains_did:", err);
151
151
+
}
152
152
+
}),
153
153
+
db`CREATE INDEX IF NOT EXISTS idx_custom_domains_did_rkey ON custom_domains(did, rkey)`.catch((err) => {
154
154
+
if (!hasAlreadyExists(err)) {
155
155
+
console.error("Failed to create idx_custom_domains_did_rkey:", err);
156
156
+
}
157
157
+
}),
158
158
+
db`CREATE INDEX IF NOT EXISTS idx_custom_domains_verified ON custom_domains(verified)`.catch((err) => {
159
159
+
if (!hasAlreadyExists(err)) {
160
160
+
console.error("Failed to create idx_custom_domains_verified:", err);
161
161
+
}
162
162
+
}),
163
163
+
db`CREATE INDEX IF NOT EXISTS idx_sites_did ON sites(did)`.catch((err) => {
164
164
+
if (!hasAlreadyExists(err)) {
165
165
+
console.error("Failed to create idx_sites_did:", err);
166
166
+
}
167
167
+
}),
168
168
+
db`CREATE INDEX IF NOT EXISTS idx_site_cache_did ON site_cache(did)`.catch((err) => {
169
169
+
if (!hasAlreadyExists(err)) {
170
170
+
console.error("Failed to create idx_site_cache_did:", err);
171
171
+
}
172
172
+
}),
173
173
+
db`CREATE INDEX IF NOT EXISTS idx_site_cache_updated ON site_cache(updated_at)`.catch((err) => {
174
174
+
if (!hasAlreadyExists(err)) {
175
175
+
console.error("Failed to create idx_site_cache_updated:", err);
176
176
+
}
177
177
+
}),
178
178
+
]);
179
179
+
};
···
6
6
import { json, XRPCRouter, XRPCError } from '@atcute/xrpc-server';
7
7
import { ServiceJwtVerifier } from '@atcute/xrpc-server/auth';
8
8
import {
9
9
+
PlaceWispV2DomainAddSite,
9
10
PlaceWispV2DomainClaim,
10
11
PlaceWispV2DomainClaimSubdomain,
11
12
PlaceWispV2DomainDelete,
12
13
PlaceWispV2DomainGetList,
13
14
PlaceWispV2DomainGetStatus,
15
15
+
PlaceWispV2SiteDelete,
16
16
+
PlaceWispV2SiteGetList,
14
17
} from '@wispplace/lexicons/atcute';
15
18
import { BASE_HOST } from '@wispplace/constants';
16
19
···
20
23
claimCustomDomain,
21
24
claimDomain,
22
25
deleteCustomDomain,
26
26
+
deleteSite,
23
27
deleteWispDomain,
28
28
+
getDomainsBySite,
24
29
getAllWispDomains,
25
30
getCustomDomainsByDid,
26
31
getCustomDomainInfo,
32
32
+
getSitesByDid,
27
33
isDomainRegistered,
28
34
updateCustomDomainRkey,
29
35
updateWispDomainSite,
···
56
62
});
57
63
58
64
const NSID_ALIASES: Record<string, string> = {
65
65
+
'place.wisp.v2.domain.add-site': 'place.wisp.v2.domain.addSite',
66
66
+
'place.wisp.v2.domain.addsite': 'place.wisp.v2.domain.addSite',
59
67
'place.wisp.v2.domain.claim-subdomain': 'place.wisp.v2.domain.claimSubdomain',
60
68
'place.wisp.v2.domain.claimsubdomain': 'place.wisp.v2.domain.claimSubdomain',
61
69
'place.wisp.v2.domain.claimsub-domain': 'place.wisp.v2.domain.claimSubdomain',
···
63
71
'place.wisp.v2.domain.getlist': 'place.wisp.v2.domain.getList',
64
72
'place.wisp.v2.domain.getstatus': 'place.wisp.v2.domain.getStatus',
65
73
'place.wisp.v2.domain.get-status': 'place.wisp.v2.domain.getStatus',
74
74
+
'place.wisp.v2.site.delete-site': 'place.wisp.v2.site.delete',
75
75
+
'place.wisp.v2.site.deletesite': 'place.wisp.v2.site.delete',
76
76
+
'place.wisp.v2.site.get-list': 'place.wisp.v2.site.getList',
77
77
+
'place.wisp.v2.site.getlist': 'place.wisp.v2.site.getList',
66
78
};
67
79
68
80
const XRPC_NSIDS = {
81
81
+
addSite: 'place.wisp.v2.domain.addSite',
69
82
getStatus: 'place.wisp.v2.domain.getStatus',
70
83
getList: 'place.wisp.v2.domain.getList',
84
84
+
siteGetList: 'place.wisp.v2.site.getList',
71
85
claimSubdomain: 'place.wisp.v2.domain.claimSubdomain',
72
86
claim: 'place.wisp.v2.domain.claim',
73
87
delete: 'place.wisp.v2.domain.delete',
88
88
+
deleteSite: 'place.wisp.v2.site.delete',
74
89
} as const;
75
90
76
91
const toIsoFromEpoch = (epoch: unknown): string | undefined => {
···
139
154
throw new XRPCError({
140
155
status: 404,
141
156
error: 'NotFound',
157
157
+
description,
158
158
+
});
159
159
+
};
160
160
+
161
161
+
const invalidRequest = (description: string): never => {
162
162
+
throw new XRPCError({
163
163
+
status: 400,
164
164
+
error: 'InvalidRequest',
142
165
description,
143
166
});
144
167
};
···
393
416
});
394
417
};
395
418
419
419
+
const mapDomainToSiteForDid = async (
420
420
+
did: DidString,
421
421
+
input: { domain: string; siteRkey: string },
422
422
+
) => {
423
423
+
const domain = normalizeDomain(input.domain);
424
424
+
if (domain.length === 0) {
425
425
+
invalidDomain('domain is required');
426
426
+
}
427
427
+
428
428
+
const siteRkey = input.siteRkey.trim();
429
429
+
if (siteRkey.length === 0) {
430
430
+
invalidRequest('siteRkey is required');
431
431
+
}
432
432
+
433
433
+
const sites = await getSitesByDid(did);
434
434
+
const ownsSite = sites.some((entry: { rkey: string }) => entry.rkey === siteRkey);
435
435
+
if (!ownsSite) {
436
436
+
notFound('site not found');
437
437
+
}
438
438
+
439
439
+
const existing = await isDomainRegistered(domain);
440
440
+
if (!existing.registered || existing.did !== did) {
441
441
+
notFound('domain not found');
442
442
+
}
443
443
+
444
444
+
if (existing.type === 'wisp') {
445
445
+
await updateWispDomainSite(domain, siteRkey);
446
446
+
447
447
+
return json({
448
448
+
domain,
449
449
+
kind: 'wisp',
450
450
+
status: 'verified',
451
451
+
siteRkey,
452
452
+
mapped: true,
453
453
+
});
454
454
+
}
455
455
+
456
456
+
const custom = await getCustomDomainInfo(domain);
457
457
+
if (!custom || custom.did !== did) {
458
458
+
notFound('domain not found');
459
459
+
}
460
460
+
461
461
+
await updateCustomDomainRkey(custom.id as string, siteRkey);
462
462
+
463
463
+
return json({
464
464
+
domain,
465
465
+
kind: 'custom',
466
466
+
status: custom.verified ? 'verified' : 'pendingVerification',
467
467
+
siteRkey,
468
468
+
mapped: true,
469
469
+
});
470
470
+
};
471
471
+
472
472
+
const deleteSiteForDid = async (
473
473
+
did: DidString,
474
474
+
input: { siteRkey: string },
475
475
+
) => {
476
476
+
const siteRkey = input.siteRkey.trim();
477
477
+
if (siteRkey.length === 0) {
478
478
+
invalidRequest('siteRkey is required');
479
479
+
}
480
480
+
481
481
+
const sites = await getSitesByDid(did);
482
482
+
const ownsSite = sites.some((entry: { rkey: string }) => entry.rkey === siteRkey);
483
483
+
if (!ownsSite) {
484
484
+
notFound('site not found');
485
485
+
}
486
486
+
487
487
+
const mappedDomains = await getDomainsBySite(did, siteRkey);
488
488
+
489
489
+
const unmappedDomains: Array<{
490
490
+
domain: string;
491
491
+
kind: 'wisp' | 'custom';
492
492
+
status: 'pendingVerification' | 'verified';
493
493
+
}> = [];
494
494
+
495
495
+
for (const mapped of mappedDomains as Array<{
496
496
+
type: 'wisp' | 'custom';
497
497
+
domain: string;
498
498
+
id?: string;
499
499
+
verified?: boolean;
500
500
+
}>) {
501
501
+
if (mapped.type === 'wisp') {
502
502
+
await updateWispDomainSite(mapped.domain, null);
503
503
+
unmappedDomains.push({
504
504
+
domain: mapped.domain,
505
505
+
kind: 'wisp',
506
506
+
status: 'verified',
507
507
+
});
508
508
+
continue;
509
509
+
}
510
510
+
511
511
+
if (mapped.id) {
512
512
+
await updateCustomDomainRkey(mapped.id, null);
513
513
+
unmappedDomains.push({
514
514
+
domain: mapped.domain,
515
515
+
kind: 'custom',
516
516
+
status: mapped.verified ? 'verified' : 'pendingVerification',
517
517
+
});
518
518
+
}
519
519
+
}
520
520
+
521
521
+
const deleted = await deleteSite(did, siteRkey);
522
522
+
if (!deleted.success) {
523
523
+
throw new XRPCError({
524
524
+
status: 500,
525
525
+
error: 'InternalServerError',
526
526
+
description: 'failed to delete site',
527
527
+
});
528
528
+
}
529
529
+
530
530
+
return json({
531
531
+
siteRkey,
532
532
+
deleted: true,
533
533
+
unmappedDomains: unmappedDomains.sort((a, b) => a.domain.localeCompare(b.domain)),
534
534
+
});
535
535
+
};
536
536
+
396
537
export const xrpcRoutes = () => {
397
538
const authByRequest = new WeakMap<Request, XrpcAuthContext>();
398
539
const router = new XRPCRouter();
399
540
const registeredNsids = [
541
541
+
XRPC_NSIDS.addSite,
400
542
XRPC_NSIDS.getStatus,
401
543
XRPC_NSIDS.getList,
544
544
+
XRPC_NSIDS.siteGetList,
402
545
XRPC_NSIDS.claimSubdomain,
403
546
XRPC_NSIDS.claim,
404
547
XRPC_NSIDS.delete,
548
548
+
XRPC_NSIDS.deleteSite,
405
549
];
550
550
+
551
551
+
addProcedureWithAliases(
552
552
+
router,
553
553
+
withNsid(PlaceWispV2DomainAddSite.mainSchema as any, XRPC_NSIDS.addSite),
554
554
+
['place.wisp.v2.domain.addsite', 'place.wisp.v2.domain.add-site'],
555
555
+
{
556
556
+
async handler({ input, request }) {
557
557
+
const auth = requireAuthenticated(authByRequest.get(request));
558
558
+
const did = auth.did as DidString;
559
559
+
560
560
+
return mapDomainToSiteForDid(did, {
561
561
+
domain: input.domain,
562
562
+
siteRkey: input.siteRkey,
563
563
+
});
564
564
+
},
565
565
+
},
566
566
+
);
406
567
407
568
addQueryWithAliases(
408
569
router,
···
509
670
},
510
671
);
511
672
673
673
+
addQueryWithAliases(
674
674
+
router,
675
675
+
withNsid(PlaceWispV2SiteGetList.mainSchema as any, XRPC_NSIDS.siteGetList),
676
676
+
['place.wisp.v2.site.getlist', 'place.wisp.v2.site.get-list'],
677
677
+
{
678
678
+
async handler({ request }) {
679
679
+
const auth = requireAuthenticated(authByRequest.get(request));
680
680
+
const did = auth.did as DidString;
681
681
+
682
682
+
const sites = await getSitesByDid(did);
683
683
+
684
684
+
const siteSummaries = await Promise.all(
685
685
+
sites.map(async (site: {
686
686
+
rkey: string;
687
687
+
display_name?: string | null;
688
688
+
created_at?: number | string | null;
689
689
+
updated_at?: number | string | null;
690
690
+
}) => {
691
691
+
const mappedDomains = await getDomainsBySite(did, site.rkey);
692
692
+
const domains = (mappedDomains as Array<{
693
693
+
type: 'wisp' | 'custom';
694
694
+
domain: string;
695
695
+
verified?: boolean;
696
696
+
}>)
697
697
+
.map((entry) => ({
698
698
+
domain: entry.domain,
699
699
+
kind: entry.type,
700
700
+
status:
701
701
+
entry.type === 'wisp'
702
702
+
? ('verified' as const)
703
703
+
: entry.verified
704
704
+
? ('verified' as const)
705
705
+
: ('pendingVerification' as const),
706
706
+
verified: entry.type === 'wisp' ? true : Boolean(entry.verified),
707
707
+
}))
708
708
+
.sort((a, b) => a.domain.localeCompare(b.domain));
709
709
+
710
710
+
return {
711
711
+
siteRkey: site.rkey,
712
712
+
displayName: site.display_name ?? undefined,
713
713
+
createdAt: toIsoFromEpoch(site.created_at),
714
714
+
updatedAt: toIsoFromEpoch(site.updated_at),
715
715
+
domains,
716
716
+
};
717
717
+
}),
718
718
+
);
719
719
+
720
720
+
return json({ sites: siteSummaries });
721
721
+
},
722
722
+
},
723
723
+
);
724
724
+
512
725
addProcedureWithAliases(
513
726
router,
514
727
withNsid(PlaceWispV2DomainClaimSubdomain.mainSchema as any, XRPC_NSIDS.claimSubdomain),
···
585
798
},
586
799
);
587
800
801
801
+
addProcedureWithAliases(
802
802
+
router,
803
803
+
withNsid(PlaceWispV2SiteDelete.mainSchema as any, XRPC_NSIDS.deleteSite),
804
804
+
['place.wisp.v2.site.deletesite', 'place.wisp.v2.site.delete-site'],
805
805
+
{
806
806
+
async handler({ input, request }) {
807
807
+
const auth = requireAuthenticated(authByRequest.get(request));
808
808
+
const did = auth.did as DidString;
809
809
+
810
810
+
return deleteSiteForDid(did, {
811
811
+
siteRkey: input.siteRkey,
812
812
+
});
813
813
+
},
814
814
+
},
815
815
+
);
816
816
+
588
817
const schemaNsids = {
818
818
+
addSite: (PlaceWispV2DomainAddSite.mainSchema as any).nsid,
589
819
getStatus: (PlaceWispV2DomainGetStatus.mainSchema as any).nsid,
590
820
getList: (PlaceWispV2DomainGetList.mainSchema as any).nsid,
821
821
+
siteGetList: (PlaceWispV2SiteGetList.mainSchema as any).nsid,
591
822
claimSubdomain: (PlaceWispV2DomainClaimSubdomain.mainSchema as any).nsid,
592
823
claim: (PlaceWispV2DomainClaim.mainSchema as any).nsid,
593
824
delete: (PlaceWispV2DomainDelete.mainSchema as any).nsid,
825
825
+
deleteSite: (PlaceWispV2SiteDelete.mainSchema as any).nsid,
594
826
};
595
827
logger.info('[XRPC] Registered methods', {
596
828
expectedNsids: registeredNsids,
···
1
1
+
{
2
2
+
"lexicon": 1,
3
3
+
"id": "place.wisp.v2.domain.addSite",
4
4
+
"defs": {
5
5
+
"main": {
6
6
+
"type": "procedure",
7
7
+
"description": "Map an owned domain to one owned site record.",
8
8
+
"input": {
9
9
+
"encoding": "application/json",
10
10
+
"schema": {
11
11
+
"type": "object",
12
12
+
"required": ["domain", "siteRkey"],
13
13
+
"properties": {
14
14
+
"domain": {
15
15
+
"type": "string",
16
16
+
"description": "Fully-qualified domain to map.",
17
17
+
"minLength": 3,
18
18
+
"maxLength": 253
19
19
+
},
20
20
+
"siteRkey": {
21
21
+
"type": "string",
22
22
+
"format": "record-key",
23
23
+
"description": "Owned place.wisp.fs record key to map this domain to."
24
24
+
}
25
25
+
}
26
26
+
}
27
27
+
},
28
28
+
"output": {
29
29
+
"encoding": "application/json",
30
30
+
"schema": {
31
31
+
"type": "object",
32
32
+
"required": ["domain", "kind", "status", "siteRkey", "mapped"],
33
33
+
"properties": {
34
34
+
"domain": {
35
35
+
"type": "string"
36
36
+
},
37
37
+
"kind": {
38
38
+
"type": "string",
39
39
+
"enum": ["wisp", "custom"]
40
40
+
},
41
41
+
"status": {
42
42
+
"type": "string",
43
43
+
"enum": ["pendingVerification", "verified"]
44
44
+
},
45
45
+
"siteRkey": {
46
46
+
"type": "string",
47
47
+
"format": "record-key"
48
48
+
},
49
49
+
"mapped": {
50
50
+
"type": "boolean",
51
51
+
"const": true
52
52
+
}
53
53
+
}
54
54
+
}
55
55
+
},
56
56
+
"errors": [
57
57
+
{ "name": "AuthenticationRequired" },
58
58
+
{ "name": "InvalidDomain" },
59
59
+
{ "name": "InvalidRequest" },
60
60
+
{ "name": "NotFound" }
61
61
+
]
62
62
+
}
63
63
+
}
64
64
+
}
···
1
1
+
{
2
2
+
"lexicon": 1,
3
3
+
"id": "place.wisp.v2.site.delete",
4
4
+
"defs": {
5
5
+
"main": {
6
6
+
"type": "procedure",
7
7
+
"description": "Delete one owned site metadata entry and unmap any domains pointing to it.",
8
8
+
"input": {
9
9
+
"encoding": "application/json",
10
10
+
"schema": {
11
11
+
"type": "object",
12
12
+
"required": ["siteRkey"],
13
13
+
"properties": {
14
14
+
"siteRkey": {
15
15
+
"type": "string",
16
16
+
"format": "record-key",
17
17
+
"description": "Owned place.wisp.fs record key to delete from wisp metadata."
18
18
+
}
19
19
+
}
20
20
+
}
21
21
+
},
22
22
+
"output": {
23
23
+
"encoding": "application/json",
24
24
+
"schema": {
25
25
+
"type": "object",
26
26
+
"required": ["siteRkey", "deleted", "unmappedDomains"],
27
27
+
"properties": {
28
28
+
"siteRkey": {
29
29
+
"type": "string",
30
30
+
"format": "record-key"
31
31
+
},
32
32
+
"deleted": {
33
33
+
"type": "boolean",
34
34
+
"const": true
35
35
+
},
36
36
+
"unmappedDomains": {
37
37
+
"type": "array",
38
38
+
"description": "Domains that were detached from this site before deletion.",
39
39
+
"items": {
40
40
+
"type": "ref",
41
41
+
"ref": "#unmappedDomain"
42
42
+
}
43
43
+
}
44
44
+
}
45
45
+
}
46
46
+
},
47
47
+
"errors": [
48
48
+
{ "name": "AuthenticationRequired" },
49
49
+
{ "name": "InvalidRequest" },
50
50
+
{ "name": "NotFound" }
51
51
+
]
52
52
+
},
53
53
+
"unmappedDomain": {
54
54
+
"type": "object",
55
55
+
"required": ["domain", "kind", "status"],
56
56
+
"properties": {
57
57
+
"domain": {
58
58
+
"type": "string",
59
59
+
"minLength": 3,
60
60
+
"maxLength": 253
61
61
+
},
62
62
+
"kind": {
63
63
+
"type": "string",
64
64
+
"enum": ["wisp", "custom"]
65
65
+
},
66
66
+
"status": {
67
67
+
"type": "string",
68
68
+
"enum": ["pendingVerification", "verified"]
69
69
+
}
70
70
+
}
71
71
+
}
72
72
+
}
73
73
+
}
···
1
1
+
{
2
2
+
"lexicon": 1,
3
3
+
"id": "place.wisp.v2.site.getList",
4
4
+
"defs": {
5
5
+
"main": {
6
6
+
"type": "query",
7
7
+
"description": "List owned sites and the domains currently mapped to each site.",
8
8
+
"output": {
9
9
+
"encoding": "application/json",
10
10
+
"schema": {
11
11
+
"type": "object",
12
12
+
"required": ["sites"],
13
13
+
"properties": {
14
14
+
"sites": {
15
15
+
"type": "array",
16
16
+
"items": {
17
17
+
"type": "ref",
18
18
+
"ref": "#siteSummary"
19
19
+
}
20
20
+
}
21
21
+
}
22
22
+
}
23
23
+
},
24
24
+
"errors": [
25
25
+
{ "name": "AuthenticationRequired" }
26
26
+
]
27
27
+
},
28
28
+
"siteSummary": {
29
29
+
"type": "object",
30
30
+
"required": ["siteRkey", "domains"],
31
31
+
"properties": {
32
32
+
"siteRkey": {
33
33
+
"type": "string",
34
34
+
"format": "record-key"
35
35
+
},
36
36
+
"displayName": {
37
37
+
"type": "string",
38
38
+
"maxLength": 200
39
39
+
},
40
40
+
"createdAt": {
41
41
+
"type": "string",
42
42
+
"format": "datetime"
43
43
+
},
44
44
+
"updatedAt": {
45
45
+
"type": "string",
46
46
+
"format": "datetime"
47
47
+
},
48
48
+
"domains": {
49
49
+
"type": "array",
50
50
+
"items": {
51
51
+
"type": "ref",
52
52
+
"ref": "#siteDomain"
53
53
+
}
54
54
+
}
55
55
+
}
56
56
+
},
57
57
+
"siteDomain": {
58
58
+
"type": "object",
59
59
+
"required": ["domain", "kind", "status", "verified"],
60
60
+
"properties": {
61
61
+
"domain": {
62
62
+
"type": "string",
63
63
+
"minLength": 3,
64
64
+
"maxLength": 253
65
65
+
},
66
66
+
"kind": {
67
67
+
"type": "string",
68
68
+
"enum": ["wisp", "custom"]
69
69
+
},
70
70
+
"status": {
71
71
+
"type": "string",
72
72
+
"enum": ["pendingVerification", "verified"]
73
73
+
},
74
74
+
"verified": {
75
75
+
"type": "boolean"
76
76
+
}
77
77
+
}
78
78
+
}
79
79
+
}
80
80
+
}
···
60
60
}
61
61
},
62
62
"scripts": {
63
63
-
"codegen": "lex gen-server ./src ../../../lexicons/*.json",
64
64
-
"codegen:atcute": "lex-cli generate -c lex.atcute.config.js"
63
63
+
"codegen": "bun run codegen:server && bun run codegen:atcute && bun run codegen:verify",
64
64
+
"codegen:server": "lex gen-server ./src ../../../lexicons/*.json",
65
65
+
"codegen:atcute": "lex-cli generate -c lex.atcute.config.js",
66
66
+
"codegen:verify": "test -f ./src/atcute/lexicons/index.ts"
65
67
},
66
68
"dependencies": {
67
69
"@atcute/lexicons": "^1.2.9",
···
1
1
+
export * as PlaceWispV2DomainAddSite from "./types/place/wisp/v2/domain/addSite.js";
1
2
export * as PlaceWispV2DomainClaim from "./types/place/wisp/v2/domain/claim.js";
2
3
export * as PlaceWispV2DomainClaimSubdomain from "./types/place/wisp/v2/domain/claimSubdomain.js";
3
4
export * as PlaceWispV2DomainDelete from "./types/place/wisp/v2/domain/delete.js";
4
5
export * as PlaceWispV2DomainGetList from "./types/place/wisp/v2/domain/getList.js";
5
6
export * as PlaceWispV2DomainGetStatus from "./types/place/wisp/v2/domain/getStatus.js";
6
7
export * as PlaceWispV2Domains from "./types/place/wisp/v2/domains.js";
8
8
+
export * as PlaceWispV2SiteDelete from "./types/place/wisp/v2/site/delete.js";
9
9
+
export * as PlaceWispV2SiteGetList from "./types/place/wisp/v2/site/getList.js";
···
1
1
+
import type {} from "@atcute/lexicons";
2
2
+
import * as v from "@atcute/lexicons/validations";
3
3
+
import type {} from "@atcute/lexicons/ambient";
4
4
+
5
5
+
const _mainSchema = /*#__PURE__*/ v.procedure("place.wisp.v2.domain.addSite", {
6
6
+
params: null,
7
7
+
input: {
8
8
+
type: "lex",
9
9
+
schema: /*#__PURE__*/ v.object({
10
10
+
/**
11
11
+
* Fully-qualified domain to map.
12
12
+
* @minLength 3
13
13
+
* @maxLength 253
14
14
+
*/
15
15
+
domain: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [
16
16
+
/*#__PURE__*/ v.stringLength(3, 253),
17
17
+
]),
18
18
+
/**
19
19
+
* Owned place.wisp.fs record key to map this domain to.
20
20
+
*/
21
21
+
siteRkey: /*#__PURE__*/ v.recordKeyString(),
22
22
+
}),
23
23
+
},
24
24
+
output: {
25
25
+
type: "lex",
26
26
+
schema: /*#__PURE__*/ v.object({
27
27
+
domain: /*#__PURE__*/ v.string(),
28
28
+
kind: /*#__PURE__*/ v.literalEnum(["custom", "wisp"]),
29
29
+
mapped: /*#__PURE__*/ v.literal(true),
30
30
+
siteRkey: /*#__PURE__*/ v.recordKeyString(),
31
31
+
status: /*#__PURE__*/ v.literalEnum(["pendingVerification", "verified"]),
32
32
+
}),
33
33
+
},
34
34
+
});
35
35
+
36
36
+
type main$schematype = typeof _mainSchema;
37
37
+
38
38
+
export interface mainSchema extends main$schematype {}
39
39
+
40
40
+
export const mainSchema = _mainSchema as mainSchema;
41
41
+
42
42
+
export interface $params {}
43
43
+
export interface $input extends v.InferXRPCBodyInput<mainSchema["input"]> {}
44
44
+
export interface $output extends v.InferXRPCBodyInput<mainSchema["output"]> {}
45
45
+
46
46
+
declare module "@atcute/lexicons/ambient" {
47
47
+
interface XRPCProcedures {
48
48
+
"place.wisp.v2.domain.addSite": mainSchema;
49
49
+
}
50
50
+
}
···
1
1
+
import type {} from "@atcute/lexicons";
2
2
+
import * as v from "@atcute/lexicons/validations";
3
3
+
import type {} from "@atcute/lexicons/ambient";
4
4
+
5
5
+
const _mainSchema = /*#__PURE__*/ v.procedure("place.wisp.v2.site.delete", {
6
6
+
params: null,
7
7
+
input: {
8
8
+
type: "lex",
9
9
+
schema: /*#__PURE__*/ v.object({
10
10
+
/**
11
11
+
* Owned place.wisp.fs record key to delete from wisp metadata.
12
12
+
*/
13
13
+
siteRkey: /*#__PURE__*/ v.recordKeyString(),
14
14
+
}),
15
15
+
},
16
16
+
output: {
17
17
+
type: "lex",
18
18
+
schema: /*#__PURE__*/ v.object({
19
19
+
deleted: /*#__PURE__*/ v.literal(true),
20
20
+
siteRkey: /*#__PURE__*/ v.recordKeyString(),
21
21
+
/**
22
22
+
* Domains that were detached from this site before deletion.
23
23
+
*/
24
24
+
get unmappedDomains() {
25
25
+
return /*#__PURE__*/ v.array(unmappedDomainSchema);
26
26
+
},
27
27
+
}),
28
28
+
},
29
29
+
});
30
30
+
const _unmappedDomainSchema = /*#__PURE__*/ v.object({
31
31
+
$type: /*#__PURE__*/ v.optional(
32
32
+
/*#__PURE__*/ v.literal("place.wisp.v2.site.delete#unmappedDomain"),
33
33
+
),
34
34
+
/**
35
35
+
* @minLength 3
36
36
+
* @maxLength 253
37
37
+
*/
38
38
+
domain: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [
39
39
+
/*#__PURE__*/ v.stringLength(3, 253),
40
40
+
]),
41
41
+
kind: /*#__PURE__*/ v.literalEnum(["custom", "wisp"]),
42
42
+
status: /*#__PURE__*/ v.literalEnum(["pendingVerification", "verified"]),
43
43
+
});
44
44
+
45
45
+
type main$schematype = typeof _mainSchema;
46
46
+
type unmappedDomain$schematype = typeof _unmappedDomainSchema;
47
47
+
48
48
+
export interface mainSchema extends main$schematype {}
49
49
+
export interface unmappedDomainSchema extends unmappedDomain$schematype {}
50
50
+
51
51
+
export const mainSchema = _mainSchema as mainSchema;
52
52
+
export const unmappedDomainSchema =
53
53
+
_unmappedDomainSchema as unmappedDomainSchema;
54
54
+
55
55
+
export interface UnmappedDomain extends v.InferInput<
56
56
+
typeof unmappedDomainSchema
57
57
+
> {}
58
58
+
59
59
+
export interface $params {}
60
60
+
export interface $input extends v.InferXRPCBodyInput<mainSchema["input"]> {}
61
61
+
export interface $output extends v.InferXRPCBodyInput<mainSchema["output"]> {}
62
62
+
63
63
+
declare module "@atcute/lexicons/ambient" {
64
64
+
interface XRPCProcedures {
65
65
+
"place.wisp.v2.site.delete": mainSchema;
66
66
+
}
67
67
+
}
···
1
1
+
import type {} from "@atcute/lexicons";
2
2
+
import * as v from "@atcute/lexicons/validations";
3
3
+
import type {} from "@atcute/lexicons/ambient";
4
4
+
5
5
+
const _mainSchema = /*#__PURE__*/ v.query("place.wisp.v2.site.getList", {
6
6
+
params: null,
7
7
+
output: {
8
8
+
type: "lex",
9
9
+
schema: /*#__PURE__*/ v.object({
10
10
+
get sites() {
11
11
+
return /*#__PURE__*/ v.array(siteSummarySchema);
12
12
+
},
13
13
+
}),
14
14
+
},
15
15
+
});
16
16
+
const _siteDomainSchema = /*#__PURE__*/ v.object({
17
17
+
$type: /*#__PURE__*/ v.optional(
18
18
+
/*#__PURE__*/ v.literal("place.wisp.v2.site.getList#siteDomain"),
19
19
+
),
20
20
+
/**
21
21
+
* @minLength 3
22
22
+
* @maxLength 253
23
23
+
*/
24
24
+
domain: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [
25
25
+
/*#__PURE__*/ v.stringLength(3, 253),
26
26
+
]),
27
27
+
kind: /*#__PURE__*/ v.literalEnum(["custom", "wisp"]),
28
28
+
status: /*#__PURE__*/ v.literalEnum(["pendingVerification", "verified"]),
29
29
+
verified: /*#__PURE__*/ v.boolean(),
30
30
+
});
31
31
+
const _siteSummarySchema = /*#__PURE__*/ v.object({
32
32
+
$type: /*#__PURE__*/ v.optional(
33
33
+
/*#__PURE__*/ v.literal("place.wisp.v2.site.getList#siteSummary"),
34
34
+
),
35
35
+
createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()),
36
36
+
/**
37
37
+
* @maxLength 200
38
38
+
*/
39
39
+
displayName: /*#__PURE__*/ v.optional(
40
40
+
/*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [
41
41
+
/*#__PURE__*/ v.stringLength(0, 200),
42
42
+
]),
43
43
+
),
44
44
+
get domains() {
45
45
+
return /*#__PURE__*/ v.array(siteDomainSchema);
46
46
+
},
47
47
+
siteRkey: /*#__PURE__*/ v.recordKeyString(),
48
48
+
updatedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()),
49
49
+
});
50
50
+
51
51
+
type main$schematype = typeof _mainSchema;
52
52
+
type siteDomain$schematype = typeof _siteDomainSchema;
53
53
+
type siteSummary$schematype = typeof _siteSummarySchema;
54
54
+
55
55
+
export interface mainSchema extends main$schematype {}
56
56
+
export interface siteDomainSchema extends siteDomain$schematype {}
57
57
+
export interface siteSummarySchema extends siteSummary$schematype {}
58
58
+
59
59
+
export const mainSchema = _mainSchema as mainSchema;
60
60
+
export const siteDomainSchema = _siteDomainSchema as siteDomainSchema;
61
61
+
export const siteSummarySchema = _siteSummarySchema as siteSummarySchema;
62
62
+
63
63
+
export interface SiteDomain extends v.InferInput<typeof siteDomainSchema> {}
64
64
+
export interface SiteSummary extends v.InferInput<typeof siteSummarySchema> {}
65
65
+
66
66
+
export interface $params {}
67
67
+
export interface $output extends v.InferXRPCBodyInput<mainSchema["output"]> {}
68
68
+
69
69
+
declare module "@atcute/lexicons/ambient" {
70
70
+
interface XRPCQueries {
71
71
+
"place.wisp.v2.site.getList": mainSchema;
72
72
+
}
73
73
+
}
···
10
10
createServer as createXrpcServer,
11
11
} from '@atproto/xrpc-server'
12
12
import { schemas } from './lexicons.js'
13
13
+
import * as PlaceWispV2DomainAddSite from './types/place/wisp/v2/domain/addSite.js'
13
14
import * as PlaceWispV2DomainClaimSubdomain from './types/place/wisp/v2/domain/claimSubdomain.js'
14
15
import * as PlaceWispV2DomainClaim from './types/place/wisp/v2/domain/claim.js'
15
16
import * as PlaceWispV2DomainDelete from './types/place/wisp/v2/domain/delete.js'
16
17
import * as PlaceWispV2DomainGetList from './types/place/wisp/v2/domain/getList.js'
17
18
import * as PlaceWispV2DomainGetStatus from './types/place/wisp/v2/domain/getStatus.js'
19
19
+
import * as PlaceWispV2SiteDelete from './types/place/wisp/v2/site/delete.js'
20
20
+
import * as PlaceWispV2SiteGetList from './types/place/wisp/v2/site/getList.js'
18
21
19
22
export function createServer(options?: XrpcOptions): Server {
20
23
return new Server(options)
···
53
56
export class PlaceWispV2NS {
54
57
_server: Server
55
58
domain: PlaceWispV2DomainNS
59
59
+
site: PlaceWispV2SiteNS
56
60
57
61
constructor(server: Server) {
58
62
this._server = server
59
63
this.domain = new PlaceWispV2DomainNS(server)
64
64
+
this.site = new PlaceWispV2SiteNS(server)
60
65
}
61
66
}
62
67
···
67
72
this._server = server
68
73
}
69
74
75
75
+
addSite<A extends Auth = void>(
76
76
+
cfg: MethodConfigOrHandler<
77
77
+
A,
78
78
+
PlaceWispV2DomainAddSite.QueryParams,
79
79
+
PlaceWispV2DomainAddSite.HandlerInput,
80
80
+
PlaceWispV2DomainAddSite.HandlerOutput
81
81
+
>,
82
82
+
) {
83
83
+
const nsid = 'place.wisp.v2.domain.addSite' // @ts-ignore
84
84
+
return this._server.xrpc.method(nsid, cfg)
85
85
+
}
86
86
+
70
87
claimSubdomain<A extends Auth = void>(
71
88
cfg: MethodConfigOrHandler<
72
89
A,
···
127
144
return this._server.xrpc.method(nsid, cfg)
128
145
}
129
146
}
147
147
+
148
148
+
export class PlaceWispV2SiteNS {
149
149
+
_server: Server
150
150
+
151
151
+
constructor(server: Server) {
152
152
+
this._server = server
153
153
+
}
154
154
+
155
155
+
delete<A extends Auth = void>(
156
156
+
cfg: MethodConfigOrHandler<
157
157
+
A,
158
158
+
PlaceWispV2SiteDelete.QueryParams,
159
159
+
PlaceWispV2SiteDelete.HandlerInput,
160
160
+
PlaceWispV2SiteDelete.HandlerOutput
161
161
+
>,
162
162
+
) {
163
163
+
const nsid = 'place.wisp.v2.site.delete' // @ts-ignore
164
164
+
return this._server.xrpc.method(nsid, cfg)
165
165
+
}
166
166
+
167
167
+
getList<A extends Auth = void>(
168
168
+
cfg: MethodConfigOrHandler<
169
169
+
A,
170
170
+
PlaceWispV2SiteGetList.QueryParams,
171
171
+
PlaceWispV2SiteGetList.HandlerInput,
172
172
+
PlaceWispV2SiteGetList.HandlerOutput
173
173
+
>,
174
174
+
) {
175
175
+
const nsid = 'place.wisp.v2.site.getList' // @ts-ignore
176
176
+
return this._server.xrpc.method(nsid, cfg)
177
177
+
}
178
178
+
}
···
10
10
import { type $Typed, is$typed, maybe$typed } from './util.js'
11
11
12
12
export const schemaDict = {
13
13
+
PlaceWispV2DomainAddSite: {
14
14
+
lexicon: 1,
15
15
+
id: 'place.wisp.v2.domain.addSite',
16
16
+
defs: {
17
17
+
main: {
18
18
+
type: 'procedure',
19
19
+
description: 'Map an owned domain to one owned site record.',
20
20
+
input: {
21
21
+
encoding: 'application/json',
22
22
+
schema: {
23
23
+
type: 'object',
24
24
+
required: ['domain', 'siteRkey'],
25
25
+
properties: {
26
26
+
domain: {
27
27
+
type: 'string',
28
28
+
description: 'Fully-qualified domain to map.',
29
29
+
minLength: 3,
30
30
+
maxLength: 253,
31
31
+
},
32
32
+
siteRkey: {
33
33
+
type: 'string',
34
34
+
format: 'record-key',
35
35
+
description:
36
36
+
'Owned place.wisp.fs record key to map this domain to.',
37
37
+
},
38
38
+
},
39
39
+
},
40
40
+
},
41
41
+
output: {
42
42
+
encoding: 'application/json',
43
43
+
schema: {
44
44
+
type: 'object',
45
45
+
required: ['domain', 'kind', 'status', 'siteRkey', 'mapped'],
46
46
+
properties: {
47
47
+
domain: {
48
48
+
type: 'string',
49
49
+
},
50
50
+
kind: {
51
51
+
type: 'string',
52
52
+
enum: ['wisp', 'custom'],
53
53
+
},
54
54
+
status: {
55
55
+
type: 'string',
56
56
+
enum: ['pendingVerification', 'verified'],
57
57
+
},
58
58
+
siteRkey: {
59
59
+
type: 'string',
60
60
+
format: 'record-key',
61
61
+
},
62
62
+
mapped: {
63
63
+
type: 'boolean',
64
64
+
const: true,
65
65
+
},
66
66
+
},
67
67
+
},
68
68
+
},
69
69
+
errors: [
70
70
+
{
71
71
+
name: 'AuthenticationRequired',
72
72
+
},
73
73
+
{
74
74
+
name: 'InvalidDomain',
75
75
+
},
76
76
+
{
77
77
+
name: 'InvalidRequest',
78
78
+
},
79
79
+
{
80
80
+
name: 'NotFound',
81
81
+
},
82
82
+
],
83
83
+
},
84
84
+
},
85
85
+
},
13
86
PlaceWispV2DomainClaimSubdomain: {
14
87
lexicon: 1,
15
88
id: 'place.wisp.v2.domain.claimSubdomain',
···
678
751
},
679
752
},
680
753
},
754
754
+
PlaceWispV2SiteDelete: {
755
755
+
lexicon: 1,
756
756
+
id: 'place.wisp.v2.site.delete',
757
757
+
defs: {
758
758
+
main: {
759
759
+
type: 'procedure',
760
760
+
description:
761
761
+
'Delete one owned site metadata entry and unmap any domains pointing to it.',
762
762
+
input: {
763
763
+
encoding: 'application/json',
764
764
+
schema: {
765
765
+
type: 'object',
766
766
+
required: ['siteRkey'],
767
767
+
properties: {
768
768
+
siteRkey: {
769
769
+
type: 'string',
770
770
+
format: 'record-key',
771
771
+
description:
772
772
+
'Owned place.wisp.fs record key to delete from wisp metadata.',
773
773
+
},
774
774
+
},
775
775
+
},
776
776
+
},
777
777
+
output: {
778
778
+
encoding: 'application/json',
779
779
+
schema: {
780
780
+
type: 'object',
781
781
+
required: ['siteRkey', 'deleted', 'unmappedDomains'],
782
782
+
properties: {
783
783
+
siteRkey: {
784
784
+
type: 'string',
785
785
+
format: 'record-key',
786
786
+
},
787
787
+
deleted: {
788
788
+
type: 'boolean',
789
789
+
const: true,
790
790
+
},
791
791
+
unmappedDomains: {
792
792
+
type: 'array',
793
793
+
description:
794
794
+
'Domains that were detached from this site before deletion.',
795
795
+
items: {
796
796
+
type: 'ref',
797
797
+
ref: 'lex:place.wisp.v2.site.delete#unmappedDomain',
798
798
+
},
799
799
+
},
800
800
+
},
801
801
+
},
802
802
+
},
803
803
+
errors: [
804
804
+
{
805
805
+
name: 'AuthenticationRequired',
806
806
+
},
807
807
+
{
808
808
+
name: 'InvalidRequest',
809
809
+
},
810
810
+
{
811
811
+
name: 'NotFound',
812
812
+
},
813
813
+
],
814
814
+
},
815
815
+
unmappedDomain: {
816
816
+
type: 'object',
817
817
+
required: ['domain', 'kind', 'status'],
818
818
+
properties: {
819
819
+
domain: {
820
820
+
type: 'string',
821
821
+
minLength: 3,
822
822
+
maxLength: 253,
823
823
+
},
824
824
+
kind: {
825
825
+
type: 'string',
826
826
+
enum: ['wisp', 'custom'],
827
827
+
},
828
828
+
status: {
829
829
+
type: 'string',
830
830
+
enum: ['pendingVerification', 'verified'],
831
831
+
},
832
832
+
},
833
833
+
},
834
834
+
},
835
835
+
},
836
836
+
PlaceWispV2SiteGetList: {
837
837
+
lexicon: 1,
838
838
+
id: 'place.wisp.v2.site.getList',
839
839
+
defs: {
840
840
+
main: {
841
841
+
type: 'query',
842
842
+
description:
843
843
+
'List owned sites and the domains currently mapped to each site.',
844
844
+
output: {
845
845
+
encoding: 'application/json',
846
846
+
schema: {
847
847
+
type: 'object',
848
848
+
required: ['sites'],
849
849
+
properties: {
850
850
+
sites: {
851
851
+
type: 'array',
852
852
+
items: {
853
853
+
type: 'ref',
854
854
+
ref: 'lex:place.wisp.v2.site.getList#siteSummary',
855
855
+
},
856
856
+
},
857
857
+
},
858
858
+
},
859
859
+
},
860
860
+
errors: [
861
861
+
{
862
862
+
name: 'AuthenticationRequired',
863
863
+
},
864
864
+
],
865
865
+
},
866
866
+
siteSummary: {
867
867
+
type: 'object',
868
868
+
required: ['siteRkey', 'domains'],
869
869
+
properties: {
870
870
+
siteRkey: {
871
871
+
type: 'string',
872
872
+
format: 'record-key',
873
873
+
},
874
874
+
displayName: {
875
875
+
type: 'string',
876
876
+
maxLength: 200,
877
877
+
},
878
878
+
createdAt: {
879
879
+
type: 'string',
880
880
+
format: 'datetime',
881
881
+
},
882
882
+
updatedAt: {
883
883
+
type: 'string',
884
884
+
format: 'datetime',
885
885
+
},
886
886
+
domains: {
887
887
+
type: 'array',
888
888
+
items: {
889
889
+
type: 'ref',
890
890
+
ref: 'lex:place.wisp.v2.site.getList#siteDomain',
891
891
+
},
892
892
+
},
893
893
+
},
894
894
+
},
895
895
+
siteDomain: {
896
896
+
type: 'object',
897
897
+
required: ['domain', 'kind', 'status', 'verified'],
898
898
+
properties: {
899
899
+
domain: {
900
900
+
type: 'string',
901
901
+
minLength: 3,
902
902
+
maxLength: 253,
903
903
+
},
904
904
+
kind: {
905
905
+
type: 'string',
906
906
+
enum: ['wisp', 'custom'],
907
907
+
},
908
908
+
status: {
909
909
+
type: 'string',
910
910
+
enum: ['pendingVerification', 'verified'],
911
911
+
},
912
912
+
verified: {
913
913
+
type: 'boolean',
914
914
+
},
915
915
+
},
916
916
+
},
917
917
+
},
918
918
+
},
681
919
PlaceWispSubfs: {
682
920
lexicon: 1,
683
921
id: 'place.wisp.subfs',
···
823
1061
}
824
1062
825
1063
export const ids = {
1064
1064
+
PlaceWispV2DomainAddSite: 'place.wisp.v2.domain.addSite',
826
1065
PlaceWispV2DomainClaimSubdomain: 'place.wisp.v2.domain.claimSubdomain',
827
1066
PlaceWispV2DomainClaim: 'place.wisp.v2.domain.claim',
828
1067
PlaceWispV2DomainDelete: 'place.wisp.v2.domain.delete',
···
831
1070
PlaceWispV2Domains: 'place.wisp.v2.domains',
832
1071
PlaceWispFs: 'place.wisp.fs',
833
1072
PlaceWispSettings: 'place.wisp.settings',
1073
1073
+
PlaceWispV2SiteDelete: 'place.wisp.v2.site.delete',
1074
1074
+
PlaceWispV2SiteGetList: 'place.wisp.v2.site.getList',
834
1075
PlaceWispSubfs: 'place.wisp.subfs',
835
1076
} as const
···
1
1
+
/**
2
2
+
* GENERATED CODE - DO NOT MODIFY
3
3
+
*/
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
+
import { CID } from 'multiformats/cid'
6
6
+
import { validate as _validate } from '../../../../../lexicons'
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../../../util'
12
12
+
13
13
+
const is$typed = _is$typed,
14
14
+
validate = _validate
15
15
+
const id = 'place.wisp.v2.domain.addSite'
16
16
+
17
17
+
export type QueryParams = {}
18
18
+
19
19
+
export interface InputSchema {
20
20
+
/** Fully-qualified domain to map. */
21
21
+
domain: string
22
22
+
/** Owned place.wisp.fs record key to map this domain to. */
23
23
+
siteRkey: string
24
24
+
}
25
25
+
26
26
+
export interface OutputSchema {
27
27
+
domain: string
28
28
+
kind: 'wisp' | 'custom'
29
29
+
status: 'pendingVerification' | 'verified'
30
30
+
siteRkey: string
31
31
+
mapped: true
32
32
+
}
33
33
+
34
34
+
export interface HandlerInput {
35
35
+
encoding: 'application/json'
36
36
+
body: InputSchema
37
37
+
}
38
38
+
39
39
+
export interface HandlerSuccess {
40
40
+
encoding: 'application/json'
41
41
+
body: OutputSchema
42
42
+
headers?: { [key: string]: string }
43
43
+
}
44
44
+
45
45
+
export interface HandlerError {
46
46
+
status: number
47
47
+
message?: string
48
48
+
error?:
49
49
+
| 'AuthenticationRequired'
50
50
+
| 'InvalidDomain'
51
51
+
| 'InvalidRequest'
52
52
+
| 'NotFound'
53
53
+
}
54
54
+
55
55
+
export type HandlerOutput = HandlerError | HandlerSuccess
···
1
1
+
/**
2
2
+
* GENERATED CODE - DO NOT MODIFY
3
3
+
*/
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
+
import { CID } from 'multiformats/cid'
6
6
+
import { validate as _validate } from '../../../../../lexicons'
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../../../util'
12
12
+
13
13
+
const is$typed = _is$typed,
14
14
+
validate = _validate
15
15
+
const id = 'place.wisp.v2.site.delete'
16
16
+
17
17
+
export type QueryParams = {}
18
18
+
19
19
+
export interface InputSchema {
20
20
+
/** Owned place.wisp.fs record key to delete from wisp metadata. */
21
21
+
siteRkey: string
22
22
+
}
23
23
+
24
24
+
export interface OutputSchema {
25
25
+
siteRkey: string
26
26
+
deleted: true
27
27
+
/** Domains that were detached from this site before deletion. */
28
28
+
unmappedDomains: UnmappedDomain[]
29
29
+
}
30
30
+
31
31
+
export interface HandlerInput {
32
32
+
encoding: 'application/json'
33
33
+
body: InputSchema
34
34
+
}
35
35
+
36
36
+
export interface HandlerSuccess {
37
37
+
encoding: 'application/json'
38
38
+
body: OutputSchema
39
39
+
headers?: { [key: string]: string }
40
40
+
}
41
41
+
42
42
+
export interface HandlerError {
43
43
+
status: number
44
44
+
message?: string
45
45
+
error?: 'AuthenticationRequired' | 'InvalidRequest' | 'NotFound'
46
46
+
}
47
47
+
48
48
+
export type HandlerOutput = HandlerError | HandlerSuccess
49
49
+
50
50
+
export interface UnmappedDomain {
51
51
+
$type?: 'place.wisp.v2.site.delete#unmappedDomain'
52
52
+
domain: string
53
53
+
kind: 'wisp' | 'custom'
54
54
+
status: 'pendingVerification' | 'verified'
55
55
+
}
56
56
+
57
57
+
const hashUnmappedDomain = 'unmappedDomain'
58
58
+
59
59
+
export function isUnmappedDomain<V>(v: V) {
60
60
+
return is$typed(v, id, hashUnmappedDomain)
61
61
+
}
62
62
+
63
63
+
export function validateUnmappedDomain<V>(v: V) {
64
64
+
return validate<UnmappedDomain & V>(v, id, hashUnmappedDomain)
65
65
+
}
···
1
1
+
/**
2
2
+
* GENERATED CODE - DO NOT MODIFY
3
3
+
*/
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
+
import { CID } from 'multiformats/cid'
6
6
+
import { validate as _validate } from '../../../../../lexicons'
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../../../util'
12
12
+
13
13
+
const is$typed = _is$typed,
14
14
+
validate = _validate
15
15
+
const id = 'place.wisp.v2.site.getList'
16
16
+
17
17
+
export type QueryParams = {}
18
18
+
export type InputSchema = undefined
19
19
+
20
20
+
export interface OutputSchema {
21
21
+
sites: SiteSummary[]
22
22
+
}
23
23
+
24
24
+
export type HandlerInput = void
25
25
+
26
26
+
export interface HandlerSuccess {
27
27
+
encoding: 'application/json'
28
28
+
body: OutputSchema
29
29
+
headers?: { [key: string]: string }
30
30
+
}
31
31
+
32
32
+
export interface HandlerError {
33
33
+
status: number
34
34
+
message?: string
35
35
+
error?: 'AuthenticationRequired'
36
36
+
}
37
37
+
38
38
+
export type HandlerOutput = HandlerError | HandlerSuccess
39
39
+
40
40
+
export interface SiteSummary {
41
41
+
$type?: 'place.wisp.v2.site.getList#siteSummary'
42
42
+
siteRkey: string
43
43
+
displayName?: string
44
44
+
createdAt?: string
45
45
+
updatedAt?: string
46
46
+
domains: SiteDomain[]
47
47
+
}
48
48
+
49
49
+
const hashSiteSummary = 'siteSummary'
50
50
+
51
51
+
export function isSiteSummary<V>(v: V) {
52
52
+
return is$typed(v, id, hashSiteSummary)
53
53
+
}
54
54
+
55
55
+
export function validateSiteSummary<V>(v: V) {
56
56
+
return validate<SiteSummary & V>(v, id, hashSiteSummary)
57
57
+
}
58
58
+
59
59
+
export interface SiteDomain {
60
60
+
$type?: 'place.wisp.v2.site.getList#siteDomain'
61
61
+
domain: string
62
62
+
kind: 'wisp' | 'custom'
63
63
+
status: 'pendingVerification' | 'verified'
64
64
+
verified: boolean
65
65
+
}
66
66
+
67
67
+
const hashSiteDomain = 'siteDomain'
68
68
+
69
69
+
export function isSiteDomain<V>(v: V) {
70
70
+
return is$typed(v, id, hashSiteDomain)
71
71
+
}
72
72
+
73
73
+
export function validateSiteDomain<V>(v: V) {
74
74
+
return validate<SiteDomain & V>(v, id, hashSiteDomain)
75
75
+
}
···
14
14
cd "$ROOT_DIR/packages/@wispplace/lexicons"
15
15
eval "$AUTO_ACCEPT bun run codegen"
16
16
17
17
-
echo "=== Generating atcute lexicons ==="
18
18
-
eval "$AUTO_ACCEPT bun run codegen:atcute"
17
17
+
if [[ ! -f "$ROOT_DIR/packages/@wispplace/lexicons/src/atcute/lexicons/index.ts" ]]; then
18
18
+
echo "ERROR: missing generated atcute lexicons index at packages/@wispplace/lexicons/src/atcute/lexicons/index.ts" >&2
19
19
+
exit 1
20
20
+
fi
19
21
20
22
echo "=== Done ==="