fluxmail 0.0.1 → 0.1.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/dist/accounts/googleAuth.d.ts +31 -0
- package/dist/accounts/googleAuth.d.ts.map +1 -0
- package/dist/accounts/googleAuth.js +143 -0
- package/dist/accounts/googleAuth.js.map +1 -0
- package/dist/accounts/registry.d.ts +28 -0
- package/dist/accounts/registry.d.ts.map +1 -0
- package/dist/accounts/registry.js +185 -0
- package/dist/accounts/registry.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +434 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +215 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +15 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +15 -0
- package/dist/context.js.map +1 -0
- package/dist/http/app.d.ts +16 -0
- package/dist/http/app.d.ts.map +1 -0
- package/dist/http/app.js +110 -0
- package/dist/http/app.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/licensing/activation.d.ts +5 -0
- package/dist/licensing/activation.d.ts.map +1 -0
- package/dist/licensing/activation.js +11 -0
- package/dist/licensing/activation.js.map +1 -0
- package/dist/licensing/client.d.ts +52 -0
- package/dist/licensing/client.d.ts.map +1 -0
- package/dist/licensing/client.js +75 -0
- package/dist/licensing/client.js.map +1 -0
- package/dist/licensing/entitlements.d.ts +55 -0
- package/dist/licensing/entitlements.d.ts.map +1 -0
- package/dist/licensing/entitlements.js +116 -0
- package/dist/licensing/entitlements.js.map +1 -0
- package/dist/licensing/lease.d.ts +47 -0
- package/dist/licensing/lease.d.ts.map +1 -0
- package/dist/licensing/lease.js +103 -0
- package/dist/licensing/lease.js.map +1 -0
- package/dist/licensing/refresher.d.ts +46 -0
- package/dist/licensing/refresher.d.ts.map +1 -0
- package/dist/licensing/refresher.js +150 -0
- package/dist/licensing/refresher.js.map +1 -0
- package/dist/mcp/buildServer.d.ts +28 -0
- package/dist/mcp/buildServer.d.ts.map +1 -0
- package/dist/mcp/buildServer.js +394 -0
- package/dist/mcp/buildServer.js.map +1 -0
- package/dist/scheduler/sendScheduler.d.ts +38 -0
- package/dist/scheduler/sendScheduler.d.ts.map +1 -0
- package/dist/scheduler/sendScheduler.js +130 -0
- package/dist/scheduler/sendScheduler.js.map +1 -0
- package/dist/service/emailService.d.ts +138 -0
- package/dist/service/emailService.d.ts.map +1 -0
- package/dist/service/emailService.js +375 -0
- package/dist/service/emailService.js.map +1 -0
- package/dist/storage/apiKeys.d.ts +27 -0
- package/dist/storage/apiKeys.d.ts.map +1 -0
- package/dist/storage/apiKeys.js +43 -0
- package/dist/storage/apiKeys.js.map +1 -0
- package/dist/storage/crypto.d.ts +4 -0
- package/dist/storage/crypto.d.ts.map +1 -0
- package/dist/storage/crypto.js +21 -0
- package/dist/storage/crypto.js.map +1 -0
- package/dist/storage/db.d.ts +729 -0
- package/dist/storage/db.d.ts.map +1 -0
- package/dist/storage/db.js +143 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/members.d.ts +29 -0
- package/dist/storage/members.d.ts.map +1 -0
- package/dist/storage/members.js +67 -0
- package/dist/storage/members.js.map +1 -0
- package/dist/storage/scheduledSends.d.ts +60 -0
- package/dist/storage/scheduledSends.d.ts.map +1 -0
- package/dist/storage/scheduledSends.js +169 -0
- package/dist/storage/scheduledSends.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +38 -15
- package/README.md +0 -29
- package/bin/fluxmail.js +0 -4
- package/src/cli.js +0 -36
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/storage/db.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAGjF,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASnB,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAapB,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAOtB,CAAC;AAEH,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASlB,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMvB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuB1B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAiE/C,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAgBjD"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
3
|
+
import { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
4
|
+
export const members = sqliteTable('members', {
|
|
5
|
+
id: text('id').primaryKey(),
|
|
6
|
+
name: text('name').notNull(),
|
|
7
|
+
email: text('email'),
|
|
8
|
+
createdAt: integer('created_at').notNull(),
|
|
9
|
+
}, (table) => [uniqueIndex('members_email_unique').on(table.email)]);
|
|
10
|
+
export const accounts = sqliteTable('accounts', {
|
|
11
|
+
id: text('id').primaryKey(),
|
|
12
|
+
provider: text('provider').notNull(),
|
|
13
|
+
email: text('email').notNull(),
|
|
14
|
+
displayName: text('display_name'),
|
|
15
|
+
status: text('status').notNull().default('active'),
|
|
16
|
+
createdAt: integer('created_at').notNull(),
|
|
17
|
+
/** Owning member; NULL means the mailbox is shared across the instance. */
|
|
18
|
+
memberId: text('member_id').references(() => members.id, { onDelete: 'set null' }),
|
|
19
|
+
}, (table) => [uniqueIndex('accounts_provider_email_unique').on(table.provider, table.email)]);
|
|
20
|
+
export const oauthTokens = sqliteTable('oauth_tokens', {
|
|
21
|
+
accountId: text('account_id')
|
|
22
|
+
.primaryKey()
|
|
23
|
+
.references(() => accounts.id, { onDelete: 'cascade' }),
|
|
24
|
+
/** AES-256-GCM encrypted JSON blob of provider tokens. */
|
|
25
|
+
encryptedTokens: text('encrypted_tokens').notNull(),
|
|
26
|
+
updatedAt: integer('updated_at').notNull(),
|
|
27
|
+
});
|
|
28
|
+
export const apiKeys = sqliteTable('api_keys', {
|
|
29
|
+
id: text('id').primaryKey(),
|
|
30
|
+
name: text('name').notNull(),
|
|
31
|
+
/** SHA-256 hex of the key; the key itself is shown once at creation. */
|
|
32
|
+
keyHash: text('key_hash').notNull().unique(),
|
|
33
|
+
createdAt: integer('created_at').notNull(),
|
|
34
|
+
lastUsedAt: integer('last_used_at'),
|
|
35
|
+
/** Member the key was issued to; NULL means an unscoped admin key. */
|
|
36
|
+
memberId: text('member_id').references(() => members.id, { onDelete: 'set null' }),
|
|
37
|
+
});
|
|
38
|
+
export const licenseLease = sqliteTable('license_lease', {
|
|
39
|
+
/** Single-row table; the row id is always 'current'. */
|
|
40
|
+
id: text('id').primaryKey(),
|
|
41
|
+
/** Signed entitlement lease exactly as issued by the license server. */
|
|
42
|
+
token: text('token').notNull(),
|
|
43
|
+
updatedAt: integer('updated_at').notNull(),
|
|
44
|
+
});
|
|
45
|
+
export const scheduledSends = sqliteTable('scheduled_sends', {
|
|
46
|
+
id: text('id').primaryKey(),
|
|
47
|
+
accountId: text('account_id')
|
|
48
|
+
.notNull()
|
|
49
|
+
.references(() => accounts.id, { onDelete: 'cascade' }),
|
|
50
|
+
draftId: text('draft_id').notNull(),
|
|
51
|
+
/** Epoch milliseconds. */
|
|
52
|
+
sendAt: integer('send_at').notNull(),
|
|
53
|
+
createdAt: integer('created_at').notNull(),
|
|
54
|
+
status: text('status').notNull().default('pending'),
|
|
55
|
+
attempts: integer('attempts').notNull().default(0),
|
|
56
|
+
lastError: text('last_error'),
|
|
57
|
+
sentMessageId: text('sent_message_id'),
|
|
58
|
+
sentThreadId: text('sent_thread_id'),
|
|
59
|
+
/** Display snapshots taken at schedule time so listing never hits the provider. */
|
|
60
|
+
subject: text('subject'),
|
|
61
|
+
toRecipients: text('to_recipients'),
|
|
62
|
+
claimToken: text('claim_token'),
|
|
63
|
+
claimUntil: integer('claim_until'),
|
|
64
|
+
}, (table) => [index('scheduled_sends_pending_due').on(table.status, table.sendAt)]);
|
|
65
|
+
const BOOTSTRAP_SQL = `
|
|
66
|
+
CREATE TABLE IF NOT EXISTS members (
|
|
67
|
+
id TEXT PRIMARY KEY,
|
|
68
|
+
name TEXT NOT NULL,
|
|
69
|
+
email TEXT,
|
|
70
|
+
created_at INTEGER NOT NULL
|
|
71
|
+
);
|
|
72
|
+
CREATE UNIQUE INDEX IF NOT EXISTS members_email_unique ON members(email);
|
|
73
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
74
|
+
id TEXT PRIMARY KEY,
|
|
75
|
+
provider TEXT NOT NULL,
|
|
76
|
+
email TEXT NOT NULL,
|
|
77
|
+
display_name TEXT,
|
|
78
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
79
|
+
created_at INTEGER NOT NULL,
|
|
80
|
+
member_id TEXT REFERENCES members(id) ON DELETE SET NULL
|
|
81
|
+
);
|
|
82
|
+
CREATE UNIQUE INDEX IF NOT EXISTS accounts_provider_email_unique
|
|
83
|
+
ON accounts(provider, email);
|
|
84
|
+
CREATE TABLE IF NOT EXISTS oauth_tokens (
|
|
85
|
+
account_id TEXT PRIMARY KEY REFERENCES accounts(id) ON DELETE CASCADE,
|
|
86
|
+
encrypted_tokens TEXT NOT NULL,
|
|
87
|
+
updated_at INTEGER NOT NULL
|
|
88
|
+
);
|
|
89
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
90
|
+
id TEXT PRIMARY KEY,
|
|
91
|
+
name TEXT NOT NULL,
|
|
92
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
93
|
+
created_at INTEGER NOT NULL,
|
|
94
|
+
last_used_at INTEGER,
|
|
95
|
+
member_id TEXT REFERENCES members(id) ON DELETE SET NULL
|
|
96
|
+
);
|
|
97
|
+
CREATE TABLE IF NOT EXISTS license_lease (
|
|
98
|
+
id TEXT PRIMARY KEY,
|
|
99
|
+
token TEXT NOT NULL,
|
|
100
|
+
updated_at INTEGER NOT NULL
|
|
101
|
+
);
|
|
102
|
+
CREATE TABLE IF NOT EXISTS scheduled_sends (
|
|
103
|
+
id TEXT PRIMARY KEY,
|
|
104
|
+
account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
105
|
+
draft_id TEXT NOT NULL,
|
|
106
|
+
send_at INTEGER NOT NULL,
|
|
107
|
+
created_at INTEGER NOT NULL,
|
|
108
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
109
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
110
|
+
last_error TEXT,
|
|
111
|
+
sent_message_id TEXT,
|
|
112
|
+
sent_thread_id TEXT,
|
|
113
|
+
subject TEXT,
|
|
114
|
+
to_recipients TEXT,
|
|
115
|
+
claim_token TEXT,
|
|
116
|
+
claim_until INTEGER
|
|
117
|
+
);
|
|
118
|
+
CREATE INDEX IF NOT EXISTS scheduled_sends_pending_due
|
|
119
|
+
ON scheduled_sends(status, send_at);
|
|
120
|
+
`;
|
|
121
|
+
function tableColumns(sqlite, table) {
|
|
122
|
+
return new Set(sqlite.prepare(`PRAGMA table_info(${table})`).all().map((column) => column.name));
|
|
123
|
+
}
|
|
124
|
+
export function openDb(dbPath) {
|
|
125
|
+
const sqlite = new Database(dbPath);
|
|
126
|
+
sqlite.pragma('journal_mode = WAL');
|
|
127
|
+
sqlite.pragma('foreign_keys = ON');
|
|
128
|
+
sqlite.exec(BOOTSTRAP_SQL);
|
|
129
|
+
const scheduledCols = tableColumns(sqlite, 'scheduled_sends');
|
|
130
|
+
if (!scheduledCols.has('claim_token'))
|
|
131
|
+
sqlite.exec('ALTER TABLE scheduled_sends ADD COLUMN claim_token TEXT');
|
|
132
|
+
if (!scheduledCols.has('claim_until'))
|
|
133
|
+
sqlite.exec('ALTER TABLE scheduled_sends ADD COLUMN claim_until INTEGER');
|
|
134
|
+
// Pre-members databases: NULL means shared mailbox / unscoped key, so old rows need no backfill.
|
|
135
|
+
if (!tableColumns(sqlite, 'accounts').has('member_id')) {
|
|
136
|
+
sqlite.exec('ALTER TABLE accounts ADD COLUMN member_id TEXT REFERENCES members(id) ON DELETE SET NULL');
|
|
137
|
+
}
|
|
138
|
+
if (!tableColumns(sqlite, 'api_keys').has('member_id')) {
|
|
139
|
+
sqlite.exec('ALTER TABLE api_keys ADD COLUMN member_id TEXT REFERENCES members(id) ON DELETE SET NULL');
|
|
140
|
+
}
|
|
141
|
+
return drizzle(sqlite);
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/storage/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAA8B,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEzF,MAAM,CAAC,MAAM,OAAO,GAAG,WAAW,CAChC,SAAS,EACT;IACE,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;CAC3C,EACD,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CACjE,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG,WAAW,CACjC,UAAU,EACV;IACE,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE;IACpC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IAC9B,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC;IACjC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IAClD,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;IAC1C,2EAA2E;IAC3E,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;CACnF,EACD,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,gCAAgC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAC3F,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAC,cAAc,EAAE;IACrD,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC;SAC1B,UAAU,EAAE;SACZ,UAAU,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACzD,0DAA0D;IAC1D,eAAe,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC,OAAO,EAAE;IACnD,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;CAC3C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,WAAW,CAAC,UAAU,EAAE;IAC7C,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,wEAAwE;IACxE,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE;IAC5C,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;IAC1C,UAAU,EAAE,OAAO,CAAC,cAAc,CAAC;IACnC,sEAAsE;IACtE,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;CACnF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC,eAAe,EAAE;IACvD,wDAAwD;IACxD,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,wEAAwE;IACxE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IAC9B,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;CAC3C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CACvC,iBAAiB,EACjB;IACE,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC;SAC1B,OAAO,EAAE;SACT,UAAU,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACzD,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE;IACnC,0BAA0B;IAC1B,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IACpC,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;IAC1C,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC;IACnD,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC;IAC7B,aAAa,EAAE,IAAI,CAAC,iBAAiB,CAAC;IACtC,YAAY,EAAE,IAAI,CAAC,gBAAgB,CAAC;IACpC,mFAAmF;IACnF,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC;IACxB,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC;IACnC,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC;IAC/B,UAAU,EAAE,OAAO,CAAC,aAAa,CAAC;CACnC,EACD,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CACjF,CAAC;AAIF,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDrB,CAAC;AAEF,SAAS,YAAY,CAAC,MAAyB,EAAE,KAAa;IAC5D,OAAO,IAAI,GAAG,CACX,MAAM,CAAC,OAAO,CAAC,qBAAqB,KAAK,GAAG,CAAC,CAAC,GAAG,EAA8B,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAC9G,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,MAAc;IACnC,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACpC,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACnC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC3B,MAAM,aAAa,GAAG,YAAY,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC9D,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;IAC9G,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;IACjH,iGAAiG;IACjG,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC,0FAA0F,CAAC,CAAC;IAC1G,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC,0FAA0F,CAAC,CAAC;IAC1G,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type FluxmailDb } from './db.js';
|
|
2
|
+
export interface MemberInfo {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
email: string | null;
|
|
6
|
+
createdAt: number;
|
|
7
|
+
/** Mailboxes owned by this member (shared mailboxes are not counted). */
|
|
8
|
+
accountCount: number;
|
|
9
|
+
apiKeyCount: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function addMember(db: FluxmailDb, input: {
|
|
12
|
+
name: string;
|
|
13
|
+
email?: string;
|
|
14
|
+
}): MemberInfo;
|
|
15
|
+
export declare function getMember(db: FluxmailDb, id: string): MemberInfo;
|
|
16
|
+
/** Resolve a member by id or email, for CLI --member flags. */
|
|
17
|
+
export declare function findMember(db: FluxmailDb, ref: string): MemberInfo;
|
|
18
|
+
export declare function listMembers(db: FluxmailDb): MemberInfo[];
|
|
19
|
+
/**
|
|
20
|
+
* Delete a member. Their mailboxes become shared (member_id is set to NULL by
|
|
21
|
+
* the foreign key), and their API keys are revoked: a key must never outlive
|
|
22
|
+
* the member scoping it, or it would fall back to unscoped admin access.
|
|
23
|
+
*/
|
|
24
|
+
export declare function removeMember(db: FluxmailDb, id: string): {
|
|
25
|
+
name: string;
|
|
26
|
+
freedAccounts: number;
|
|
27
|
+
revokedApiKeys: number;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=members.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"members.d.ts","sourceRoot":"","sources":["../../src/storage/members.ts"],"names":[],"mappings":"AAGA,OAAO,EAA8B,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAGtE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAYD,wBAAgB,SAAS,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAgB7F;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,CAIhE;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CASlE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,UAAU,GAAG,UAAU,EAAE,CAMxD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,MAAM,GACT;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAOjE"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { eq } from 'drizzle-orm';
|
|
3
|
+
import { EmailError } from '@fluxmail/core';
|
|
4
|
+
import { accounts, apiKeys, members } from './db.js';
|
|
5
|
+
import { assertMemberLimit, getEntitlements } from '../licensing/entitlements.js';
|
|
6
|
+
function toInfo(db, row) {
|
|
7
|
+
return {
|
|
8
|
+
...row,
|
|
9
|
+
accountCount: db.select().from(accounts).where(eq(accounts.memberId, row.id)).all().length,
|
|
10
|
+
apiKeyCount: db.select().from(apiKeys).where(eq(apiKeys.memberId, row.id)).all().length,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function addMember(db, input) {
|
|
14
|
+
const name = input.name.trim();
|
|
15
|
+
if (!name)
|
|
16
|
+
throw new EmailError('invalid_request', 'Member name cannot be empty');
|
|
17
|
+
const email = input.email?.trim().toLowerCase();
|
|
18
|
+
const id = `member_${randomBytes(6).toString('hex')}`;
|
|
19
|
+
db.transaction((tx) => {
|
|
20
|
+
if (email && tx.select().from(members).where(eq(members.email, email)).get()) {
|
|
21
|
+
throw new EmailError('invalid_request', `A member with email ${email} already exists`);
|
|
22
|
+
}
|
|
23
|
+
const memberCount = tx.select().from(members).all().length;
|
|
24
|
+
assertMemberLimit(memberCount, getEntitlements(tx));
|
|
25
|
+
tx.insert(members)
|
|
26
|
+
.values({ id, name, email: email ?? null, createdAt: Date.now() })
|
|
27
|
+
.run();
|
|
28
|
+
});
|
|
29
|
+
return getMember(db, id);
|
|
30
|
+
}
|
|
31
|
+
export function getMember(db, id) {
|
|
32
|
+
const row = db.select().from(members).where(eq(members.id, id)).get();
|
|
33
|
+
if (!row)
|
|
34
|
+
throw new EmailError('not_found', `No member with id "${id}"`);
|
|
35
|
+
return toInfo(db, row);
|
|
36
|
+
}
|
|
37
|
+
/** Resolve a member by id or email, for CLI --member flags. */
|
|
38
|
+
export function findMember(db, ref) {
|
|
39
|
+
const byId = db.select().from(members).where(eq(members.id, ref)).get();
|
|
40
|
+
if (byId)
|
|
41
|
+
return toInfo(db, byId);
|
|
42
|
+
const byEmail = db.select().from(members).where(eq(members.email, ref.trim().toLowerCase())).get();
|
|
43
|
+
if (byEmail)
|
|
44
|
+
return toInfo(db, byEmail);
|
|
45
|
+
throw new EmailError('not_found', `No member with id or email "${ref}". Run "fluxmail members list" to see members.`);
|
|
46
|
+
}
|
|
47
|
+
export function listMembers(db) {
|
|
48
|
+
return db
|
|
49
|
+
.select()
|
|
50
|
+
.from(members)
|
|
51
|
+
.all()
|
|
52
|
+
.map((row) => toInfo(db, row));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Delete a member. Their mailboxes become shared (member_id is set to NULL by
|
|
56
|
+
* the foreign key), and their API keys are revoked: a key must never outlive
|
|
57
|
+
* the member scoping it, or it would fall back to unscoped admin access.
|
|
58
|
+
*/
|
|
59
|
+
export function removeMember(db, id) {
|
|
60
|
+
const info = getMember(db, id);
|
|
61
|
+
db.transaction((tx) => {
|
|
62
|
+
tx.delete(apiKeys).where(eq(apiKeys.memberId, id)).run();
|
|
63
|
+
tx.delete(members).where(eq(members.id, id)).run();
|
|
64
|
+
});
|
|
65
|
+
return { name: info.name, freedAccounts: info.accountCount, revokedApiKeys: info.apiKeyCount };
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=members.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"members.js","sourceRoot":"","sources":["../../src/storage/members.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type FluxmailDb } from './db.js';
|
|
2
|
+
export type ScheduledSendStatus = 'pending' | 'sending' | 'sent' | 'failed' | 'canceled';
|
|
3
|
+
export interface ScheduledSendRow {
|
|
4
|
+
id: string;
|
|
5
|
+
accountId: string;
|
|
6
|
+
draftId: string;
|
|
7
|
+
/** Epoch milliseconds. */
|
|
8
|
+
sendAt: number;
|
|
9
|
+
createdAt: number;
|
|
10
|
+
status: ScheduledSendStatus;
|
|
11
|
+
attempts: number;
|
|
12
|
+
lastError: string | null;
|
|
13
|
+
sentMessageId: string | null;
|
|
14
|
+
sentThreadId: string | null;
|
|
15
|
+
subject: string | null;
|
|
16
|
+
toRecipients: string | null;
|
|
17
|
+
claimToken: string | null;
|
|
18
|
+
claimUntil: number | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function createScheduledSend(db: FluxmailDb, input: {
|
|
21
|
+
accountId: string;
|
|
22
|
+
draftId: string;
|
|
23
|
+
sendAt: number;
|
|
24
|
+
subject?: string;
|
|
25
|
+
toRecipients?: string;
|
|
26
|
+
}): ScheduledSendRow;
|
|
27
|
+
export declare function getScheduledSend(db: FluxmailDb, id: string): ScheduledSendRow | undefined;
|
|
28
|
+
/** All schedules, pending first by due time, then the rest newest-first. */
|
|
29
|
+
export declare function listScheduledSends(db: FluxmailDb, accountId?: string): ScheduledSendRow[];
|
|
30
|
+
export declare function listPending(db: FluxmailDb): ScheduledSendRow[];
|
|
31
|
+
export declare function listActive(db: FluxmailDb): ScheduledSendRow[];
|
|
32
|
+
export declare function findPendingByDraft(db: FluxmailDb, accountId: string, draftId: string): ScheduledSendRow | undefined;
|
|
33
|
+
export declare function markSent(db: FluxmailDb, id: string, result: {
|
|
34
|
+
id: string;
|
|
35
|
+
threadId: string;
|
|
36
|
+
}): void;
|
|
37
|
+
/** Atomically claim a due row so only one scheduler process can dispatch it. */
|
|
38
|
+
export declare function claimScheduledSend(db: FluxmailDb, id: string, now: number, leaseMs: number): {
|
|
39
|
+
row: ScheduledSendRow;
|
|
40
|
+
token: string;
|
|
41
|
+
} | undefined;
|
|
42
|
+
export declare function completeClaim(db: FluxmailDb, id: string, token: string, result: {
|
|
43
|
+
id: string;
|
|
44
|
+
threadId: string;
|
|
45
|
+
}): void;
|
|
46
|
+
export declare function failClaim(db: FluxmailDb, id: string, token: string, error: string): void;
|
|
47
|
+
export declare function retryClaim(db: FluxmailDb, id: string, token: string, error: string): void;
|
|
48
|
+
export declare function listClaimable(db: FluxmailDb, now: number): ScheduledSendRow[];
|
|
49
|
+
export declare function markFailed(db: FluxmailDb, id: string, error: string): void;
|
|
50
|
+
/** Record why a pending send is held (e.g. plan quota) without consuming an attempt. */
|
|
51
|
+
export declare function holdPending(db: FluxmailDb, id: string, error: string): void;
|
|
52
|
+
/** Record a retryable failure: bump attempts, keep the row pending. */
|
|
53
|
+
export declare function recordAttempt(db: FluxmailDb, id: string, error: string): void;
|
|
54
|
+
/** Only pending schedules can be canceled; returns false otherwise. */
|
|
55
|
+
export declare function cancelScheduledSend(db: FluxmailDb, id: string): boolean;
|
|
56
|
+
export declare function countPending(db: FluxmailDb): {
|
|
57
|
+
pending: number;
|
|
58
|
+
nextSendAt?: number;
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=scheduledSends.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduledSends.d.ts","sourceRoot":"","sources":["../../src/storage/scheduledSends.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { and, eq, lte, or } from 'drizzle-orm';
|
|
3
|
+
import { EmailError } from '@fluxmail/core';
|
|
4
|
+
import { scheduledSends } from './db.js';
|
|
5
|
+
function toRow(r) {
|
|
6
|
+
return { ...r, status: r.status };
|
|
7
|
+
}
|
|
8
|
+
export function createScheduledSend(db, input) {
|
|
9
|
+
const id = `sch_${randomBytes(6).toString('hex')}`;
|
|
10
|
+
const createdAt = Date.now();
|
|
11
|
+
return db.transaction((tx) => {
|
|
12
|
+
const existing = tx
|
|
13
|
+
.select()
|
|
14
|
+
.from(scheduledSends)
|
|
15
|
+
.where(and(eq(scheduledSends.accountId, input.accountId), eq(scheduledSends.draftId, input.draftId), eq(scheduledSends.status, 'pending')))
|
|
16
|
+
.get();
|
|
17
|
+
if (existing) {
|
|
18
|
+
throw new EmailError('invalid_request', `Draft ${input.draftId} is already scheduled to send at ${new Date(existing.sendAt).toISOString()}; cancel it first to reschedule`);
|
|
19
|
+
}
|
|
20
|
+
const row = {
|
|
21
|
+
id,
|
|
22
|
+
accountId: input.accountId,
|
|
23
|
+
draftId: input.draftId,
|
|
24
|
+
sendAt: input.sendAt,
|
|
25
|
+
createdAt,
|
|
26
|
+
status: 'pending',
|
|
27
|
+
attempts: 0,
|
|
28
|
+
lastError: null,
|
|
29
|
+
sentMessageId: null,
|
|
30
|
+
sentThreadId: null,
|
|
31
|
+
subject: input.subject ?? null,
|
|
32
|
+
toRecipients: input.toRecipients ?? null,
|
|
33
|
+
claimToken: null,
|
|
34
|
+
claimUntil: null,
|
|
35
|
+
};
|
|
36
|
+
tx.insert(scheduledSends).values(row).run();
|
|
37
|
+
return toRow(row);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export function getScheduledSend(db, id) {
|
|
41
|
+
const row = db.select().from(scheduledSends).where(eq(scheduledSends.id, id)).get();
|
|
42
|
+
return row ? toRow(row) : undefined;
|
|
43
|
+
}
|
|
44
|
+
/** All schedules, pending first by due time, then the rest newest-first. */
|
|
45
|
+
export function listScheduledSends(db, accountId) {
|
|
46
|
+
const rows = accountId
|
|
47
|
+
? db.select().from(scheduledSends).where(eq(scheduledSends.accountId, accountId)).all()
|
|
48
|
+
: db.select().from(scheduledSends).all();
|
|
49
|
+
return rows.map(toRow).sort((a, b) => {
|
|
50
|
+
if ((a.status === 'pending') !== (b.status === 'pending'))
|
|
51
|
+
return a.status === 'pending' ? -1 : 1;
|
|
52
|
+
return a.status === 'pending' ? a.sendAt - b.sendAt : b.createdAt - a.createdAt;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export function listPending(db) {
|
|
56
|
+
return db.select().from(scheduledSends).where(eq(scheduledSends.status, 'pending')).all().map(toRow);
|
|
57
|
+
}
|
|
58
|
+
export function listActive(db) {
|
|
59
|
+
return db
|
|
60
|
+
.select()
|
|
61
|
+
.from(scheduledSends)
|
|
62
|
+
.where(or(eq(scheduledSends.status, 'pending'), eq(scheduledSends.status, 'sending')))
|
|
63
|
+
.all()
|
|
64
|
+
.map(toRow);
|
|
65
|
+
}
|
|
66
|
+
export function findPendingByDraft(db, accountId, draftId) {
|
|
67
|
+
const row = db
|
|
68
|
+
.select()
|
|
69
|
+
.from(scheduledSends)
|
|
70
|
+
.where(and(eq(scheduledSends.accountId, accountId), eq(scheduledSends.draftId, draftId), eq(scheduledSends.status, 'pending')))
|
|
71
|
+
.get();
|
|
72
|
+
return row ? toRow(row) : undefined;
|
|
73
|
+
}
|
|
74
|
+
export function markSent(db, id, result) {
|
|
75
|
+
db.update(scheduledSends)
|
|
76
|
+
.set({ status: 'sent', sentMessageId: result.id, sentThreadId: result.threadId, lastError: null })
|
|
77
|
+
.where(eq(scheduledSends.id, id))
|
|
78
|
+
.run();
|
|
79
|
+
}
|
|
80
|
+
/** Atomically claim a due row so only one scheduler process can dispatch it. */
|
|
81
|
+
export function claimScheduledSend(db, id, now, leaseMs) {
|
|
82
|
+
const token = randomBytes(12).toString('hex');
|
|
83
|
+
const result = db
|
|
84
|
+
.update(scheduledSends)
|
|
85
|
+
.set({ status: 'sending', claimToken: token, claimUntil: now + leaseMs })
|
|
86
|
+
.where(and(eq(scheduledSends.id, id), or(eq(scheduledSends.status, 'pending'), and(eq(scheduledSends.status, 'sending'), lte(scheduledSends.claimUntil, now)))))
|
|
87
|
+
.run();
|
|
88
|
+
if (result.changes === 0)
|
|
89
|
+
return undefined;
|
|
90
|
+
return { row: getScheduledSend(db, id), token };
|
|
91
|
+
}
|
|
92
|
+
export function completeClaim(db, id, token, result) {
|
|
93
|
+
db.update(scheduledSends)
|
|
94
|
+
.set({
|
|
95
|
+
status: 'sent',
|
|
96
|
+
sentMessageId: result.id,
|
|
97
|
+
sentThreadId: result.threadId,
|
|
98
|
+
lastError: null,
|
|
99
|
+
claimToken: null,
|
|
100
|
+
claimUntil: null,
|
|
101
|
+
})
|
|
102
|
+
.where(and(eq(scheduledSends.id, id), eq(scheduledSends.claimToken, token)))
|
|
103
|
+
.run();
|
|
104
|
+
}
|
|
105
|
+
export function failClaim(db, id, token, error) {
|
|
106
|
+
db.update(scheduledSends)
|
|
107
|
+
.set({ status: 'failed', lastError: error, claimToken: null, claimUntil: null })
|
|
108
|
+
.where(and(eq(scheduledSends.id, id), eq(scheduledSends.claimToken, token)))
|
|
109
|
+
.run();
|
|
110
|
+
}
|
|
111
|
+
export function retryClaim(db, id, token, error) {
|
|
112
|
+
const row = getScheduledSend(db, id);
|
|
113
|
+
if (!row || row.claimToken !== token)
|
|
114
|
+
return;
|
|
115
|
+
db.update(scheduledSends)
|
|
116
|
+
.set({
|
|
117
|
+
status: 'pending',
|
|
118
|
+
attempts: row.attempts + 1,
|
|
119
|
+
lastError: error,
|
|
120
|
+
claimToken: null,
|
|
121
|
+
claimUntil: null,
|
|
122
|
+
})
|
|
123
|
+
.where(and(eq(scheduledSends.id, id), eq(scheduledSends.claimToken, token)))
|
|
124
|
+
.run();
|
|
125
|
+
}
|
|
126
|
+
export function listClaimable(db, now) {
|
|
127
|
+
return db
|
|
128
|
+
.select()
|
|
129
|
+
.from(scheduledSends)
|
|
130
|
+
.where(or(eq(scheduledSends.status, 'pending'), and(eq(scheduledSends.status, 'sending'), lte(scheduledSends.claimUntil, now))))
|
|
131
|
+
.all()
|
|
132
|
+
.map(toRow);
|
|
133
|
+
}
|
|
134
|
+
export function markFailed(db, id, error) {
|
|
135
|
+
db.update(scheduledSends).set({ status: 'failed', lastError: error }).where(eq(scheduledSends.id, id)).run();
|
|
136
|
+
}
|
|
137
|
+
/** Record why a pending send is held (e.g. plan quota) without consuming an attempt. */
|
|
138
|
+
export function holdPending(db, id, error) {
|
|
139
|
+
db.update(scheduledSends)
|
|
140
|
+
.set({ lastError: error })
|
|
141
|
+
.where(and(eq(scheduledSends.id, id), eq(scheduledSends.status, 'pending')))
|
|
142
|
+
.run();
|
|
143
|
+
}
|
|
144
|
+
/** Record a retryable failure: bump attempts, keep the row pending. */
|
|
145
|
+
export function recordAttempt(db, id, error) {
|
|
146
|
+
const row = db.select().from(scheduledSends).where(eq(scheduledSends.id, id)).get();
|
|
147
|
+
if (!row)
|
|
148
|
+
return;
|
|
149
|
+
db.update(scheduledSends)
|
|
150
|
+
.set({ attempts: row.attempts + 1, lastError: error })
|
|
151
|
+
.where(eq(scheduledSends.id, id))
|
|
152
|
+
.run();
|
|
153
|
+
}
|
|
154
|
+
/** Only pending schedules can be canceled; returns false otherwise. */
|
|
155
|
+
export function cancelScheduledSend(db, id) {
|
|
156
|
+
const result = db
|
|
157
|
+
.update(scheduledSends)
|
|
158
|
+
.set({ status: 'canceled' })
|
|
159
|
+
.where(and(eq(scheduledSends.id, id), eq(scheduledSends.status, 'pending')))
|
|
160
|
+
.run();
|
|
161
|
+
return result.changes > 0;
|
|
162
|
+
}
|
|
163
|
+
export function countPending(db) {
|
|
164
|
+
const rows = listActive(db);
|
|
165
|
+
if (!rows.length)
|
|
166
|
+
return { pending: 0 };
|
|
167
|
+
return { pending: rows.length, nextSendAt: Math.min(...rows.map((r) => r.sendAt)) };
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=scheduledSends.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduledSends.js","sourceRoot":"","sources":["../../src/storage/scheduledSends.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,OAAO,QAAmB,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
3
|
+
if (typeof manifest.version !== 'string' || manifest.version.length === 0) {
|
|
4
|
+
throw new Error('The fluxmail package manifest must define a version.');
|
|
5
|
+
}
|
|
6
|
+
export const VERSION = manifest.version;
|
|
7
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMvC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CACzB,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAC/C,CAAC;AAErB,IAAI,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IAC1E,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fluxmail",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "Fluxmail
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Fluxmail MCP server: unified email tools over Gmail (Outlook and IMAP planned)",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
6
8
|
"bin": {
|
|
7
|
-
"fluxmail": "
|
|
9
|
+
"fluxmail": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
8
16
|
},
|
|
9
17
|
"files": [
|
|
10
|
-
"
|
|
11
|
-
"src"
|
|
18
|
+
"dist"
|
|
12
19
|
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
13
23
|
"engines": {
|
|
14
|
-
"node": ">=
|
|
24
|
+
"node": ">=20"
|
|
15
25
|
},
|
|
16
|
-
"
|
|
17
|
-
"
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@hono/node-server": "^1.14.3",
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.13.0",
|
|
29
|
+
"better-sqlite3": "^11.10.0",
|
|
30
|
+
"commander": "^13.1.0",
|
|
31
|
+
"drizzle-orm": "^0.45.2",
|
|
32
|
+
"google-auth-library": "10.5.0",
|
|
33
|
+
"hono": "^4.7.11",
|
|
34
|
+
"zod": "^3.25.67",
|
|
35
|
+
"@fluxmail/core": "0.1.0",
|
|
36
|
+
"@fluxmail/provider-gmail": "0.1.0"
|
|
18
37
|
},
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
40
|
+
"@types/node": "^22.15.0"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -p tsconfig.json",
|
|
44
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"dev": "tsc -p tsconfig.json --watch"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/README.md
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# fluxmail
|
|
2
|
-
|
|
3
|
-
CLI stub for `fluxmail`.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install -g fluxmail
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
fluxmail help
|
|
15
|
-
fluxmail version
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Local development
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
node bin/fluxmail.js help
|
|
22
|
-
npm link # exposes the `fluxmail` command locally
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Publishing
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npm publish --access public
|
|
29
|
-
```
|
package/bin/fluxmail.js
DELETED