@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/db/dal.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { getDb } from './client';
|
|
2
2
|
import { record, type NewRecordRow, blob_ref, blob_usage, blob_quota } from './schema';
|
|
3
3
|
import type { Env } from '../env';
|
|
4
|
-
import { eq, inArray, and } from 'drizzle-orm';
|
|
4
|
+
import { eq, inArray, and, sql } from 'drizzle-orm';
|
|
5
5
|
|
|
6
6
|
export async function putRecord(env: Env, row: NewRecordRow) {
|
|
7
7
|
const db = getDb(env);
|
|
8
|
-
await db.insert(record).values(row).onConflictDoUpdate({
|
|
8
|
+
await db.insert(record).values(row).onConflictDoUpdate({
|
|
9
|
+
target: record.uri,
|
|
10
|
+
set: {
|
|
11
|
+
cid: sql.raw(`excluded.${record.cid.name}`),
|
|
12
|
+
json: sql.raw(`excluded.${record.json.name}`)
|
|
13
|
+
}
|
|
14
|
+
});
|
|
9
15
|
}
|
|
10
16
|
|
|
11
17
|
export async function getRecord(env: Env, uri: string) {
|
|
@@ -35,7 +41,15 @@ export async function putBlobRef(env: Env, did: string, cid: string, key: string
|
|
|
35
41
|
await db
|
|
36
42
|
.insert(blob_ref)
|
|
37
43
|
.values({ did, cid, key, mime, size })
|
|
38
|
-
.onConflictDoUpdate({
|
|
44
|
+
.onConflictDoUpdate({
|
|
45
|
+
target: blob_ref.cid,
|
|
46
|
+
set: {
|
|
47
|
+
did: sql.raw(`excluded.${blob_ref.did.name}`),
|
|
48
|
+
key: sql.raw(`excluded.${blob_ref.key.name}`),
|
|
49
|
+
mime: sql.raw(`excluded.${blob_ref.mime.name}`),
|
|
50
|
+
size: sql.raw(`excluded.${blob_ref.size.name}`)
|
|
51
|
+
}
|
|
52
|
+
});
|
|
39
53
|
}
|
|
40
54
|
|
|
41
55
|
export async function setRecordBlobUsage(env: Env, uri: string, keys: string[]) {
|
|
@@ -71,20 +85,24 @@ export async function updateBlobQuota(env: Env, did: string, bytesAdded: number,
|
|
|
71
85
|
const db = getDb(env);
|
|
72
86
|
const current = await getBlobQuota(env, did);
|
|
73
87
|
|
|
88
|
+
const newTotalBytes = current.total_bytes + bytesAdded;
|
|
89
|
+
const newBlobCount = current.blob_count + countAdded;
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
|
|
74
92
|
await db
|
|
75
93
|
.insert(blob_quota)
|
|
76
94
|
.values({
|
|
77
95
|
did,
|
|
78
|
-
total_bytes:
|
|
79
|
-
blob_count:
|
|
80
|
-
updated_at:
|
|
96
|
+
total_bytes: newTotalBytes,
|
|
97
|
+
blob_count: newBlobCount,
|
|
98
|
+
updated_at: now,
|
|
81
99
|
})
|
|
82
100
|
.onConflictDoUpdate({
|
|
83
101
|
target: blob_quota.did,
|
|
84
102
|
set: {
|
|
85
|
-
total_bytes:
|
|
86
|
-
blob_count:
|
|
87
|
-
updated_at:
|
|
103
|
+
total_bytes: sql.raw(`excluded.${blob_quota.total_bytes.name}`),
|
|
104
|
+
blob_count: sql.raw(`excluded.${blob_quota.blob_count.name}`),
|
|
105
|
+
updated_at: sql.raw(`excluded.${blob_quota.updated_at.name}`),
|
|
88
106
|
},
|
|
89
107
|
});
|
|
90
108
|
}
|
package/src/db/repo.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { Env } from '../env';
|
|
2
2
|
import { drizzle } from 'drizzle-orm/d1';
|
|
3
|
-
import { eq } from 'drizzle-orm';
|
|
3
|
+
import { eq, sql } from 'drizzle-orm';
|
|
4
4
|
import { repo_root, commit_log } from './schema';
|
|
5
5
|
import { RepoManager } from '../services/repo-manager';
|
|
6
6
|
import { createCommit, signCommit, commitCid, generateTid, serializeCommit } from '../lib/commit';
|
|
7
7
|
import { CID } from 'multiformats/cid';
|
|
8
|
+
import { resolveSecret } from '../lib/secrets';
|
|
8
9
|
|
|
9
10
|
export async function getRoot(env: Env) {
|
|
10
11
|
const db = drizzle(env.DB);
|
|
11
|
-
const did = env.PDS_DID ?? 'did:example:single-user';
|
|
12
|
+
const did = (await resolveSecret(env.PDS_DID)) ?? 'did:example:single-user';
|
|
12
13
|
return db.select().from(repo_root).where(eq(repo_root.did, did)).get();
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -22,7 +23,7 @@ export async function bumpRoot(env: Env, prevMstRoot?: CID): Promise<{
|
|
|
22
23
|
mstRoot: CID;
|
|
23
24
|
}> {
|
|
24
25
|
const db = drizzle(env.DB);
|
|
25
|
-
const did = env.PDS_DID ?? 'did:example:single-user';
|
|
26
|
+
const did = (await resolveSecret(env.PDS_DID)) ?? 'did:example:single-user';
|
|
26
27
|
|
|
27
28
|
// Resolve signing key (use ephemeral dev key if not configured and not production)
|
|
28
29
|
const signingKey = await getSigningKey(env);
|
|
@@ -54,19 +55,19 @@ export async function bumpRoot(env: Env, prevMstRoot?: CID): Promise<{
|
|
|
54
55
|
const cid = await commitCid(signedCommit);
|
|
55
56
|
const cidString = cid.toString();
|
|
56
57
|
|
|
57
|
-
// Update repo root
|
|
58
|
+
// Update repo root - use sql.raw with excluded to properly reference INSERT values
|
|
58
59
|
await db
|
|
59
60
|
.insert(repo_root)
|
|
60
61
|
.values({
|
|
61
62
|
did,
|
|
62
63
|
commitCid: cidString,
|
|
63
|
-
rev
|
|
64
|
+
rev, // Store TID as text
|
|
64
65
|
})
|
|
65
66
|
.onConflictDoUpdate({
|
|
66
67
|
target: repo_root.did,
|
|
67
68
|
set: {
|
|
68
|
-
commitCid:
|
|
69
|
-
rev:
|
|
69
|
+
commitCid: sql.raw('excluded.commit_cid'),
|
|
70
|
+
rev: sql.raw('excluded.rev'),
|
|
70
71
|
},
|
|
71
72
|
})
|
|
72
73
|
.run();
|
|
@@ -111,7 +112,7 @@ export async function appendCommit(env: Env, cid: string, rev: string, data: str
|
|
|
111
112
|
let cachedDevSigningKey: string | undefined;
|
|
112
113
|
|
|
113
114
|
async function getSigningKey(env: Env): Promise<string> {
|
|
114
|
-
const configured = env.REPO_SIGNING_KEY;
|
|
115
|
+
const configured = await resolveSecret(env.REPO_SIGNING_KEY);
|
|
115
116
|
if (configured && configured.trim() !== '') return configured;
|
|
116
117
|
|
|
117
118
|
const envName = (env as any).ENVIRONMENT || 'development';
|
package/src/db/schema.ts
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
|
-
import { sqliteTable, text, integer, index, primaryKey } from 'drizzle-orm/sqlite-core';
|
|
1
|
+
import { sqliteTable, text, integer, index, primaryKey, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
|
|
3
|
+
export const secret = sqliteTable('secret', {
|
|
4
|
+
key: text('key').primaryKey().notNull(),
|
|
5
|
+
value: text('value').notNull(),
|
|
6
|
+
updatedAt: integer('updated_at', { mode: 'number' }).notNull(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const account = sqliteTable('account', {
|
|
10
|
+
did: text('did').primaryKey().notNull(),
|
|
11
|
+
handle: text('handle').notNull(),
|
|
12
|
+
passwordScrypt: text('password_scrypt'),
|
|
13
|
+
email: text('email'),
|
|
14
|
+
createdAt: integer('created_at', { mode: 'number' }).notNull(),
|
|
15
|
+
updatedAt: integer('updated_at', { mode: 'number' }).notNull(),
|
|
16
|
+
}, (table) => ({
|
|
17
|
+
handleIdx: uniqueIndex('account_handle_unique').on(table.handle),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
export const refresh_token_store = sqliteTable('refresh_token', {
|
|
21
|
+
id: text('id').primaryKey().notNull(),
|
|
22
|
+
did: text('did').notNull(),
|
|
23
|
+
expiresAt: integer('expires_at', { mode: 'number' }).notNull(),
|
|
24
|
+
appPasswordName: text('app_password_name'),
|
|
25
|
+
nextId: text('next_id'),
|
|
26
|
+
}, (table) => ({
|
|
27
|
+
didIdx: index('refresh_token_did_idx').on(table.did),
|
|
28
|
+
}));
|
|
2
29
|
|
|
3
30
|
export const repo_root = sqliteTable('repo_root', {
|
|
4
31
|
did: text('did').primaryKey().notNull(),
|
|
5
32
|
commitCid: text('commit_cid').notNull(),
|
|
6
|
-
rev:
|
|
33
|
+
rev: text('rev').notNull(), // TID format (e.g., "3m2biurz7cl27")
|
|
7
34
|
});
|
|
8
35
|
|
|
9
36
|
export const record = sqliteTable('record', {
|
|
@@ -61,15 +88,6 @@ export const blockstore = sqliteTable('blockstore', {
|
|
|
61
88
|
bytes: text('bytes'),
|
|
62
89
|
});
|
|
63
90
|
|
|
64
|
-
export const token_revocation = sqliteTable('token_revocation', {
|
|
65
|
-
jti: text('jti').primaryKey().notNull(),
|
|
66
|
-
exp: integer('exp').notNull(),
|
|
67
|
-
revoked_at: integer('revoked_at').notNull(),
|
|
68
|
-
}, (table) => ({
|
|
69
|
-
// Index for cleanup queries (finding expired tokens)
|
|
70
|
-
expIdx: index('token_revocation_exp_idx').on(table.exp),
|
|
71
|
-
}));
|
|
72
|
-
|
|
73
91
|
export const login_attempts = sqliteTable('login_attempts', {
|
|
74
92
|
ip: text('ip').primaryKey().notNull(),
|
|
75
93
|
attempts: integer('attempts').notNull().default(0),
|
package/src/lib/actor.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { getDb } from '../db/client';
|
|
2
|
+
import { record } from '../db/schema';
|
|
3
|
+
import { resolveSecret } from './secrets';
|
|
4
|
+
import type { Env } from '../env';
|
|
5
|
+
import { eq } from 'drizzle-orm';
|
|
6
|
+
|
|
7
|
+
interface ProfileRecord {
|
|
8
|
+
displayName?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
pronouns?: string;
|
|
11
|
+
website?: string;
|
|
12
|
+
avatar?: string;
|
|
13
|
+
banner?: string;
|
|
14
|
+
joinedViaStarterPack?: any;
|
|
15
|
+
pinnedPost?: any;
|
|
16
|
+
labels?: any;
|
|
17
|
+
createdAt?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PrimaryActor {
|
|
21
|
+
did: string;
|
|
22
|
+
handle: string;
|
|
23
|
+
displayName?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
pronouns?: string;
|
|
26
|
+
website?: string;
|
|
27
|
+
avatar?: string;
|
|
28
|
+
banner?: string;
|
|
29
|
+
labels?: any;
|
|
30
|
+
createdAt?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const PROFILE_COLLECTION = 'app.bsky.actor.profile';
|
|
34
|
+
|
|
35
|
+
export async function fetchProfileRecord(env: Env, did: string): Promise<ProfileRecord | null> {
|
|
36
|
+
const db = getDb(env);
|
|
37
|
+
|
|
38
|
+
const targetUri = `at://${did}/${PROFILE_COLLECTION}/self`;
|
|
39
|
+
const byDid = await db.select().from(record).where(eq(record.uri, targetUri)).get();
|
|
40
|
+
if (byDid?.json) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(byDid.json) as ProfileRecord;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Fallback: pick the most recent profile record regardless of DID
|
|
49
|
+
const fallback = await env.DB.prepare(
|
|
50
|
+
'SELECT json FROM record WHERE uri LIKE ? ORDER BY rowid DESC LIMIT 1'
|
|
51
|
+
)
|
|
52
|
+
.bind(`%/${PROFILE_COLLECTION}/%`)
|
|
53
|
+
.first<{ json: string }>();
|
|
54
|
+
|
|
55
|
+
if (fallback?.json) {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(fallback.json) as ProfileRecord;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function getPrimaryActor(env: Env): Promise<PrimaryActor> {
|
|
67
|
+
const did = (await resolveSecret(env.PDS_DID)) ?? 'did:example:single-user';
|
|
68
|
+
const handle = (await resolveSecret(env.PDS_HANDLE)) ?? 'user.example.com';
|
|
69
|
+
|
|
70
|
+
const profile = await fetchProfileRecord(env, did);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
did,
|
|
74
|
+
handle,
|
|
75
|
+
displayName: profile?.displayName ?? handle,
|
|
76
|
+
description: profile?.description,
|
|
77
|
+
pronouns: profile?.pronouns,
|
|
78
|
+
website: profile?.website,
|
|
79
|
+
avatar: profile?.avatar,
|
|
80
|
+
banner: profile?.banner,
|
|
81
|
+
labels: profile?.labels,
|
|
82
|
+
createdAt: profile?.createdAt,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function matchesPrimaryActor(identifier: string | null | undefined, actor: PrimaryActor): boolean {
|
|
87
|
+
if (!identifier) return false;
|
|
88
|
+
const lower = identifier.toLowerCase();
|
|
89
|
+
return lower === actor.did.toLowerCase() || lower === actor.handle.toLowerCase();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function buildProfileViewBasic(actor: PrimaryActor) {
|
|
93
|
+
const createdAt = actor.createdAt ?? new Date().toISOString();
|
|
94
|
+
const labels = Array.isArray(actor.labels) ? actor.labels : [];
|
|
95
|
+
return {
|
|
96
|
+
$type: 'app.bsky.actor.defs#profileViewBasic',
|
|
97
|
+
did: actor.did,
|
|
98
|
+
handle: actor.handle,
|
|
99
|
+
displayName: actor.displayName,
|
|
100
|
+
pronouns: actor.pronouns,
|
|
101
|
+
avatar: actor.avatar,
|
|
102
|
+
createdAt,
|
|
103
|
+
associated: { $type: 'app.bsky.actor.defs#profileAssociated' },
|
|
104
|
+
labels,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function buildProfileView(actor: PrimaryActor) {
|
|
109
|
+
const basic = buildProfileViewBasic(actor);
|
|
110
|
+
return {
|
|
111
|
+
...basic,
|
|
112
|
+
$type: 'app.bsky.actor.defs#profileView',
|
|
113
|
+
description: actor.description,
|
|
114
|
+
indexedAt: actor.createdAt ?? new Date().toISOString(),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function buildProfileViewDetailed(actor: PrimaryActor, counts: {
|
|
119
|
+
followers: number;
|
|
120
|
+
follows: number;
|
|
121
|
+
posts: number;
|
|
122
|
+
}) {
|
|
123
|
+
const view = buildProfileView(actor);
|
|
124
|
+
return {
|
|
125
|
+
...view,
|
|
126
|
+
$type: 'app.bsky.actor.defs#profileViewDetailed',
|
|
127
|
+
banner: actor.banner,
|
|
128
|
+
website: actor.website,
|
|
129
|
+
followersCount: counts.followers,
|
|
130
|
+
followsCount: counts.follows,
|
|
131
|
+
postsCount: counts.posts,
|
|
132
|
+
};
|
|
133
|
+
}
|