@alteran/astro 0.1.14 → 0.3.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/README.md +25 -0
- package/index.js +2 -4
- package/migrations/0006_adorable_spectrum.sql +11 -0
- package/migrations/meta/0006_snapshot.json +429 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +6 -3
- package/src/db/account.ts +145 -0
- package/src/db/dal.ts +27 -9
- package/src/db/repo.ts +9 -8
- package/src/db/schema.ts +29 -11
- package/src/lib/actor.ts +133 -0
- package/src/lib/appview.ts +508 -0
- package/src/lib/auth.ts +22 -2
- package/src/lib/blob-refs.ts +9 -13
- package/src/lib/chat.ts +238 -0
- package/src/lib/config.ts +15 -7
- package/src/lib/feed.ts +165 -0
- package/src/lib/jwt.ts +135 -44
- package/src/lib/labeler.ts +91 -0
- package/src/lib/mst/blockstore.ts +98 -14
- package/src/lib/password.ts +40 -0
- package/src/lib/preferences.ts +73 -0
- package/src/lib/relay.ts +101 -0
- package/src/lib/secrets.ts +3 -0
- package/src/lib/session-tokens.ts +202 -0
- package/src/lib/token-cleanup.ts +3 -12
- package/src/lib/util.ts +17 -2
- package/src/middleware.ts +20 -21
- package/src/pages/.well-known/did.json.ts +45 -32
- package/src/pages/xrpc/app.bsky.actor.getPreferences.ts +23 -0
- package/src/pages/xrpc/app.bsky.actor.getProfile.ts +34 -0
- package/src/pages/xrpc/app.bsky.actor.getProfiles.ts +42 -0
- package/src/pages/xrpc/app.bsky.actor.putPreferences.ts +36 -0
- package/src/pages/xrpc/app.bsky.feed.getAuthorFeed.ts +42 -0
- package/src/pages/xrpc/app.bsky.feed.getPostThread.ts +37 -0
- package/src/pages/xrpc/app.bsky.feed.getPosts.ts +26 -0
- package/src/pages/xrpc/app.bsky.feed.getTimeline.ts +35 -0
- package/src/pages/xrpc/app.bsky.graph.getFollowers.ts +29 -0
- package/src/pages/xrpc/app.bsky.graph.getFollows.ts +29 -0
- package/src/pages/xrpc/app.bsky.labeler.getServices.ts +29 -0
- package/src/pages/xrpc/app.bsky.notification.getUnreadCount.ts +20 -0
- package/src/pages/xrpc/app.bsky.notification.listNotifications.ts +27 -0
- package/src/pages/xrpc/app.bsky.unspecced.getAgeAssuranceState.ts +19 -0
- package/src/pages/xrpc/app.bsky.unspecced.getConfig.ts +15 -0
- package/src/pages/xrpc/chat.bsky.convo.getLog.ts +26 -0
- package/src/pages/xrpc/chat.bsky.convo.listConvos.ts +37 -0
- package/src/pages/xrpc/com.atproto.identity.getRecommendedDidCredentials.ts +64 -66
- package/src/pages/xrpc/com.atproto.identity.requestPlcOperationSignature.ts +24 -0
- package/src/pages/xrpc/com.atproto.identity.signPlcOperation.ts +127 -0
- package/src/pages/xrpc/com.atproto.identity.submitPlcOperation.ts +91 -0
- package/src/pages/xrpc/com.atproto.repo.uploadBlob.ts +6 -2
- package/src/pages/xrpc/com.atproto.server.createSession.ts +36 -8
- package/src/pages/xrpc/com.atproto.server.describeServer.ts +37 -4
- package/src/pages/xrpc/com.atproto.server.getServiceAuth.ts +64 -0
- package/src/pages/xrpc/com.atproto.server.refreshSession.ts +55 -32
- package/src/services/repo-manager.ts +15 -6
- package/src/worker/runtime.ts +9 -0
- package/types/env.d.ts +9 -0
- package/src/pages/xrpc/com.atproto.repo.importRepo.ts +0 -142
- package/src/pages/xrpc/com.atproto.server.activateAccount.ts +0 -53
- package/src/pages/xrpc/com.atproto.server.createAccount.ts +0 -99
- package/src/pages/xrpc/com.atproto.server.deactivateAccount.ts +0 -53
package/src/lib/util.ts
CHANGED
|
@@ -38,10 +38,25 @@ export function bearerToken(request: Request): string | null {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export function isAllowedMime(env: any, mime: string): boolean {
|
|
41
|
-
const def = [
|
|
41
|
+
const def = [
|
|
42
|
+
// Images
|
|
43
|
+
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif',
|
|
44
|
+
// Videos
|
|
45
|
+
'video/mp4', 'video/mpeg', 'video/webm', 'video/quicktime',
|
|
46
|
+
// Audio
|
|
47
|
+
'audio/mpeg', 'audio/mp4', 'audio/wav', 'audio/webm',
|
|
48
|
+
// JSON (for some Bluesky data)
|
|
49
|
+
'application/json',
|
|
50
|
+
// Generic fallback
|
|
51
|
+
'application/octet-stream'
|
|
52
|
+
];
|
|
42
53
|
const raw = (env.PDS_ALLOWED_MIME as string | undefined) ?? def.join(',');
|
|
43
54
|
const set = new Set(raw.split(',').map((s) => s.trim()).filter(Boolean));
|
|
44
|
-
|
|
55
|
+
|
|
56
|
+
// Extract base MIME type (remove charset and other parameters)
|
|
57
|
+
const baseMime = mime.toLowerCase().split(';')[0].trim();
|
|
58
|
+
|
|
59
|
+
return set.has(baseMime);
|
|
45
60
|
}
|
|
46
61
|
|
|
47
62
|
export function randomRkey(): string {
|
package/src/middleware.ts
CHANGED
|
@@ -1,36 +1,35 @@
|
|
|
1
1
|
import { defineMiddleware, sequence } from 'astro:middleware';
|
|
2
2
|
|
|
3
3
|
const cors = defineMiddleware(async ({ locals, request }, next) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// In production, never allow wildcard - require explicit origins
|
|
9
|
-
const isProduction = env.PDS_HOSTNAME && !env.PDS_HOSTNAME.includes('localhost');
|
|
10
|
-
const allowWildcard = !isProduction && corsOrigins.includes('*');
|
|
11
|
-
|
|
12
|
-
// Check if origin is in allowlist
|
|
13
|
-
const isAllowed = allowWildcard || corsOrigins.includes(origin);
|
|
4
|
+
// Match atproto CORS implementation: use wildcard for public endpoints
|
|
5
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
|
6
|
+
// For requests without credentials, "*" can be specified as a wildcard
|
|
7
|
+
// This is safer than reflecting the request origin and matches atproto standard
|
|
14
8
|
|
|
15
9
|
if (request.method === 'OPTIONS') {
|
|
16
|
-
|
|
17
|
-
return new Response('CORS origin not allowed', { status: 403 });
|
|
18
|
-
}
|
|
19
|
-
|
|
10
|
+
// CORS preflight - match atproto PDS implementation
|
|
20
11
|
const headers = new Headers({
|
|
21
|
-
'Access-Control-Allow-Origin':
|
|
22
|
-
|
|
23
|
-
'Access-Control-Allow-
|
|
24
|
-
|
|
12
|
+
'Access-Control-Allow-Origin': '*',
|
|
13
|
+
// Use wildcard for methods (atproto standard)
|
|
14
|
+
'Access-Control-Allow-Methods': '*',
|
|
15
|
+
// Use wildcard for headers to allow atproto-accept-labelers and other custom headers
|
|
16
|
+
'Access-Control-Allow-Headers': '*',
|
|
17
|
+
// Match atproto: 1 day max-age for CORS preflight cache
|
|
18
|
+
'Access-Control-Max-Age': '86400',
|
|
25
19
|
});
|
|
26
20
|
return new Response(null, { status: 204, headers });
|
|
27
21
|
}
|
|
28
22
|
|
|
29
23
|
const response = await next();
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
// Set CORS headers on all responses (atproto standard)
|
|
26
|
+
response.headers.set('Access-Control-Allow-Origin', '*');
|
|
27
|
+
|
|
28
|
+
// Expose DPoP-Nonce header for OAuth clients (atproto standard)
|
|
29
|
+
// This allows clients to read the DPoP-Nonce header from responses
|
|
30
|
+
const dpopNonce = response.headers.get('DPoP-Nonce');
|
|
31
|
+
if (dpopNonce) {
|
|
32
|
+
response.headers.set('Access-Control-Expose-Headers', 'DPoP-Nonce');
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
return response;
|
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
2
|
import { withCache, CACHE_CONFIGS } from '../../lib/cache';
|
|
3
3
|
import { base58btc } from 'multiformats/bases/base58';
|
|
4
|
+
import { resolveSecret } from '../../lib/secrets';
|
|
5
|
+
import { Secp256k1Keypair } from '@atproto/crypto';
|
|
6
|
+
import { formatMultikey } from '@atproto/crypto/dist/did';
|
|
4
7
|
|
|
5
8
|
export const prerender = false;
|
|
6
9
|
|
|
7
|
-
/**
|
|
8
|
-
* DID Document endpoint for did:web
|
|
9
|
-
*
|
|
10
|
-
* Returns a DID document with:
|
|
11
|
-
* - Service endpoints (PDS, firehose)
|
|
12
|
-
* - Verification methods (signing key)
|
|
13
|
-
*
|
|
14
|
-
* Spec: https://w3c-ccg.github.io/did-method-web/
|
|
15
|
-
*/
|
|
16
10
|
export async function GET({ locals, request }: APIContext) {
|
|
17
11
|
const { env } = locals.runtime;
|
|
18
12
|
|
|
@@ -23,39 +17,57 @@ export async function GET({ locals, request }: APIContext) {
|
|
|
23
17
|
const handle = env.PDS_HANDLE ?? 'user.example.com';
|
|
24
18
|
const hostname = env.PDS_HOSTNAME ?? new URL(request.url).hostname;
|
|
25
19
|
|
|
26
|
-
// Public repository signing key (raw 32-byte Ed25519) base64-encoded
|
|
27
|
-
const pubKeyB64: string | undefined = (env as any).REPO_SIGNING_PUBLIC_KEY;
|
|
28
20
|
let publicKeyMultibase: string | undefined;
|
|
29
|
-
|
|
21
|
+
|
|
22
|
+
const serviceKeyHex = await resolveSecret(env.PDS_SERVICE_SIGNING_KEY_HEX as any);
|
|
23
|
+
if (serviceKeyHex) {
|
|
30
24
|
try {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
const keypair = await Secp256k1Keypair.import(serviceKeyHex.trim());
|
|
26
|
+
publicKeyMultibase = formatMultikey(keypair.jwtAlg, keypair.publicKeyBytes());
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.warn('Failed to encode service signing key', error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!publicKeyMultibase) {
|
|
33
|
+
const repoPubKeyB64 = await resolveSecret((env as any).REPO_SIGNING_KEY_PUBLIC);
|
|
34
|
+
if (repoPubKeyB64) {
|
|
35
|
+
try {
|
|
36
|
+
const bin = atob(repoPubKeyB64.replace(/\s+/g, ''));
|
|
37
|
+
const raw = new Uint8Array(bin.length);
|
|
38
|
+
for (let i = 0; i < bin.length; i++) raw[i] = bin.charCodeAt(i);
|
|
39
|
+
if (raw.byteLength === 32) {
|
|
40
|
+
const prefixed = new Uint8Array(2 + raw.byteLength);
|
|
41
|
+
prefixed[0] = 0xed;
|
|
42
|
+
prefixed[1] = 0x01;
|
|
43
|
+
prefixed.set(raw, 2);
|
|
44
|
+
publicKeyMultibase = base58btc.encode(prefixed);
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn('Failed to encode repo signing key', error);
|
|
39
48
|
}
|
|
40
|
-
}
|
|
49
|
+
}
|
|
41
50
|
}
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
const verificationMethods = publicKeyMultibase
|
|
53
|
+
? [
|
|
54
|
+
{
|
|
55
|
+
id: `${did}#atproto`,
|
|
56
|
+
type: 'Multikey',
|
|
57
|
+
controller: did,
|
|
58
|
+
publicKeyMultibase,
|
|
59
|
+
},
|
|
60
|
+
]
|
|
61
|
+
: [];
|
|
62
|
+
|
|
44
63
|
const didDocument = {
|
|
45
64
|
'@context': [
|
|
46
65
|
'https://www.w3.org/ns/did/v1',
|
|
47
|
-
'https://w3id.org/security/
|
|
66
|
+
'https://w3id.org/security/multikey/v1',
|
|
48
67
|
],
|
|
49
68
|
id: did,
|
|
50
69
|
alsoKnownAs: [`at://${handle}`],
|
|
51
|
-
verificationMethod:
|
|
52
|
-
{
|
|
53
|
-
id: `${did}#atproto`,
|
|
54
|
-
type: 'Ed25519VerificationKey2020',
|
|
55
|
-
controller: did,
|
|
56
|
-
publicKeyMultibase,
|
|
57
|
-
},
|
|
58
|
-
] : [],
|
|
70
|
+
verificationMethod: verificationMethods,
|
|
59
71
|
service: [
|
|
60
72
|
{
|
|
61
73
|
id: `${did}#atproto_pds`,
|
|
@@ -71,6 +83,7 @@ export async function GET({ locals, request }: APIContext) {
|
|
|
71
83
|
},
|
|
72
84
|
});
|
|
73
85
|
},
|
|
74
|
-
CACHE_CONFIGS.DID_DOCUMENT
|
|
86
|
+
CACHE_CONFIGS.DID_DOCUMENT,
|
|
75
87
|
);
|
|
76
88
|
}
|
|
89
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
4
|
+
import { getActorPreferences } from '../../lib/preferences';
|
|
5
|
+
|
|
6
|
+
export const prerender = false;
|
|
7
|
+
|
|
8
|
+
export async function GET({ locals, request }: APIContext) {
|
|
9
|
+
const { env } = locals.runtime;
|
|
10
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
11
|
+
|
|
12
|
+
return proxyAppView({
|
|
13
|
+
request,
|
|
14
|
+
env,
|
|
15
|
+
lxm: 'app.bsky.actor.getPreferences',
|
|
16
|
+
fallback: async () => {
|
|
17
|
+
const { preferences } = await getActorPreferences(env);
|
|
18
|
+
return new Response(JSON.stringify({ preferences }), {
|
|
19
|
+
headers: { 'Content-Type': 'application/json' },
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import { buildProfileViewDetailed, getPrimaryActor, matchesPrimaryActor } from '../../lib/actor';
|
|
4
|
+
import { countPosts } from '../../lib/feed';
|
|
5
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
6
|
+
|
|
7
|
+
export const prerender = false;
|
|
8
|
+
|
|
9
|
+
export async function GET({ locals, request }: APIContext) {
|
|
10
|
+
const { env } = locals.runtime;
|
|
11
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
12
|
+
|
|
13
|
+
return proxyAppView({
|
|
14
|
+
request,
|
|
15
|
+
env,
|
|
16
|
+
lxm: 'app.bsky.actor.getProfile',
|
|
17
|
+
fallback: async () => {
|
|
18
|
+
const url = new URL(request.url);
|
|
19
|
+
const identifier = url.searchParams.get('actor');
|
|
20
|
+
const actor = await getPrimaryActor(env);
|
|
21
|
+
if (!matchesPrimaryActor(identifier, actor)) {
|
|
22
|
+
return new Response(JSON.stringify({ error: 'ProfileNotFound' }), { status: 404 });
|
|
23
|
+
}
|
|
24
|
+
const profile = buildProfileViewDetailed(actor, {
|
|
25
|
+
followers: 0,
|
|
26
|
+
follows: 0,
|
|
27
|
+
posts: await countPosts(env),
|
|
28
|
+
});
|
|
29
|
+
return new Response(JSON.stringify(profile), {
|
|
30
|
+
headers: { 'Content-Type': 'application/json' },
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import {
|
|
4
|
+
buildProfileViewDetailed,
|
|
5
|
+
getPrimaryActor,
|
|
6
|
+
matchesPrimaryActor,
|
|
7
|
+
} from '../../lib/actor';
|
|
8
|
+
import { countPosts } from '../../lib/feed';
|
|
9
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
10
|
+
|
|
11
|
+
export const prerender = false;
|
|
12
|
+
|
|
13
|
+
export async function GET({ locals, request }: APIContext) {
|
|
14
|
+
const { env } = locals.runtime;
|
|
15
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
16
|
+
|
|
17
|
+
return proxyAppView({
|
|
18
|
+
request,
|
|
19
|
+
env,
|
|
20
|
+
lxm: 'app.bsky.actor.getProfiles',
|
|
21
|
+
fallback: async () => {
|
|
22
|
+
const url = new URL(request.url);
|
|
23
|
+
const actors = url.searchParams.getAll('actors');
|
|
24
|
+
const actor = await getPrimaryActor(env);
|
|
25
|
+
const posts = await countPosts(env);
|
|
26
|
+
|
|
27
|
+
const profiles = actors
|
|
28
|
+
.filter((identifier) => matchesPrimaryActor(identifier, actor))
|
|
29
|
+
.map(() =>
|
|
30
|
+
buildProfileViewDetailed(actor, {
|
|
31
|
+
followers: 0,
|
|
32
|
+
follows: 0,
|
|
33
|
+
posts,
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return new Response(JSON.stringify({ profiles }), {
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
4
|
+
import { readJsonBounded } from '../../lib/util';
|
|
5
|
+
import { setActorPreferences } from '../../lib/preferences';
|
|
6
|
+
|
|
7
|
+
export const prerender = false;
|
|
8
|
+
|
|
9
|
+
export async function POST({ locals, request }: APIContext) {
|
|
10
|
+
const { env } = locals.runtime;
|
|
11
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
12
|
+
|
|
13
|
+
return proxyAppView({
|
|
14
|
+
request,
|
|
15
|
+
env,
|
|
16
|
+
lxm: 'app.bsky.actor.putPreferences',
|
|
17
|
+
fallback: async () => {
|
|
18
|
+
let body: any;
|
|
19
|
+
try {
|
|
20
|
+
body = await readJsonBounded(env, request);
|
|
21
|
+
} catch (err: any) {
|
|
22
|
+
if (err?.code === 'PayloadTooLarge') {
|
|
23
|
+
return new Response(JSON.stringify({ error: 'PayloadTooLarge' }), { status: 413 });
|
|
24
|
+
}
|
|
25
|
+
return new Response(JSON.stringify({ error: 'BadRequest' }), { status: 400 });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const preferences = Array.isArray(body?.preferences) ? body.preferences : [];
|
|
29
|
+
await setActorPreferences(env, preferences);
|
|
30
|
+
|
|
31
|
+
return new Response(JSON.stringify({}), {
|
|
32
|
+
headers: { 'Content-Type': 'application/json' },
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import { matchesPrimaryActor, getPrimaryActor } from '../../lib/actor';
|
|
4
|
+
import { buildFeedViewPosts, listPosts } from '../../lib/feed';
|
|
5
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
6
|
+
|
|
7
|
+
export const prerender = false;
|
|
8
|
+
|
|
9
|
+
export async function GET({ locals, request }: APIContext) {
|
|
10
|
+
const { env } = locals.runtime;
|
|
11
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
12
|
+
|
|
13
|
+
return proxyAppView({
|
|
14
|
+
request,
|
|
15
|
+
env,
|
|
16
|
+
lxm: 'app.bsky.feed.getAuthorFeed',
|
|
17
|
+
fallback: async () => {
|
|
18
|
+
const url = new URL(request.url);
|
|
19
|
+
const identifier = url.searchParams.get('actor');
|
|
20
|
+
const actor = await getPrimaryActor(env);
|
|
21
|
+
if (!matchesPrimaryActor(identifier, actor)) {
|
|
22
|
+
return new Response(JSON.stringify({ error: 'ActorNotFound' }), { status: 404 });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const limitParam = Number.parseInt(url.searchParams.get('limit') ?? '', 10);
|
|
26
|
+
const limitInput = Number.isFinite(limitParam) ? limitParam : 50;
|
|
27
|
+
const limit = Math.max(1, Math.min(limitInput, 100));
|
|
28
|
+
const cursor = url.searchParams.get('cursor') ?? undefined;
|
|
29
|
+
|
|
30
|
+
const posts = await listPosts(env, limit, cursor);
|
|
31
|
+
const feed = await buildFeedViewPosts(env, posts);
|
|
32
|
+
const nextCursor = posts.length === limit ? String(posts[posts.length - 1].rowid) : undefined;
|
|
33
|
+
|
|
34
|
+
const payload: Record<string, unknown> = { feed };
|
|
35
|
+
if (nextCursor) payload.cursor = nextCursor;
|
|
36
|
+
|
|
37
|
+
return new Response(JSON.stringify(payload), {
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import { buildThreadView, getPostByUri } from '../../lib/feed';
|
|
4
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
5
|
+
|
|
6
|
+
export const prerender = false;
|
|
7
|
+
|
|
8
|
+
export async function GET({ locals, request }: APIContext) {
|
|
9
|
+
const { env } = locals.runtime;
|
|
10
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
11
|
+
|
|
12
|
+
return proxyAppView({
|
|
13
|
+
request,
|
|
14
|
+
env,
|
|
15
|
+
lxm: 'app.bsky.feed.getPostThread',
|
|
16
|
+
fallback: async () => {
|
|
17
|
+
const url = new URL(request.url);
|
|
18
|
+
const uri = url.searchParams.get('uri');
|
|
19
|
+
if (!uri) {
|
|
20
|
+
return new Response(
|
|
21
|
+
JSON.stringify({ error: 'BadRequest', message: 'uri parameter required' }),
|
|
22
|
+
{ status: 400 },
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const post = await getPostByUri(env, uri);
|
|
27
|
+
if (!post) {
|
|
28
|
+
return new Response(JSON.stringify({ error: 'NotFound' }), { status: 404 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const thread = await buildThreadView(env, post);
|
|
32
|
+
return new Response(JSON.stringify({ thread }), {
|
|
33
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import { buildPostViews, getPostsByUris } from '../../lib/feed';
|
|
4
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
5
|
+
|
|
6
|
+
export const prerender = false;
|
|
7
|
+
|
|
8
|
+
export async function GET({ locals, request }: APIContext) {
|
|
9
|
+
const { env } = locals.runtime;
|
|
10
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
11
|
+
|
|
12
|
+
return proxyAppView({
|
|
13
|
+
request,
|
|
14
|
+
env,
|
|
15
|
+
lxm: 'app.bsky.feed.getPosts',
|
|
16
|
+
fallback: async () => {
|
|
17
|
+
const url = new URL(request.url);
|
|
18
|
+
const uris = url.searchParams.getAll('uris').filter(Boolean);
|
|
19
|
+
const posts = await getPostsByUris(env, uris.slice(0, 25));
|
|
20
|
+
const views = await buildPostViews(env, posts);
|
|
21
|
+
return new Response(JSON.stringify({ posts: views }), {
|
|
22
|
+
headers: { 'Content-Type': 'application/json' },
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import { buildFeedViewPosts, listPosts } from '../../lib/feed';
|
|
4
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
5
|
+
|
|
6
|
+
export const prerender = false;
|
|
7
|
+
|
|
8
|
+
export async function GET({ locals, request }: APIContext) {
|
|
9
|
+
const { env } = locals.runtime;
|
|
10
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
11
|
+
|
|
12
|
+
return proxyAppView({
|
|
13
|
+
request,
|
|
14
|
+
env,
|
|
15
|
+
lxm: 'app.bsky.feed.getTimeline',
|
|
16
|
+
fallback: async () => {
|
|
17
|
+
const url = new URL(request.url);
|
|
18
|
+
const cursor = url.searchParams.get('cursor') ?? undefined;
|
|
19
|
+
const limitParam = Number.parseInt(url.searchParams.get('limit') ?? '', 10);
|
|
20
|
+
const limitInput = Number.isFinite(limitParam) ? limitParam : 50;
|
|
21
|
+
const limit = Math.max(1, Math.min(limitInput, 100));
|
|
22
|
+
|
|
23
|
+
const posts = await listPosts(env, limit, cursor);
|
|
24
|
+
const feed = await buildFeedViewPosts(env, posts);
|
|
25
|
+
const nextCursor = posts.length === limit ? String(posts[posts.length - 1].rowid) : undefined;
|
|
26
|
+
|
|
27
|
+
const payload: Record<string, unknown> = { feed };
|
|
28
|
+
if (nextCursor) payload.cursor = nextCursor;
|
|
29
|
+
|
|
30
|
+
return new Response(JSON.stringify(payload), {
|
|
31
|
+
headers: { 'Content-Type': 'application/json' },
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import { buildProfileView, getPrimaryActor, matchesPrimaryActor } from '../../lib/actor';
|
|
4
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
5
|
+
|
|
6
|
+
export const prerender = false;
|
|
7
|
+
|
|
8
|
+
export async function GET({ locals, request }: APIContext) {
|
|
9
|
+
const { env } = locals.runtime;
|
|
10
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
11
|
+
|
|
12
|
+
return proxyAppView({
|
|
13
|
+
request,
|
|
14
|
+
env,
|
|
15
|
+
lxm: 'app.bsky.graph.getFollowers',
|
|
16
|
+
fallback: async () => {
|
|
17
|
+
const url = new URL(request.url);
|
|
18
|
+
const identifier = url.searchParams.get('actor');
|
|
19
|
+
const actor = await getPrimaryActor(env);
|
|
20
|
+
if (!matchesPrimaryActor(identifier, actor)) {
|
|
21
|
+
return new Response(JSON.stringify({ error: 'ActorNotFound' }), { status: 404 });
|
|
22
|
+
}
|
|
23
|
+
return new Response(
|
|
24
|
+
JSON.stringify({ subject: buildProfileView(actor), followers: [] }),
|
|
25
|
+
{ headers: { 'Content-Type': 'application/json' } },
|
|
26
|
+
);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import { buildProfileView, getPrimaryActor, matchesPrimaryActor } from '../../lib/actor';
|
|
4
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
5
|
+
|
|
6
|
+
export const prerender = false;
|
|
7
|
+
|
|
8
|
+
export async function GET({ locals, request }: APIContext) {
|
|
9
|
+
const { env } = locals.runtime;
|
|
10
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
11
|
+
|
|
12
|
+
return proxyAppView({
|
|
13
|
+
request,
|
|
14
|
+
env,
|
|
15
|
+
lxm: 'app.bsky.graph.getFollows',
|
|
16
|
+
fallback: async () => {
|
|
17
|
+
const url = new URL(request.url);
|
|
18
|
+
const identifier = url.searchParams.get('actor');
|
|
19
|
+
const actor = await getPrimaryActor(env);
|
|
20
|
+
if (!matchesPrimaryActor(identifier, actor)) {
|
|
21
|
+
return new Response(JSON.stringify({ error: 'ActorNotFound' }), { status: 404 });
|
|
22
|
+
}
|
|
23
|
+
return new Response(
|
|
24
|
+
JSON.stringify({ subject: buildProfileView(actor), follows: [] }),
|
|
25
|
+
{ headers: { 'Content-Type': 'application/json' } },
|
|
26
|
+
);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { getLabelerServiceViews } from '../../lib/labeler';
|
|
3
|
+
|
|
4
|
+
export const prerender = false;
|
|
5
|
+
|
|
6
|
+
export async function GET({ locals, request }: APIContext) {
|
|
7
|
+
const { env } = locals.runtime;
|
|
8
|
+
const url = new URL(request.url);
|
|
9
|
+
|
|
10
|
+
const didParams = url.searchParams.getAll('dids');
|
|
11
|
+
const dids = didParams
|
|
12
|
+
.flatMap((entry) => entry.split(',').map((did) => did.trim()))
|
|
13
|
+
.filter(Boolean);
|
|
14
|
+
|
|
15
|
+
if (dids.length === 0) {
|
|
16
|
+
return new Response(JSON.stringify({ error: 'BadRequest', message: 'dids parameter required' }), {
|
|
17
|
+
status: 400,
|
|
18
|
+
headers: { 'Content-Type': 'application/json' },
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const detailed = url.searchParams.get('detailed') === 'true';
|
|
23
|
+
|
|
24
|
+
const views = await getLabelerServiceViews(env, dids, { detailed });
|
|
25
|
+
|
|
26
|
+
return new Response(JSON.stringify({ views }), {
|
|
27
|
+
headers: { 'Content-Type': 'application/json' },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
4
|
+
|
|
5
|
+
export const prerender = false;
|
|
6
|
+
|
|
7
|
+
export async function GET({ locals, request }: APIContext) {
|
|
8
|
+
const { env } = locals.runtime;
|
|
9
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
10
|
+
|
|
11
|
+
return proxyAppView({
|
|
12
|
+
request,
|
|
13
|
+
env,
|
|
14
|
+
lxm: 'app.bsky.notification.getUnreadCount',
|
|
15
|
+
fallback: async () =>
|
|
16
|
+
new Response(JSON.stringify({ count: 0 }), {
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { proxyAppView } from '../../lib/appview';
|
|
3
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
4
|
+
|
|
5
|
+
export const prerender = false;
|
|
6
|
+
|
|
7
|
+
export async function GET({ locals, request }: APIContext) {
|
|
8
|
+
const { env } = locals.runtime;
|
|
9
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
10
|
+
|
|
11
|
+
return proxyAppView({
|
|
12
|
+
request,
|
|
13
|
+
env,
|
|
14
|
+
lxm: 'app.bsky.notification.listNotifications',
|
|
15
|
+
fallback: async () =>
|
|
16
|
+
new Response(
|
|
17
|
+
JSON.stringify({
|
|
18
|
+
notifications: [],
|
|
19
|
+
priority: false,
|
|
20
|
+
seenAt: new Date(0).toISOString(),
|
|
21
|
+
}),
|
|
22
|
+
{
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
},
|
|
25
|
+
),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { isAuthorized, unauthorized } from '../../lib/auth';
|
|
3
|
+
|
|
4
|
+
export const prerender = false;
|
|
5
|
+
|
|
6
|
+
export async function GET({ locals, request }: APIContext) {
|
|
7
|
+
const { env } = locals.runtime;
|
|
8
|
+
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
9
|
+
|
|
10
|
+
return new Response(
|
|
11
|
+
JSON.stringify({
|
|
12
|
+
status: 'unknown',
|
|
13
|
+
lastInitiatedAt: new Date(0).toISOString(),
|
|
14
|
+
}),
|
|
15
|
+
{
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const prerender = false;
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
return new Response(
|
|
7
|
+
JSON.stringify({
|
|
8
|
+
checkEmailConfirmed: false,
|
|
9
|
+
liveNow: [],
|
|
10
|
+
}),
|
|
11
|
+
{
|
|
12
|
+
headers: { 'Content-Type': 'application/json' },
|
|
13
|
+
},
|
|
14
|
+
);
|
|
15
|
+
}
|