Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c36ad9c
feat: add Pixiv media and bookmark support
sleroq Aug 15, 2026
97f692a
fix: tighten Pixiv credential and media handling
sleroq Aug 15, 2026
63a6d0c
fix: preserve Pixiv animation media kind
sleroq Aug 15, 2026
b9cf4cc
refactor: rename post provider payload field
sleroq Aug 15, 2026
c4afd6b
refactor: move Twitter cookies to provider credentials
sleroq Aug 15, 2026
ebda045
fix: harden provider credential migration
sleroq Aug 15, 2026
988b2c1
fix: validate Twitter credential cookies
sleroq Aug 15, 2026
ac126ca
fix: support legacy Twitter cookie exports
sleroq Aug 15, 2026
a472267
fix: prevent root TypeScript emits
sleroq Aug 15, 2026
54aa53e
feat: make media search provider agnostic
sleroq Aug 15, 2026
c09e8cf
Remove divider between sources in the UI
sleroq Aug 15, 2026
ceb98a4
fix: scope Pixiv crawl idempotency by run
sleroq Aug 15, 2026
d078cb5
fix: preserve Pixiv schedule chain
sleroq Aug 15, 2026
8eeccfc
fix: persist Pixiv token rotation before work
sleroq Aug 15, 2026
523d241
fix: reject malformed public media ids
sleroq Aug 15, 2026
ee1251d
refactor: remove unused Pixiv force mode
sleroq Aug 15, 2026
7267a08
fix: release Pixiv credential lock before work
sleroq Aug 15, 2026
5a8398d
perf: parallelize Pixiv media queueing
sleroq Aug 15, 2026
b07a3c6
fix: label Pixiv refresh token field
sleroq Aug 15, 2026
eb85d45
fix: surface settings mutation errors
sleroq Aug 15, 2026
416e685
refactor: unify provider scraper activation
sleroq Aug 15, 2026
8500043
fix: order gallery media by position
sleroq Aug 15, 2026
c19edff
fix: use app aliases in server tests
sleroq Aug 15, 2026
47abd3b
refactor: adopt provider-neutral post naming
sleroq Aug 15, 2026
b565ead
fix: resolve duplicate media per user
sleroq Aug 15, 2026
33f2511
refactor: consolidate media helpers
sleroq Aug 15, 2026
860711e
fix: regenerate provider-neutral migration
sleroq Aug 15, 2026
a6cbf15
refactor: use Bun APIs for ugoira files
sleroq Aug 15, 2026
23714db
refactor: separate tag normalization concerns
sleroq Aug 15, 2026
075e343
fix(server): persist collected media without nested provider field
sleroq Aug 15, 2026
e11b176
test: consolidate provider credential coverage
sleroq Aug 15, 2026
2efc2e7
fix: preserve pixiv search attribution
sleroq Aug 15, 2026
bf24741
refactor: simplify scraper command flow
sleroq Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"date-fns": "^4.4.0",
"effect": "4.0.0-beta.100",
"grammy": "catalog:",
"jszip": "^3.10.1",
"pg": "^8.22.0",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3",
Expand Down
66 changes: 29 additions & 37 deletions apps/server/src/crypto/cookie-encryption.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,35 @@ describe("CookieEncryption", () => {
});

describe("key derivation", () => {
test("decrypts scoped ciphertext without marking it as legacy", () => {
const encrypted = encryption.encryptScoped(
testCookieData,
testUserId,
"provider:twitter:cookies:v1",
);
const decrypted = encryption.decryptScopedOrLegacy(
encrypted,
testUserId,
"provider:twitter:cookies:v1",
"legacy-telegram-id",
);

expect(decrypted).toEqual({ data: testCookieData, usedLegacyEncryption: false });
});

test("decrypts legacy user-scoped ciphertext for migration", () => {
const legacyTelegramId = "123456";
const encrypted = encryption.encrypt(testCookieData, legacyTelegramId);
const decrypted = encryption.decryptScopedOrLegacy(
encrypted,
testUserId,
"provider:twitter:cookies:v1",
legacyTelegramId,
);

expect(decrypted).toEqual({ data: testCookieData, usedLegacyEncryption: true });
});

test("should produce consistent results for same inputs", () => {
const encrypted1 = encryption.encrypt(testCookieData, testUserId);
const decrypted1 = encryption.decrypt(encrypted1, testUserId);
Expand Down Expand Up @@ -243,41 +272,4 @@ describe("CookieEncryption", () => {
expect(() => encryption2.decrypt(encrypted1, testUserId)).toThrow();
});
});

describe("performance characteristics", () => {
test("should encrypt and decrypt efficiently", () => {
const start = performance.now();

// Perform multiple operations
for (let i = 0; i < 100; i++) {
const encrypted = encryption.encrypt(testCookieData, testUserId);
const decrypted = encryption.decrypt(encrypted, testUserId);
expect(decrypted).toBe(testCookieData);
}

const end = performance.now();
const duration = end - start;

// Should complete 100 encrypt/decrypt cycles in under 1 second
expect(duration).toBeLessThan(1000);
});

test("should handle concurrent operations", async () => {
const operations = Array.from({ length: 50 }, (_, i) =>
Promise.resolve().then(() => {
const userId = `user_${i}`;
const data = `${testCookieData}_${i}`;
const encrypted = encryption.encrypt(data, userId);
const decrypted = encryption.decrypt(encrypted, userId);
return { original: data, decrypted, userId };
}),
);

const results = await Promise.all(operations);

for (const result of results) {
expect(result.decrypted).toBe(result.original);
}
});
});
});
Loading