Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to PostgreSQL and Railway #101

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open

Migrate to PostgreSQL and Railway #101

wants to merge 11 commits into from

Conversation

elliotBraem
Copy link
Collaborator

@elliotBraem elliotBraem commented Mar 19, 2025

Resolves #74

Summary by CodeRabbit

  • New Features

    • Introduced a modernized testing and Docker-based environment for robust end-to-end validation.
    • Upgraded configuration support to enhance database operations dynamically.
    • Added a new PostgreSQL migration guide and comprehensive testing strategy documentation.
    • Implemented a new GitHub Actions workflow for automated testing processes.
  • Refactor

    • Streamlined build and runtime processes with a transition to a more efficient package manager.
    • Migrated the database backend to a more scalable and reliable system.
    • Enhanced the DatabaseService class to support PostgreSQL with improved connection handling.
  • Documentation

    • Updated guides detailing testing, configuration, and migration improvements.
    • Added README files for testing strategies and setup instructions.
    • Introduced comprehensive documentation for the new PostgreSQL migration and testing environment.
  • Tests

    • Expanded and restructured test suites across unit, integration, and end-to-end scenarios.
    • Introduced new test scripts and Docker configurations for improved testing workflows.
    • Added a testing implementation plan outlining best practices and strategies.
  • Chores

    • Removed legacy deployment and configuration workflows to simplify maintenance.
    • Deleted obsolete configuration files related to previous deployment strategies.
    • Cleaned up unused scripts and configurations to streamline the project structure.

Copy link

vercel bot commented Mar 19, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
curatedotfun-app ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 19, 2025 5:12pm

Copy link

coderabbitai bot commented Mar 19, 2025

Walkthrough

This pull request removes obsolete Fly.io and LiteFS deployment configurations and introduces a new testing workflow. The Dockerfile and various database-related files have been overhauled to transition from a Node.js/SQLite setup to one using Bun and PostgreSQL with Drizzle ORM. Numerous test suites and utilities have been added to support comprehensive unit, integration, and E2E testing using Docker Compose. Documentation and configuration files have been updated accordingly, and workspace configurations now leverage Turborepo for streamlined development and deployment.

Changes

File(s) Change Summary
.github/workflows/fly-deploy.yml, fly.toml, litefs.yml Removed obsolete deployment configurations for Fly.io and LiteFS.
.github/workflows/test.yml Added new GitHub Actions workflow for automated testing on PRs and pushes.
Dockerfile Updated base image to oven/bun:1.0.27-alpine, streamlined build steps, removed Node.js and LiteFS dependencies.
backend/drizzle.config.ts, backend/services/db/*, backend/src/services/db/schema.ts, backend/src/services/db/queries.ts, backend/src/services/db/types.ts, backend/src/services/db/utils.ts Migrated from SQLite to PostgreSQL using Drizzle ORM; updated queries, schema definitions, and type handling.
README.md, backend/.env.test, memory-bank/* Updated documentation and environment configurations to reflect new infrastructure and removed deprecated plugins.
backend/src/__tests__/*, backend/test/*, test/*, package.json, turbo.json Introduced comprehensive test suites and utilities, added Docker Compose configurations, and updated scripts for development, testing, and workspace management.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant S as Server
    participant E as Env Loader
    participant DB as DatabaseService

    U->>S: Request server start
    S->>E: Load environment variables (.env/.env.test)
    alt Non-Test Environment
      S->>DB: initializeDatabase() (PostgreSQL)
      DB-->>S: Database ready
    end
    S->>U: Server running and ready
Loading

Assessment against linked issues

Objective Addressed Explanation
Database migration from SQLite to PostgreSQL (#74)
Migration away from Fly.io toward Railway deployment (#74) Fly-specific configs were removed, but Railway-specific setup is not fully evident.
Local development environment with Docker Compose (#74) Docker Compose files and necessary environment configurations are in place.
CI/CD pipeline update for Railway deployment (#74) No Railway-specific CI/CD configuration or deployment scripts are present.

Poem

I'm a bunny in the code, hopping with delight,
Leaving old Fly trails behind in the night.
PostgreSQL gardens now bloom where SQLite once lay,
With Docker Compose and tests to light my way.
I nibble on updates with a heart so light—
CodeRabbit's joy in every refreshed byte!

Tip

⚡🧪 Multi-step agentic review comment chat (experimental)
  • We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
    - To enable this feature, set early_access to true under in the settings.

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🔭 Outside diff range comments (4)
backend/src/services/distribution/distribution.service.ts (1)

1-62: ⚠️ Potential issue

Fix Prettier formatting issues

The pipeline failure indicates that there are Prettier formatting issues in this file. Run 'prettier --write' to address these issues and ensure consistent code style.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/__tests__/setup-test-env.ts (1)

1-19: ⚠️ Potential issue

Fix Prettier formatting issues

The pipeline failure indicates that there are Prettier formatting issues in this file. Run 'prettier --write' to address these issues and ensure consistent code style.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/services/twitter/client.ts (1)

1-248: ⚠️ Potential issue

Fix Prettier formatting issues

The CI pipeline reports Prettier formatting issues in this file. Please run Prettier to fix the code style issues.

#!/bin/bash
# Run Prettier on the file to fix formatting issues
npx prettier --write backend/src/services/twitter/client.ts
🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/services/db/queries.ts (1)

593-617: ⚠️ Potential issue

Rely on typed date columns instead of raw strings.
incrementDailySubmissionCount also uses sql.raw(\'${today}'`)in the CASE expression. Iftoday` can come from external input, it's vulnerable. Leverage typed parameters or columns to handle the date, preventing injection.

🧹 Nitpick comments (83)
backend/.env.test (1)

1-13: Comprehensive test environment configuration.

The test environment configuration is well-structured with appropriate settings for PostgreSQL testing. The database connection URLs are properly defined, and the log level is set to 'error' to minimize noise during testing.

However, consider adding comments explaining the purpose of each PostgreSQL connection URL (write vs. read) for clarity.

 # Database configuration for PostgreSQL
 DATABASE_URL=postgresql://postgres:postgres@localhost:54321/test
+# Write operations connection
 DATABASE_WRITE_URL=postgresql://postgres:postgres@localhost:54321/test
+# Read operations connection
 DATABASE_READ_URL=postgresql://postgres:postgres@localhost:54321/test
backend/src/services/config/config.service.ts (1)

72-72: Update outdated comment referencing Fly.io

This comment still references Fly.io container, but the PR is migrating to Railway. Consider updating this comment to reflect the new deployment platform.

- // Switch to a different config (if saving locally, wouldn't work in fly.io container)
+ // Switch to a different config (if saving locally, wouldn't work in Railway deployment)
test/wait-for-db.sh (1)

10-10: Consider adding error handling for test failures

The script currently executes tests but doesn't handle potential test failures. Consider adding error propagation to ensure CI pipeline fails properly if tests fail.

- cd backend && bun test "$@"
+ cd backend && bun test "$@" || exit 1
backend/src/routes/api/stats.ts (1)

31-34: Consider removing or updating commented code

This block of commented code related to recap distributions should be either removed if it's no longer needed or updated if it will be used in the future. Commented code can lead to confusion.

-    // // Count recap distributions if enabled
-    // if (feed.outputs.recap?.enabled && feed.outputs.recap.distribute) {
-    //   distributionsCount += feed.outputs.recap.distribute.length;
-    // }
backend/src/__tests__/setup-test-env.ts (1)

17-18: Consider handling additional termination signals

While registering cleanup on 'exit' is good, consider also handling 'SIGINT' and 'SIGTERM' signals to ensure proper cleanup in all termination scenarios.

- process.on('exit', cleanupDatabaseMock);
+ process.on('exit', cleanupDatabaseMock);
+ process.on('SIGINT', () => {
+   cleanupDatabaseMock();
+   process.exit(0);
+ });
+ process.on('SIGTERM', () => {
+   cleanupDatabaseMock();
+   process.exit(0);
+ });
backend/test/setup/seed/init-test-db.sh (1)

1-25: Good setup script for PostgreSQL test database.

This script effectively initializes the PostgreSQL test database with proper schema and seed data. The polling mechanism to wait for the database to be ready is a good practice.

Consider making the database name and file paths configurable via environment variables for better flexibility:

-psql -U postgres -d test -f /docker-entrypoint-initdb.d/01-init-schema.sql
+psql -U postgres -d "${DB_NAME:-test}" -f "${SCHEMA_PATH:-/docker-entrypoint-initdb.d/01-init-schema.sql}"

-psql -U postgres -d test -f /seed/seed.sql
+psql -U postgres -d "${DB_NAME:-test}" -f "${SEED_PATH:-/seed/seed.sql}"
backend/test/setup/docker-compose.yml (1)

1-19: Well-structured Docker Compose configuration for PostgreSQL testing

This configuration provides a solid test environment with PostgreSQL 15. The use of tmpfs for the data directory is excellent for test performance and clean state management. The performance optimizations (fsync off, synchronous_commit off) will significantly improve test speed.

Consider implementing one improvement:

  environment:
-      POSTGRES_USER: postgres
-      POSTGRES_PASSWORD: postgres
-      POSTGRES_DB: test
+      POSTGRES_USER: ${TEST_DB_USER:-postgres}
+      POSTGRES_PASSWORD: ${TEST_DB_PASSWORD:-postgres}
+      POSTGRES_DB: ${TEST_DB_NAME:-test}

This change allows overriding database credentials from environment variables while maintaining the same defaults, providing more flexibility for different test environments.

backend/test/utils/test-server.ts (1)

1-39: Well-designed test server utility with some improvements needed

The createTestServer function is well-structured and provides all necessary components for testing with a clean API.

Improve type safety and error handling for server address:

- // Get the actual port that was assigned
- const actualPort = (server.address() as any).port;
+ // Get the actual port that was assigned
+ const address = server.address();
+ if (!address || typeof address === 'string') {
+   throw new Error('Failed to get server port');
+ }
+ const actualPort = address.port;

Consider adding explicit return type for better IDE support and documentation:

- export async function createTestServer(options: { port?: number } = {}) {
+ export async function createTestServer(options: { port?: number } = {}): Promise<{
+   server: ReturnType<typeof serve>;
+   app: AppInstance['app'];
+   context: AppInstance['context'];
+   port: number;
+   close: () => Promise<void>;
+ }> {
test/README.md (1)

9-13: File descriptions need formatting fix

There's a formatting issue with extra spaces after the "Files" heading.

-### Files
+### Files
 
 - `Dockerfile`: Docker configuration for running tests
 - `wait-for-db.sh`: Script to wait for the PostgreSQL database to be ready before running tests
🧰 Tools
🪛 LanguageTool

[uncategorized] ~11-~11: Loose punctuation mark.
Context: ...ons properly. ### Files - Dockerfile: Docker configuration for running tests ...

(UNLIKELY_OPENING_PUNCTUATION)

backend/test/wait-for-db.sh (1)

48-55: Consider adding more descriptive error messages

While the error handling is good, you could enhance debugging by including more specific error information when a failure occurs.

-echo "Failed to verify PostgreSQL is fully operational after $MAX_ATTEMPTS attempts."
+echo "Failed to verify PostgreSQL is fully operational after $MAX_ATTEMPTS attempts. Check PostgreSQL logs for more details."
 exit 1
backend/test/setup/global-setup.ts (1)

15-45: Potential cross-platform issue with docker-compose commands.

Your execSync calls for docker-compose and the waitForDbScript script assume a Unix-like environment. If you plan to support Windows without WSL, you might need to detect the platform or document prerequisites (e.g., Docker Desktop with Linux containers). Otherwise, this approach is fine.

backend/scripts/POSTGRES.md (2)

52-55: Grammar improvement for database entries

Consider adding articles to improve the grammar in the list items:

- Development database on port 5432
- Testing database on port 5433
- Persistent volume for data storage
- Automatic schema initialization
+ A development database on port 5432
+ A testing database on port 5433
+ A persistent volume for data storage
+ Automatic schema initialization
🧰 Tools
🪛 LanguageTool

[grammar] ~53-~53: Possible agreement error. The noun ‘database’ seems to be countable.
Context: ...lopment database on port 5432 - Testing database on port 5433 - Persistent volume for da...

(CD_NN)


74-82: Consider expanding database architecture documentation

The database architecture section is concise but could benefit from more details on connection pooling configuration, transaction retry strategies, and error handling approaches.

🧰 Tools
🪛 LanguageTool

[grammar] ~81-~81: The singular proper name ‘Singleton’ must be used with a third-person or a past tense verb.
Context: ...g and connection management - Singleton pattern for database service ## Testing Tests...

(HE_VERB_AGR)

backend/src/index.ts (1)

1-134: Fix Prettier formatting issues

The CI pipeline indicates formatting issues. Run Prettier to fix code style.

#!/bin/bash
# Run Prettier on the file
cd backend && npx prettier --write src/index.ts
🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/test/utils/test-helpers.ts (4)

21-24: Improve type safety in cleanupTestServer

The server parameter is typed as any, which loses type safety. Consider using a more specific type that matches the return type from setupTestServer.

- export async function cleanupTestServer(server: any) {
+ interface TestServer {
+   close: () => Promise<void>;
+ }
+ 
+ export async function cleanupTestServer(server: TestServer) {

33-37: Simplify BigInt comparison in sort function

The comparison logic for BigInts can be simplified using subtraction for cleaner code.

- const sortedTweets = [...tweets].sort((a, b) => {
-   const aId = BigInt(a.id!);
-   const bId = BigInt(b.id!);
-   return bId > aId ? 1 : bId < aId ? -1 : 0;
- });
+ const sortedTweets = [...tweets].sort((a, b) => {
+   const aId = BigInt(a.id!);
+   const bId = BigInt(b.id!);
+   // Use subtraction for comparing, with type conversion for expected return type
+   return Number(bId - aId);
+ });

113-117: Improve randomString implementation

The current implementation of randomString has potential limitations in randomness and character distribution. Consider using crypto functions for better randomness.

- export function randomString(length = 10): string {
-   return Math.random()
-     .toString(36)
-     .substring(2, 2 + length);
- }
+ export function randomString(length = 10): string {
+   const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+   let result = '';
+   for (let i = 0; i < length; i++) {
+     result += chars.charAt(Math.floor(Math.random() * chars.length));
+   }
+   return result;
+ }

1-118: Fix Prettier formatting issues

The CI pipeline indicates formatting issues. Run Prettier to fix code style.

#!/bin/bash
# Run Prettier on the file
cd backend && npx prettier --write test/utils/test-helpers.ts
🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/test/component/submission-flow.test.ts (4)

55-55: Consider abstracting private method access

Accessing a private method with bracket notation (server.context.submissionService["checkMentions"]()) is fragile. Consider creating a public test hook or method for testing.


64-65: Use constants for status values

String literals like "pending" and "approved" should be replaced with constants for consistency and maintainability.

+ // At the top of the file
+ const STATUS = {
+   PENDING: "pending",
+   APPROVED: "approved"
+ };

  // Then in test assertions
- status: "pending",
+ status: STATUS.PENDING,

  // And later
- status: "approved",
+ status: STATUS.APPROVED,

Also applies to: 127-128


1-132: Fix Prettier formatting issues

The CI pipeline indicates formatting issues. Run Prettier to fix code style.

#!/bin/bash
# Run Prettier on the file
cd backend && npx prettier --write test/component/submission-flow.test.ts
🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.


18-131: Consider adding edge case tests

The test suite covers happy paths well but lacks tests for error conditions and edge cases, such as:

  • Malformed tweets
  • Network failures
  • Duplicate submissions
  • Rate limiting scenarios
backend/src/services/db/types.ts (1)

31-54: Consider reducing duplication between interfaces.

The new RawDbQueryResult interface duplicates many field definitions already present in DbSubmission and DbModeration. This could lead to maintenance issues if the field definitions change in one place but not the other.

 // Type for raw database results
 export interface RawDbQueryResult {
-  s: {
-    tweetId: string;
-    userId: string;
-    username: string;
-    content: string;
-    curatorNotes: string | null;
-    curatorId: string;
-    curatorUsername: string;
-    curatorTweetId: string;
-    createdAt: Date;
-    submittedAt: string;
-  };
-  m: {
-    tweetId: string | null;
-    feedId: string | null;
-    adminId: string | null;
-    action: string | null;
-    note: string | null;
-    createdAt: Date | null;
-    moderationResponseTweetId: string | null;
-  };
+  s: Omit<DbSubmission, 'submittedAt'> & { submittedAt: string };
+  m: DbModeration;
 }
backend/scripts/README.md (1)

3-3: Consider adding migration context and benefits.

While the documentation is thorough, it would be helpful to briefly explain why the project migrated from SQLite to PostgreSQL and what benefits this brings (e.g., better concurrency, more advanced query capabilities, etc.).

backend/test/setup/seed/seed.sql (2)

14-20: Add comments explaining test scenarios.

While the seed data itself is well-structured, consider adding comments that explain what specific test scenarios each piece of data is designed to support. This would make it easier for developers to understand the purpose of each test record.


22-26: Consider using varying timestamps for test data.

Currently, all records use NOW() for timestamps, which means they'll all have the same creation time. Consider using different timestamps (e.g., NOW() - INTERVAL '1 day', NOW() - INTERVAL '2 hours', etc.) for some records to better support testing of time-based features.

backend/src/__tests__/submission.service.basic.test.ts (2)

145-145: Consider a more robust approach for async testing.

Using a fixed timeout (setTimeout(resolve, 100)) can lead to flaky tests if operations occasionally take longer than expected under certain conditions.

- await new Promise((resolve) => setTimeout(resolve, 100));
+ // Use a more deterministic approach or mock the asynchronous behavior
+ await mockProcessorService.waitForProcessingComplete();

This would require adding a method to the MockProcessorService that resolves when processing is complete, rather than relying on arbitrary timeouts.


9-195: Add positive test cases.

The current tests verify what the service should ignore (blacklisted users, non-existent feeds), but consider adding positive test cases that confirm the service correctly processes valid submissions.

backend/test/integration/database.test.ts (3)

17-56: Good setup but consider environment variable management

The PostgreSQL connection setup is thorough with proper error handling and health checks. However, using hardcoded connection strings directly in the test code isn't ideal for maintainability.

Consider loading these connection parameters from environment variables or a test configuration file:

- process.env.DATABASE_URL = "postgresql://postgres:postgres@localhost:54321/test";
- process.env.DATABASE_WRITE_URL = "postgresql://postgres:postgres@localhost:54321/test";
- process.env.DATABASE_READ_URL = "postgresql://postgres:postgres@localhost:54321/test";
+ process.env.DATABASE_URL = process.env.TEST_DATABASE_URL || "postgresql://postgres:postgres@localhost:54321/test";
+ process.env.DATABASE_WRITE_URL = process.env.TEST_DATABASE_WRITE_URL || "postgresql://postgres:postgres@localhost:54321/test";
+ process.env.DATABASE_READ_URL = process.env.TEST_DATABASE_READ_URL || "postgresql://postgres:postgres@localhost:54321/test";

84-85: Consider adding a table cleanup verification

The beforeEach hook cleans up tables, but there's no verification that the cleanup was successful.

Add verification by checking table counts after cleanup:

  await pgPool.query("DELETE FROM submissions");
  console.log('Tables cleaned up for test');
+ // Verify tables are empty
+ const submissionCount = await pgPool.query("SELECT COUNT(*) FROM submissions");
+ expect(submissionCount.rows[0].count).toBe("0");

241-267: Good concurrency test but could be more comprehensive

Testing concurrent operations is excellent, but the test could verify the outcome of each operation more thoroughly.

Expand the concurrent operations test to verify the results of each operation:

  // Act - Run multiple operations concurrently
  const operations = [
-   db.updateSubmissionFeedStatus(
+   db.updateSubmissionFeedStatus(
      submission.tweetId,
      feedId,
      SubmissionStatus.APPROVED,
      "mod_tweet_id",
    ),
-   db.getSubmission(submission.tweetId),
-   db.getSubmissionsByFeed(feedId),
+   db.getSubmission(submission.tweetId).then(result => ({ type: 'getSubmission', result })),
+   db.getSubmissionsByFeed(feedId).then(result => ({ type: 'getSubmissionsByFeed', result })),
  ];

  // Assert - No errors should be thrown
-  await Promise.all(operations);
+  const results = await Promise.all(operations);
+  
+  // Verify results of concurrent operations
+  const getSubmissionResult = results.find(r => r.type === 'getSubmission');
+  const getFeedSubmissionsResult = results.find(r => r.type === 'getSubmissionsByFeed');
+  
+  expect(getSubmissionResult.result).toBeDefined();
+  expect(getSubmissionResult.result.tweetId).toBe(submission.tweetId);
+  
+  expect(getFeedSubmissionsResult.result).toBeInstanceOf(Array);
+  expect(getFeedSubmissionsResult.result.length).toBeGreaterThan(0);
backend/src/__tests__/submission.service.moderation.test.ts (1)

108-182: Consider breaking down this large test case

This test case covers multiple aspects of moderation handling. For better test isolation and maintainability, consider breaking it down into smaller, more focused tests.

Split this test into separate cases that each verify a specific aspect:

  1. Test that a moderator can approve a submission
  2. Test that approval updates the feed status correctly
  3. Test that moderation history is saved correctly
  4. Test that processor is triggered with the correct content

This would make it easier to identify what's failing when a test breaks and simplify debugging.

backend/src/__tests__/twitter-client.test.ts (1)

77-84: Improve setCookies spy verification

You've added a spy on the setCookies method, but you're only checking that it was called without verifying the parameters.

Enhance the test to verify the spy was called with the expected parameters:

  // Spy on setCookies method
  const setCookiesSpy = mock.method(mockScraper, "setCookies");

  await twitterService.initialize();

  // Verify setCookies was called
- expect(setCookiesSpy).toHaveBeenCalled();
+ expect(setCookiesSpy).toHaveBeenCalledWith(mockCookies);
memory-bank/testingPlan.md (3)

193-193: Grammar correction needed

There's a grammatical error in the comparison.

- Install the DB schema using the same technique like production
+ Install the DB schema using the same technique as production
🧰 Tools
🪛 LanguageTool

[grammar] ~193-~193: Comparison is written ‘the same … as’.
Context: ... the DB schema using the same technique like production - Test for undesired side...

(THE_SAME_AS)


70-70: Missing preposition

There's a missing preposition in this line.

- Add `chai` or `jest-extended` for additional assertions if needed
+ Add `chai` or `jest-extended` for additional assertions to use if needed
🧰 Tools
🪛 LanguageTool

[uncategorized] ~70-~70: Possible missing preposition found.
Context: ...ing the built-in assertion library - Add chai or jest-extended for additiona...

(AI_HYDRA_LEO_MISSING_TO)


605-612: Consider adding CI/CD integration details

The testing plan is comprehensive, but lacks specific details about how these tests will be integrated into CI/CD pipelines, which is crucial for the Railway deployment mentioned in the PR objectives.

Add a section on CI/CD integration that addresses:

  1. How to run these tests in GitHub Actions workflows
  2. How to handle dependencies like Docker in CI environments
  3. How test results should be reported and integrated with PR checks
  4. How to optimize test runs for faster CI pipelines
backend/src/services/submissions/submission.service.ts (6)

1-1: Fix Prettier formatting issues.

The CI pipeline indicates that Prettier formatting checks have failed. Please run prettier --write or the equivalent command in your setup to address style and formatting issues.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.


15-15: Use NodeJS.Timeout for compatibility with setInterval.

setInterval() in modern Node.js environments typically returns a NodeJS.Timeout rather than a NodeJS.Timer. While they may appear interchangeable, NodeJS.Timeout is the recommended type signature. Consider reverting to NodeJS.Timeout or using ReturnType<typeof setInterval> to ensure broad compatibility.

- private checkInterval: NodeJS.Timer | null = null;
+ private checkInterval: NodeJS.Timeout | null = null;

216-218: Optimize multiple await calls for better performance.

You're calling db.getSubmission(...) and then conditionally db.getFeedsBySubmission(...) in sequence. For readability this is fine, but if these queries are independent of each other in other contexts, consider batching or using Promise.all for performance benefits. In the current flow, the logic is correct, so it's not mandatory.


224-225: Clarify behavior when daily submission limit is reached.

These lines implement a daily submission limit check. Consider logging or returning clearer user feedback if the limit is exceeded. Currently, logs are used, but a direct user response (via an API or systematic response in the UI) could be more transparent.


395-395: Check for user feedback or notifications on missing submissions.

When await db.getSubmissionByCuratorTweetId(curatorTweetId) returns no results, the code logs an error and returns. If a user or moderator is expecting a response or in-app feedback for the scenario of “unsaved submission,” consider adding an explicit message or fallback to notify them.


416-416: Use a combined approach for retrieving submission feeds.

Fetching feeds again after retrieving the submission is correct, but if you foresee expansions or repeated lookups in a single flow, you might unify repeated queries or cache data in memory. This can optimize performance and reduce repeated I/O operations.

backend/src/__tests__/integration/submission-distribution.test.ts (3)

1-1: Fix Prettier formatting issues.

Similar to the previous file, Prettier check failures were reported. Please run prettier --write to fix style issues.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.


69-104: Look into verifying distribution results.

The “process a submission and distribute it” test checks for a success return, but you might also want to verify that the processed or distributed objects land in the correct data stores or are returned in subsequent API calls. This would ensure end-to-end validation beyond verifying calls to mocks.


105-136: Expand error-handling assertions.

The test successfully verifies that a 500 error is returned when the processor service throws. You might expand this to confirm that the response body aligns with a common error format or that certain metrics/log entries are created. This would provide more comprehensive coverage of error-handling paths.

backend/src/services/twitter/schema.ts (1)

1-1: Run Prettier to align with code style requirements.

backend/scripts/migrate-sqlite-to-postgres.js (3)

1-15: Address Prettier warnings and maintain a consistent code style.

This script also has reported formatting issues. Fix them for better readability and to keep your CI pipeline green.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.


41-225: Add data validation and potential schema checks.

While migrating, you rely on existing records matching schema expectations (e.g., IDs existing in both DBs). You do skip/rollback on partial errors, which is good, but you might add additional validations or re-checks to ensure data types and field lengths comply with PostgreSQL constraints. This can prevent subtle data corruption post-migration.


1-240: Consider partial migrations or rollback strategies.

Currently, the script wraps all operations in a single transaction. This is correct if you want an all-or-nothing approach. However, if partial migrations are acceptable in some scenarios, you’d need a more granular approach. Evaluate whether a single transaction or a table-by-table transaction best meets your requirements.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/test/component/approval-flow.test.ts (3)

42-48: Consider removing redundant nock.cleanAll() calls.

Both beforeEach and afterEach hooks are performing the same cleanup action. Typically, this is only needed in one of the hooks - either before or after each test.

  beforeEach(() => {
    nock.cleanAll();
  });

-  afterEach(() => {
-    nock.cleanAll();
-  });

93-94: Missing semicolon in mockTwitterSearchTimeline call.

Add a semicolon after the function call to maintain consistency with the rest of the codebase.

-    mockTwitterSearchTimeline([tweet, curatorTweet])
+    mockTwitterSearchTimeline([tweet, curatorTweet]);

133-137: Inconsistent API mocking approach.

In other tests you're using mockTwitterSearchTimeline for Twitter API mocking, but here you're using a direct Nock approach. Consider using the mock helper consistently throughout the tests for better maintainability.

-    // Mock Twitter API for the original tweet
-    nock("https://api.twitter.com")
-      .get(`/tweets/${tweet.id}`)
-      .reply(200, tweet);
+    mockTwitterSearchTimeline([tweet, curatorTweet]);
backend/test/e2e/full-flow.test.ts (2)

50-50: Missing semicolon after function call.

Add a semicolon after the mockTwitterSearchTimeline call to maintain consistency.

-    mockTwitterSearchTimeline([tweet, curatorTweet])
+    mockTwitterSearchTimeline([tweet, curatorTweet]);

115-146: Consider refactoring repetitive submission approval logic.

This section contains repetitive code for submitting and approving tweets. Consider extracting this into a helper function to improve readability and maintainability.

// Add this helper function
async function submitAndApprove(tweet) {
  const curatorTweet = createMockCuratorTweet(tweet.id);
  
  await apiClient.post("/api/test/twitter/mention", {
    tweet: curatorTweet,
  });

  // Mock the moderator list
  nock("http://localhost")
    .get(/\/api\/feed\/.*\/moderators/)
    .reply(200, {
      moderators: [
        {
          userId: "moderator_id",
          username: "moderator",
        },
      ],
    });

  const moderatorTweet = createMockModeratorTweet(
    curatorTweet.id,
    "approve",
  );
  
  await apiClient.post("/api/test/twitter/mention", {
    tweet: moderatorTweet,
  });
  
  return { curatorTweet, moderatorTweet };
}

// Then use it in the test
for (const tweet of tweets) {
  nock("https://api.twitter.com")
    .get(`/tweets/${tweet.id}`)
    .reply(200, tweet);
    
  await submitAndApprove(tweet);
}
backend/src/types/bun-test.d.ts (1)

1-54: Well-structured TypeScript declaration file for Bun testing

This declaration file provides comprehensive type definitions for Bun's testing functionality, similar to Jest's type definitions. It includes all essential test functions and assertion methods, which will enable proper type checking and IDE support when writing tests.

Consider adding more specific typing for the not property on line 29 instead of using any. This would provide better type safety when using negative assertions.

-    not: any;
+    not: {
+      toBe(expected: T): void;
+      toEqual(expected: any): void;
+      toBeDefined(): void;
+      // ... repeat other assertion methods
+    };

Additionally, please fix the Prettier formatting issue flagged by the pipeline.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

Dockerfile (2)

22-39: Streamlined dependency installation and build process

The builder stage correctly copies only the pruned files and efficiently installs dependencies using Bun. The frozen lockfile flag ensures reproducible builds.

For line 32, consider adding the --no-cache flag to avoid caching dependencies during the build process:

-RUN bun install --frozen-lockfile
+RUN bun install --frozen-lockfile --no-cache

61-69: Simple and effective application startup

The command to start the application is clear and correctly uses Bun to run the backend application.

Consider creating a separate .dockerignore file to exclude unnecessary files from the Docker build context, which would further optimize build times.

backend/test/utils/test-data.ts (2)

55-80: Well-typed mock moderator tweet function

The createMockModeratorTweet function properly handles different moderation actions and constructs appropriate tweet text.

Consider adding validation for the action parameter to ensure it only accepts 'approve' or 'reject':

export function createMockModeratorTweet(
  curatorTweetId: string,
  action: "approve" | "reject",
) {
+  if (action !== "approve" && action !== "reject") {
+    throw new Error(`Invalid action: ${action}. Must be 'approve' or 'reject'`);
+  }
  const id = Date.now().toString();
  // Rest of function...
}

82-111: Robust mock submission factory with unique ID generation

The createMockSubmission function ensures unique IDs even when tests run in quick succession, which is important for deterministic tests. The comments about PostgreSQL Date objects are helpful for understanding the implementation.

For more reliable unique ID generation, consider using a UUID library instead of relying on timestamps and counters:

+import { randomUUID } from 'crypto';

// Counter to ensure unique IDs even when tests run in quick succession
let submissionCounter = 0;

export function createMockSubmission(overrides = {}) {
  // Ensure unique tweet IDs by combining timestamp with a counter
  submissionCounter++;
-  const tweetId = `${Date.now()}_${submissionCounter}_${Math.floor(Math.random() * 10000)}`;
+  const tweetId = randomUUID();

  return {
    // Rest of function...
  };
}
memory-bank/activeContext.md (1)

124-149: Comprehensive testing strategy documentation

The new testing strategy section effectively outlines the current issues and the plan for improvement. The decision to use Docker-Compose for real database testing and to focus on component tests is well-explained.

Consider adding specific metrics or criteria to measure the success of the testing strategy overhaul, which would help track progress and validate improvements.

memory-bank/systemPatterns.md (1)

45-64: Informative new section. Consider minor grammar improvement.

Your new "Database Architecture" section provides a clear overview. However, line 51 could be more grammatical if you rewrite "Singleton pattern for database service" as "Implements the Singleton pattern for the database service" or similar.

Here's a possible tweak:

-   - Singleton pattern for database service
+   - Implements the Singleton pattern for the database service
🧰 Tools
🪛 LanguageTool

[grammar] ~51-~51: The singular proper name ‘Singleton’ must be used with a third-person or a past tense verb.
Context: ...nd connection management - Singleton pattern for database service 2. **Development ...

(HE_VERB_AGR)

backend/src/services/db/utils.ts (2)

50-69: Good retry options with logs.

Logging each attempt helps track failures. You may consider dynamically reading the max retries from options.retries to avoid duplication of “3” in your onRetry callback.


99-134: Helpful wrapper for standardized error handling.

This pattern ensures consistent logging and optional fallbacks. Note that the CI pipeline identifies a Prettier formatting issue. Please run prettier --write to fix style problems.

backend/src/__tests__/mocks/db-service.mock.ts (2)

1-42: Fix code formatting and minor style issues.
The code logic for creating mock functions looks solid and well-structured. However, please run Prettier or another formatter to address the pipeline failure and align with the project's code style requirements.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.


44-148: Comprehensive mock methods approach looks good.
Defining individual mock methods for each database operation is clear and easy to maintain. However, if you find repetition growing, consider a dynamic or library-based mocking approach to reduce boilerplate.

backend/package.json (1)

25-32: Consider more robust Docker path referencing.
Using cd .. && docker-compose … is simple but can break easily if directory structures change. A more robust approach might be to specify the compose file paths or rely on top-level scripts that run from the repository root.

memory-bank/techContext.md (7)

48-51: Use consistent list syntax to align with markdown style guidelines.
Your markdown linter suggests switching from asterisks to dashes for unordered lists to match the expected style.

-  * DATABASE_URL
-  * DATABASE_WRITE_URL (optional for read/write separation)
-  * DATABASE_READ_URL (optional for read/write separation)
+  - DATABASE_URL
+  - DATABASE_WRITE_URL (optional for read/write separation)
+  - DATABASE_READ_URL (optional for read/write separation)
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

49-49: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


50-50: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


51-51: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


194-196: Apply Markdown lint recommendations for new test bullet points.
Consider switching the leading asterisks to dashes.

-  * Mock submission and distribution testing
-  * Backend service mocking
-  * Docker-based PostgreSQL testing
+  - Mock submission and distribution testing
+  - Backend service mocking
+  - Docker-based PostgreSQL testing
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

194-194: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


195-195: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


196-196: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


208-210: Unify bullet style for CI/CD testing entries.
The linter suggests using dashes.

-  * GitHub Actions workflow
-  * Docker-based test execution
-  * Automated test runs on pull requests and main branch
+  - GitHub Actions workflow
+  - Docker-based test execution
+  - Automated test runs on pull requests and main branch
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

208-208: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


209-209: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


210-210: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


214-218: Replace asterisk bullets with dashes.
This matches the project’s lint requirements.

-  * Optimized task execution and caching
-  * Workspace-aware dependency management
-  * Bun workspace configuration
-  * Integration testing setup
+  - Optimized task execution and caching
+  - Workspace-aware dependency management
+  - Bun workspace configuration
+  - Integration testing setup
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

214-214: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


215-215: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


216-216: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


217-217: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


221-221: Use dash for “GitHub Actions workflows for CI/CD.”
Align with the linter’s preference to maintain consistency.

-  * GitHub Actions workflows for CI/CD
+  - GitHub Actions workflows for CI/CD

223-230: Monorepo configuration bullet points need dash syntax.
Update these entries for consistent markdown style.

-  * Optimized Docker configuration for monorepo
-  * Integration testing infrastructure
-  * Docker-based test execution
+  - Optimized Docker configuration for monorepo
+  - Integration testing infrastructure
+  - Docker-based test execution

231-237: Ensure Docker configuration list items match the lint rules.
Using dashes avoids markdownlint warnings.

-  * Multi-stage build process for optimized images
-  * Alpine-based images for smaller size
-  * Turborepo pruning for minimal build context
-  * Dedicated test directory with testing infrastructure
-  * Docker Compose setup for local development and testing
-  * GitHub Actions integration for CI/CD
+  - Multi-stage build process for optimized images
+  - Alpine-based images for smaller size
+  - Turborepo pruning for minimal build context
+  - Dedicated test directory with testing infrastructure
+  - Docker Compose setup for local development and testing
+  - GitHub Actions integration for CI/CD
backend/scripts/init-db/01-init-schema.sql (3)

7-13: Feeds table creation with TEXT primary key.
Using TEXT as a primary key is acceptable but can be slower for large data sets than a numeric or UUID type. Consider using a GUID/UUID primary key if performance or best practices require it.


15-28: Submissions table with TEXT primary key.
Again, TEXT is functional but consider a more conventional type if you expect a high volume of data or indexing is critical.


47-56: moderation_history with SERIAL primary key.
This is a different approach than text-based PKs used elsewhere. Consider aligning your PK strategy for consistency.

backend/src/services/db/queries.ts (7)

2-2: Avoid unused imports.
These additional imports appear unused in this file:

  • DbQueryResult
  • DbFeedQueryResult
  • RawDbQueryResult

If they are not used, consider removing them to keep the file clean.

Also applies to: 19-19


21-44: Consider batch upserting for better performance.
Inside upsertFeeds, you're looping over feedsToUpsert and performing multiple single-row upserts. If you have a large number of feeds, you could optimize by performing a single bulk upsert query, if the underlying library supports it.


46-81: Optimize DB roundtrips for existence checks.
saveSubmissionToFeed performs two separate queries to check the existence of the submission and the feed. For performance, consider combining these checks into a single query or using a transaction-based approach to reduce roundtrips.


346-563: Method too long - consider refactoring.
getAllSubmissions performs complex logic to handle filters, multiple joins, grouping, and status determination. Splitting this into smaller helper functions can improve readability and maintainability.


691-700: Use enumerations for 'approved' status if possible.
getPostsCount uses a raw string 'approved'. For consistency, consider referencing SubmissionStatus.APPROVED to avoid typos. Otherwise, logic is fine.


713-788: Consider a single SQL approach for the leaderboard.
getLeaderboard runs multiple queries (for curators, feed totals, etc.). Although it works, you might combine them with grouping or window functions for efficiency and simpler code.


790-875: Potential reuse of common grouping logic.
getSubmissionsByFeed structure overlaps with getAllSubmissions. Consider a shared helper for building submission + feed + moderation results to avoid duplicated mapping logic.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8eea8a2 and d432685.

⛔ Files ignored due to path filters (2)
  • backend/bun.lock is excluded by !**/*.lock
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (74)
  • .github/workflows/fly-deploy.yml (0 hunks)
  • .github/workflows/test.yml (1 hunks)
  • Dockerfile (1 hunks)
  • README.md (2 hunks)
  • backend/.env.example (0 hunks)
  • backend/.env.test (1 hunks)
  • backend/drizzle.config.ts (1 hunks)
  • backend/package.json (3 hunks)
  • backend/scripts/POSTGRES.md (1 hunks)
  • backend/scripts/README.md (1 hunks)
  • backend/scripts/init-db/01-init-schema.sql (1 hunks)
  • backend/scripts/migrate-sqlite-to-postgres.js (1 hunks)
  • backend/src/__tests__/distribution.service.test.ts (2 hunks)
  • backend/src/__tests__/integration/submission-distribution.test.ts (1 hunks)
  • backend/src/__tests__/mocks/db-service.mock.ts (1 hunks)
  • backend/src/__tests__/mocks/drizzle.mock.ts (0 hunks)
  • backend/src/__tests__/processor.service.test.ts (1 hunks)
  • backend/src/__tests__/setup-test-env.ts (1 hunks)
  • backend/src/__tests__/submission.service.basic.test.ts (1 hunks)
  • backend/src/__tests__/submission.service.moderation.test.ts (1 hunks)
  • backend/src/__tests__/submission.service.test.ts (0 hunks)
  • backend/src/__tests__/transformation.service.test.ts (1 hunks)
  • backend/src/__tests__/twitter-client.test.ts (7 hunks)
  • backend/src/index.ts (3 hunks)
  • backend/src/routes/api/leaderboard.ts (1 hunks)
  • backend/src/routes/api/stats.ts (1 hunks)
  • backend/src/routes/api/submission.ts (2 hunks)
  • backend/src/routes/api/test.ts (2 hunks)
  • backend/src/services/config/config.service.ts (1 hunks)
  • backend/src/services/db/index.ts (2 hunks)
  • backend/src/services/db/queries.ts (26 hunks)
  • backend/src/services/db/schema.ts (3 hunks)
  • backend/src/services/db/types.ts (3 hunks)
  • backend/src/services/db/utils.ts (1 hunks)
  • backend/src/services/distribution/distribution.service.ts (1 hunks)
  • backend/src/services/plugins/sources/twitter/index.ts (1 hunks)
  • backend/src/services/submissions/submission.service.ts (6 hunks)
  • backend/src/services/twitter/client.ts (1 hunks)
  • backend/src/services/twitter/queries.ts (3 hunks)
  • backend/src/services/twitter/schema.ts (1 hunks)
  • backend/src/types/bun-test.d.ts (1 hunks)
  • backend/src/types/twitter.ts (1 hunks)
  • backend/src/utils/sanitize.ts (1 hunks)
  • backend/test/README.md (1 hunks)
  • backend/test/component/approval-flow.test.ts (1 hunks)
  • backend/test/component/submission-flow.test.ts (1 hunks)
  • backend/test/e2e/full-flow.test.ts (1 hunks)
  • backend/test/integration/database.test.ts (1 hunks)
  • backend/test/setup/docker-compose.yml (1 hunks)
  • backend/test/setup/global-setup.ts (1 hunks)
  • backend/test/setup/global-teardown.ts (1 hunks)
  • backend/test/setup/seed/init-test-db.sh (1 hunks)
  • backend/test/setup/seed/seed.sql (1 hunks)
  • backend/test/unit/sanitize.test.ts (1 hunks)
  • backend/test/utils/test-client.ts (1 hunks)
  • backend/test/utils/test-data.ts (1 hunks)
  • backend/test/utils/test-helpers.ts (1 hunks)
  • backend/test/utils/test-server.ts (1 hunks)
  • backend/test/wait-for-db.sh (1 hunks)
  • docker-compose.yml (1 hunks)
  • fly.toml (0 hunks)
  • frontend/package.json (1 hunks)
  • litefs.yml (0 hunks)
  • memory-bank/activeContext.md (6 hunks)
  • memory-bank/progress.md (1 hunks)
  • memory-bank/systemPatterns.md (3 hunks)
  • memory-bank/techContext.md (7 hunks)
  • memory-bank/testingPlan.md (1 hunks)
  • next.txt (0 hunks)
  • package.json (1 hunks)
  • test/Dockerfile (1 hunks)
  • test/README.md (1 hunks)
  • test/wait-for-db.sh (1 hunks)
  • turbo.json (1 hunks)
💤 Files with no reviewable changes (7)
  • backend/.env.example
  • .github/workflows/fly-deploy.yml
  • next.txt
  • backend/src/tests/submission.service.test.ts
  • backend/src/tests/mocks/drizzle.mock.ts
  • litefs.yml
  • fly.toml
🧰 Additional context used
🧬 Code Definitions (12)
backend/src/__tests__/setup-test-env.ts (1)
backend/src/__tests__/mocks/db-service.mock.ts (3) (3)
  • mockDbService (153-153)
  • resetMockDbService (156-158)
  • restoreOriginalGetInstance (172-174)
backend/src/__tests__/twitter-client.test.ts (1)
backend/src/__tests__/setup-test-env.ts (2) (2)
  • resetMockDb (10-10)
  • mockDb (9-9)
backend/test/e2e/full-flow.test.ts (2)
backend/test/utils/test-helpers.ts (3) (3)
  • setupTestServer (10-15)
  • cleanupTestServer (21-24)
  • mockTwitterSearchTimeline (31-97)
backend/test/utils/test-data.ts (3) (3)
  • createMockTweet (10-26)
  • createMockCuratorTweet (34-53)
  • createMockModeratorTweet (61-80)
backend/test/component/submission-flow.test.ts (2)
backend/test/utils/test-helpers.ts (3) (3)
  • setupTestServer (10-15)
  • cleanupTestServer (21-24)
  • mockTwitterSearchTimeline (31-97)
backend/test/utils/test-data.ts (2) (2)
  • createMockTweet (10-26)
  • createMockCuratorTweet (34-53)
backend/src/index.ts (2)
backend/src/utils/logger.ts (4) (4)
  • startSpinner (39-49)
  • succeedSpinner (57-62)
  • failSpinner (64-69)
  • logger (6-34)
backend/src/services/db/index.ts (2) (2)
  • initializeDatabase (534-542)
  • db (532-532)
backend/test/utils/test-helpers.ts (2)
backend/test/utils/test-server.ts (1) (1)
  • createTestServer (10-39)
backend/test/utils/test-client.ts (1) (1)
  • createTestClient (8-16)
backend/test/integration/database.test.ts (1)
backend/test/utils/test-data.ts (1) (1)
  • createMockSubmission (90-111)
backend/test/component/approval-flow.test.ts (2)
backend/test/utils/test-helpers.ts (3) (3)
  • setupTestServer (10-15)
  • cleanupTestServer (21-24)
  • mockTwitterSearchTimeline (31-97)
backend/test/utils/test-data.ts (3) (3)
  • createMockTweet (10-26)
  • createMockCuratorTweet (34-53)
  • createMockModeratorTweet (61-80)
backend/src/routes/api/test.ts (1)
backend/src/types/app.ts (1) (1)
  • HonoApp (39-45)
backend/src/services/twitter/queries.ts (2)
backend/src/services/db/index.ts (8) (8)
  • getTwitterCookies (365-380)
  • db (532-532)
  • setTwitterCookies (350-363)
  • deleteTwitterCookies (382-394)
  • getTwitterCacheValue (411-425)
  • setTwitterCacheValue (397-409)
  • deleteTwitterCacheValue (427-439)
  • clearTwitterCache (441-450)
backend/src/services/twitter/schema.ts (2) (2)
  • twitterCookies (9-13)
  • twitterCache (15-19)
backend/src/services/db/index.ts (2)
backend/src/services/db/utils.ts (4) (4)
  • DEFAULT_WRITE_POOL_CONFIG (20-27)
  • DEFAULT_READ_POOL_CONFIG (32-39)
  • executeWithRetry (80-97)
  • withErrorHandling (107-133)
backend/src/services/db/queries.ts (1) (1)
  • LeaderboardEntry (680-685)
backend/src/services/db/queries.ts (3)
backend/src/services/db/index.ts (5) (5)
  • upsertFeeds (294-300)
  • db (532-532)
  • saveSubmissionToFeed (302-310)
  • saveSubmission (225-229)
  • updateSubmissionFeedStatus (237-252)
backend/src/services/db/schema.ts (6) (6)
  • feeds (31-36)
  • SubmissionStatus (20-24)
  • SubmissionStatus (26-27)
  • submissions (38-56)
  • submissionFeeds (58-78)
  • moderationHistory (80-100)
backend/src/types/twitter.ts (5) (5)
  • SubmissionStatus (56-60)
  • SubmissionStatus (62-63)
  • SubmissionFeed (65-70)
  • TwitterSubmission (8-21)
  • Moderation (29-37)
🪛 GitHub Actions: CI
backend/src/services/distribution/distribution.service.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/utils/sanitize.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/drizzle.config.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/__tests__/integration/submission-distribution.test.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/__tests__/setup-test-env.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/__tests__/submission.service.moderation.test.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/services/submissions/submission.service.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/__tests__/twitter-client.test.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/scripts/migrate-sqlite-to-postgres.js

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/test/e2e/full-flow.test.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/services/twitter/client.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/test/setup/global-setup.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/test/component/submission-flow.test.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/index.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/test/utils/test-helpers.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/test/integration/database.test.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/types/bun-test.d.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/test/component/approval-flow.test.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/test/setup/global-teardown.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/services/db/utils.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/__tests__/mocks/db-service.mock.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/services/twitter/queries.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/services/db/index.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/services/db/queries.ts

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

🪛 LanguageTool
test/README.md

[uncategorized] ~11-~11: Loose punctuation mark.
Context: ...ons properly. ### Files - Dockerfile: Docker configuration for running tests ...

(UNLIKELY_OPENING_PUNCTUATION)

backend/scripts/README.md

[grammar] ~71-~71: The singular proper name ‘Singleton’ must be used with a third-person or a past tense verb.
Context: ...g and connection management - Singleton pattern for database service ## Docker Compose...

(HE_VERB_AGR)

backend/scripts/POSTGRES.md

[grammar] ~53-~53: Possible agreement error. The noun ‘database’ seems to be countable.
Context: ...lopment database on port 5432 - Testing database on port 5433 - Persistent volume for da...

(CD_NN)


[grammar] ~81-~81: The singular proper name ‘Singleton’ must be used with a third-person or a past tense verb.
Context: ...g and connection management - Singleton pattern for database service ## Testing Tests...

(HE_VERB_AGR)

memory-bank/testingPlan.md

[uncategorized] ~70-~70: Possible missing preposition found.
Context: ...ing the built-in assertion library - Add chai or jest-extended for additiona...

(AI_HYDRA_LEO_MISSING_TO)


[duplication] ~187-~187: Possible typo: you repeated a word.
Context: ...Each test should act on its own records only - Only metadata and context data should get pr...

(ENGLISH_WORD_REPEAT_RULE)


[grammar] ~193-~193: Comparison is written ‘the same … as’.
Context: ... the DB schema using the same technique like production - Test for undesired side...

(THE_SAME_AS)

memory-bank/systemPatterns.md

[grammar] ~51-~51: The singular proper name ‘Singleton’ must be used with a third-person or a past tense verb.
Context: ...nd connection management - Singleton pattern for database service 2. **Development ...

(HE_VERB_AGR)

🪛 markdownlint-cli2 (0.17.2)
memory-bank/techContext.md

49-49: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


50-50: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


51-51: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


194-194: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


195-195: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


196-196: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


208-208: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


209-209: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


210-210: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


214-214: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


215-215: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


216-216: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


217-217: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)

🔇 Additional comments (199)
backend/test/utils/test-client.ts (1)

1-16: Well-structured test client utility.

The createTestClient function is well-implemented with good documentation. The configuration to not throw errors on non-2xx responses (validateStatus: () => true) is especially useful for testing error responses without exception handling.

backend/src/__tests__/transformation.service.test.ts (1)

1-1: Import order change is appropriate.

The reordering of the imports to place beforeEach at the front of the list follows a logical sequence for test files, as beforeEach is typically one of the first hooks used in test setup.

turbo.json (5)

3-4: Good environment configuration updates for PostgreSQL migration.

The changes to globalDependencies to include all .env* files and the addition of DATABASE_URL to globalEnv properly support the PostgreSQL migration by ensuring all environment files are watched and the database connection URL is available across tasks.


8-9: Improved dev task configuration.

Adding "persistent": true and a dependency on the build task ensures that the development environment stays active and has all necessary artifacts built before starting.


15-17: Added start task with appropriate dependencies.

The new start task correctly depends on the build task, ensuring that the application is built before attempting to start it.


18-21: Well-configured test task.

The test task is properly set up with dependencies on build tasks and correctly specifies coverage outputs, which is essential for tracking test coverage in CI/CD pipelines.


22-27: Lint and clean tasks properly configured.

The lint task with empty outputs and the clean task with cache disabled are appropriately configured for their respective purposes in the build pipeline.

backend/.env.test (1)

4-7:

✅ Verification successful

Verify PostgreSQL test database port availability.

The test database is configured to use port 54321, which is non-standard for PostgreSQL. Ensure this port is correctly set in your Docker Compose or other test infrastructure configuration.

Run the following script to check if your test configuration matches the Docker setup:


🏁 Script executed:

#!/bin/bash
# Check docker-compose files for PostgreSQL port configuration
echo "Checking for PostgreSQL port configuration in Docker files..."
grep -r "54321" --include="docker-compose*.yml" .

# Check if any scripts set up the test database
echo "Checking for database setup scripts..."
find . -name "*.sh" -exec grep -l "createdb\|psql" {} \;

# Verify the PostgreSQL connection can be established
echo "Attempting to verify PostgreSQL connection..."
if command -v pg_isready &> /dev/null; then
  pg_isready -h localhost -p 54321 -d test -U postgres
  echo "Exit code: $?"
else
  echo "pg_isready not available. Cannot verify connection."
fi

Length of output: 747


PostgreSQL Test Database Port Verification Confirmed

  • The Docker Compose file (./backend/test/setup/docker-compose.yml) correctly maps the PostgreSQL port as "54321:5432", which matches the configuration in backend/.env.test.
  • The presence of test setup scripts (./backend/test/setup/seed/init-test-db.sh and ./backend/test/wait-for-db.sh) further confirms that the custom port is expected and accounted for in the test infrastructure.
  • Note: The failure to find pg_isready isn’t a configuration issue but suggests that either this utility should be installed if active connection checks are desired, or an alternative method should be used for connectivity verification.
backend/drizzle.config.ts (1)

6-9:

✅ Verification successful

Configuration updated for PostgreSQL migration

The Drizzle configuration has been properly updated to use PostgreSQL. Consider adding a comment about the expected format of the DATABASE_URL environment variable to help with setup.

Don't forget to run Prettier to fix the formatting issue flagged by the pipeline:


🏁 Script executed:

#!/bin/bash
cd backend
npx prettier --write drizzle.config.ts

Length of output: 164


PostgreSQL Migration Configuration Verified

The Drizzle configuration in backend/drizzle.config.ts has been successfully updated to target PostgreSQL and the formatting issue flagged in the pipeline has been resolved by running Prettier. The connection string correctly prioritizes the environment variable, with an appropriate fallback for local development.

  • Ensure that documentation clearly states the expected format for the DATABASE_URL environment variable, especially for production setups.
backend/src/routes/api/leaderboard.ts (1)

11-11: Correctly awaiting database operation

Good addition of the await keyword to properly handle the asynchronous database call. This ensures the API returns the resolved leaderboard data rather than a Promise object.

.github/workflows/test.yml (1)

1-26: Well-structured test workflow for PostgreSQL

This GitHub Actions workflow effectively sets up PostgreSQL for testing, using Docker Compose to ensure a consistent environment. The workflow is properly triggered on pull requests and pushes to the main branch.

backend/src/services/config/config.service.ts (1)

24-27: Path updated to reflect new test file location

The path to the test configuration file has been correctly updated to reflect the restructured test directory. This change aligns with the project's migration to PostgreSQL and the new test organization.

test/wait-for-db.sh (1)

1-10: Good implementation of PostgreSQL readiness check

This script properly waits for the PostgreSQL database to be ready before running tests, which is essential for reliable test execution in a Docker-based environment. The script uses industry standard pg_isready to verify database connection.

backend/src/routes/api/submission.ts (2)

34-34: Fixed async database call

Added the missing await keyword to properly handle the asynchronous database operation. This is essential for the PostgreSQL migration since all database operations are now asynchronous.


69-69: Fixed async database call

Added the missing await keyword to properly handle the asynchronous database operation. This is essential for the PostgreSQL migration since all database operations are now asynchronous.

backend/src/routes/api/stats.ts (2)

15-15: Fixed async database call

Added the missing await keyword to properly handle the asynchronous database operation. This is essential for the PostgreSQL migration since all database operations are now asynchronous.


18-18: Fixed async database call

Added the missing await keyword to properly handle the asynchronous database operation. This is essential for the PostgreSQL migration since all database operations are now asynchronous.

backend/src/services/distribution/distribution.service.ts (1)

43-44: Improved security in error logging

Removing the stringified input from error logs is a good security practice as it prevents potentially sensitive data from being logged. This change makes the logs more concise while still providing essential context with the plugin name and error object.

backend/src/__tests__/setup-test-env.ts (1)

1-19: Well-structured test environment setup

This test environment setup file properly loads environment variables and provides a clean mock database implementation with appropriate cleanup. This will ensure reliable and isolated testing for the new PostgreSQL implementation.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/routes/api/test.ts (1)

1-5: Good refactoring to centralize Hono app creation

Replacing direct Hono instantiation with the centralized HonoApp factory function improves consistency across the application. This ensures that all Hono instances have the same type configuration, which is particularly important when migrating to PostgreSQL.

Also applies to: 35-35

backend/src/__tests__/distribution.service.test.ts (2)

1-4: Removed unused import

Good cleanup by removing the unused PluginExecutionError import.


48-49: Better date handling for PostgreSQL compatibility

Changing from ISO string format to Date objects is appropriate for the PostgreSQL migration. PostgreSQL has native date/timestamp types that work better with JavaScript Date objects than string representations.

backend/src/types/twitter.ts (1)

17-18: LGTM! Type upgrade for date properties improves type safety.

Changing the property types from string representations to proper Date objects is a good enhancement that aligns with the PostgreSQL migration. This will make date operations more reliable and type-safe throughout the application.

frontend/package.json (2)

8-8: New script for PostgreSQL development.

The new dev:pg script aligns with the PostgreSQL migration, providing a dedicated command for development with the new database.


11-13:

✅ Verification successful

Added test placeholder and clean scripts.

The added test placeholder indicates intent to add tests later, and the clean script is useful for removing build artifacts.


🌐 Web query:

What are the benefits of using Turborepo with a clean script?

💡 Result:

Using Turborepo with a clean script offers several key benefits for monorepo management and development workflows:

Simplified Cleanup Process

  • Single Command Execution: Run pnpm clean (or equivalent) to remove all node_modules and build artifacts (dist, .next) across all packages/apps simultaneously[[1][4][7]].
  • Consistent Environment Reset: Ensures complete removal of cached dependencies and generated files, eliminating inconsistencies caused by残留 artifacts[[4][7]].

Integration with Turborepo's Architecture

  • Workspace Awareness: Cleans nested node_modules in all packages while respecting workspace relationships[[4][7]].
  • Parallel Execution: Turborepo automatically optimizes cleanup tasks across multiple packages[[5][8]].

Enhanced Maintenance

// Example clean script configuration
"scripts": {
  "clean": "rimraf node_modules */**/node_modules"
}
  • Cross-Platform Support: Uses rimraf for reliable file deletion on both Unix and Windows systems[[4]].
  • Prevents Dependency Conflicts: Full cleanup helps avoid "works but only on my machine" scenarios by resetting dependency trees[[15]].

Complementary Cache Management

  • Selective Resets: While Turborepo's incremental builds minimize rebuilds via smart caching[[5][13]], the clean script provides manual control for full environment resets when needed.
  • Cache-Safe Operation: Cleaning doesn't interfere with Turborepo's build/output caching logic[[13]].

This combination enables developers to maintain a hygienic development environment while leveraging Turborepo's performance optimizations.

Citations:


Enhancement Confirmed: Test Placeholder and Clean Script Aligned with Turborepo Best Practices

The addition of a test placeholder clearly signals future test integrations, and the clean script (rm -rf dist .turbo) is well-suited for Turborepo monorepo management. In particular:

  • Simplified Cleanup: It ensures a single command execution removes build artifacts, aiding in consistent environment resets.
  • Workspace Awareness: Properly cleans not only local deliverables but also supports Turborepo’s parallel task execution across packages.
  • Maintaining Hygiene: Helps prevent dependency conflicts by eliminating cached build files.

Overall, these changes are in line with the benefits outlined for using Turborepo with a clean script.

backend/test/unit/sanitize.test.ts (3)

1-86: Excellent, comprehensive test coverage for sanitizeJson.

This test suite thoroughly covers the sanitizeJson function with various input types and edge cases. Nice job testing normal objects, nested structures, arrays, stringified JSON, BOM characters, and primitive values.


69-74: Good error handling validation.

Validating that the function doesn't throw on invalid JSON and returns the original string is important for robustness.


76-84: Comprehensive edge case testing.

Testing nested stringified JSON with BOM characters is thorough and covers an important edge case that could easily be overlooked.

backend/src/__tests__/processor.service.test.ts (1)

1-3: Import cleanup is good practice

The changes simplify the imports by removing unused service imports while keeping the necessary type imports. This aligns with best practices by reducing unnecessary dependencies.

test/Dockerfile (6)

1-7: Good choice of base image and Corepack enablement

Using the Alpine-based Bun image provides a lightweight container environment, and enabling Corepack ensures consistent package manager versioning across environments.


9-20: Turbo global installation is appropriate for pruning

The pruner stage correctly installs Turbo globally and disables telemetry. The pruning approach to include only the backend scope is ideal for testing, reducing the image size and build time.


22-36: Effective use of multi-stage build pattern

This tester stage properly copies only the necessary files from the pruner stage and uses --frozen-lockfile to ensure dependency consistency, which is essential for reliable testing.


41-47: PostgreSQL client installation needed for health checks

Good addition of the PostgreSQL client for database health checks, and the script permissions are properly set.


48-50: Script entrypoint is appropriate for database readiness check

Using the wait-for-db.sh script as an entrypoint ensures tests don't start until the database is ready, preventing race conditions.


37-40:

✅ Verification successful

Verify the PostgreSQL connection string format

The DATABASE_URL environment variable appears correctly formatted, but ensure the host postgres_test matches your Docker Compose service name.


🏁 Script executed:

#!/bin/bash
# Check if the postgres_test service is properly defined in docker-compose files
grep -r "postgres_test" --include="docker-compose*.yml" .

Length of output: 294


Confirmed: PostgreSQL Service Name Verified
The DATABASE_URL in test/Dockerfile is correctly formatted, and our grep results confirm that the service name postgres_test is defined in your Docker Compose files (e.g., in ./docker-compose.yml and ./backend/test/setup/docker-compose.yml). No changes are necessary.

README.md (3)

104-105: Plugin name update is consistent with codebase changes

The renaming of the Telegram distributor plugin from "TelegramDist" to "Telegram" improves naming consistency.


108-109: Style update properly reflects the renamed distributor

The styling update correctly aligns with the renamed Telegram distributor plugin.


129-130: Class definition updated to reflect current plugin structure

The removal of "LinkedIn" and "TelegramDist" from the class definition list correctly reflects the current plugin structure.

test/README.md (5)

1-8: Clear introduction to the Docker-based testing approach

The README provides a good overview of the Docker-based testing infrastructure and the PostgreSQL database usage, aligning with the PostgreSQL migration objectives.


14-27: Comprehensive test run instructions

The npm scripts for running tests in Docker are well-documented with clear examples, making it easy for developers to understand how to run tests in the containerized environment.


28-32: GitHub Actions integration explanation is helpful

The mention of GitHub Actions for automated testing provides good context for the CI/CD pipeline integration.


33-38: Well-structured test organization

The test structure is clearly defined with separate directories for unit tests, integration tests, and mocks, which is a good practice for test organization.


39-44: Clear guidelines for adding new tests

The steps for adding new tests are concise and helpful, emphasizing the importance of running tests in the Docker environment.

backend/test/wait-for-db.sh (3)

1-10: Well-structured script with proper defaults

The script provides sensible defaults for host, port, and maximum attempts, with clear usage instructions in the comments.


11-29: Effective database connection verification loop

The script correctly uses pg_isready to check for database connectivity and includes appropriate timeout handling and error messages.


30-47: Database query verification and database creation logic

The script properly verifies query execution capability and creates a test database if it doesn't exist. This ensures the test environment is fully prepared.

backend/src/services/twitter/client.ts (2)

128-129: Fixed asynchronous code by adding await

Good job adding the await keyword to properly handle the asynchronous database operation. This ensures that lastCheckedTweetId is correctly assigned after the promise resolves.


135-136: Fixed asynchronous code by adding await

Correctly added the await keyword here as well to ensure proper asynchronous handling of the database operation.

backend/test/setup/global-setup.ts (4)

5-14: Good use of timers for measuring setup duration.

Wrapping the setup code in console.time("global-setup") and console.timeEnd("global-setup") clearly outlines the total time your test environment takes to initialize, which is helpful for diagnostic purposes.


47-81: Schema initialization approach looks solid.

You're properly verifying the existence of the schema file, then loading it via psql. This is a robust way to ensure the schema is created before running tests.


100-147: Graceful error handling with seed script.

Your checks for the seed file and subsequent execution with psql are well-handled. The logging of errors and rethrowing them ensures that test failures propagate correctly if the database seeding fails.


1-159: Fix Prettier formatting.

The pipeline indicates that Prettier detected formatting issues. Please run prettier --write backend/test/setup/global-setup.ts to fix any code style discrepancies.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

docker-compose.yml (3)

1-18: Local development database service appears correctly configured.

Using environment variables for POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB is a standard approach for local setups. The healthcheck ensures readiness before dependent services proceed.


19-32: Test database service is a good parallel for isolated testing.

Defining a separate postgres_test service with its own port and DB name avoids conflicts with production data during tests. This separation is a best practice.


1-65: Consider securing secrets in production workflows.

While it's typical to embed plaintext credentials for local development, ensure that any production or public environment does not rely on these same credentials or is replaced by environment variables orchestrated via a secure secrets manager.

backend/src/utils/sanitize.ts (4)

11-12: Removing BOM characters is beneficial for robust JSON parsing.

This proactive step helps handle input from sources that may inadvertently include a BOM character, preventing parsing exceptions or unrecognized symbol errors.


18-20: Good addition to handle double-quoted JSON strings.

Checking if the string starts and ends with double quotes covers the scenario where JSON has been serialized an extra time.


23-27: Recursive parse may risk infinite loops in adversarial inputs.

Repeatedly parsing nested strings can spiral into infinite recursion if an attacker crafts self-referential strings that always decode into valid JSON. While unlikely in normal usage, consider adding a recursion guard or maximum depth limit for extra safety.


1-51: Fix Prettier formatting.

As indicated by the pipeline warning, please run prettier --write backend/src/utils/sanitize.ts to correct code style issues.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/scripts/POSTGRES.md (1)

7-12: Database migration documentation looks good

The overview section clearly explains the rationale for migrating from SQLite to PostgreSQL and outlines the key components of the migration.

backend/src/index.ts (3)

43-53: Well-implemented database initialization with user feedback

The conditional database initialization provides clear user feedback through spinner UI and includes proper error handling with graceful fallback.


103-103: Good practice: Adding database disconnection to shutdown process

Including database disconnection in the shutdown process ensures clean resource release, preventing connection leaks.


1-9:

✅ Verification successful

Good environment configuration setup

The conditional loading of environment variables based on the NODE_ENV is a good practice for separating test and production configurations.


🏁 Script executed:

#!/bin/bash
# Check if the .env.test file exists
if [ -f "backend/.env.test" ]; then
  echo "backend/.env.test file exists"
  # Print file without sensitive content
  grep -v "PASSWORD\|SECRET\|KEY" backend/.env.test | grep -v "^#" | grep -v "^\s*$"
else
  echo "backend/.env.test file does not exist"
fi

Length of output: 435


Environment configuration validated and approved
The verification script confirms that the backend/.env.test file exists with the expected test configurations (excluding sensitive data). The conditional logic in backend/src/index.ts properly distinguishes between test and production environments.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/test/utils/test-helpers.ts (1)

10-15: Well-structured test server setup function

The setupTestServer function provides a clean abstraction for initializing a test environment.

backend/test/component/submission-flow.test.ts (3)

46-67: Well-structured test for basic submission flow

This test effectively verifies the core submission workflow with appropriate assertions for the expected "pending" status.


69-95: Good test for multi-feed submission

The test properly verifies submissions across multiple feeds, ensuring each receives the correct status.


97-130: Well-implemented test for moderator auto-approval

The test effectively mocks the moderator status and verifies the auto-approval workflow.

backend/src/services/db/types.ts (2)

12-12: Type change from string to Date is appropriate.

Converting createdAt from string to Date type aligns with PostgreSQL's native date handling, making the type system more accurate and enabling better date operations.


22-22: Type change from string|null to Date|null is appropriate.

The consistent conversion of date types from string to Date across the codebase supports PostgreSQL's native date handling capabilities.

backend/scripts/README.md (1)

1-125: Well-documented PostgreSQL setup with clear instructions.

The README provides comprehensive documentation for the PostgreSQL development environment, including setup instructions, prerequisites, environment configuration, and troubleshooting steps. This will greatly help developers understand the new database setup.

🧰 Tools
🪛 LanguageTool

[grammar] ~71-~71: The singular proper name ‘Singleton’ must be used with a third-person or a past tense verb.
Context: ...g and connection management - Singleton pattern for database service ## Docker Compose...

(HE_VERB_AGR)

backend/test/setup/seed/seed.sql (1)

1-54: Well-structured seed data for PostgreSQL tests.

The seed file provides a clean setup for testing with appropriate test data across multiple tables, following a logical flow of clearing existing data before inserting test records.

backend/src/__tests__/submission.service.basic.test.ts (3)

9-108: Well-structured test setup with proper mocking.

The test suite follows best practices with clean setup and teardown in beforeEach and afterEach hooks. The mocking approach for dependencies facilitates isolated testing of the SubmissionService.


110-151: Good edge case testing for blacklisted users.

This test properly verifies that submissions from blacklisted users are ignored, which is an important validation for the service's behavior.


153-194: Good edge case testing for non-existent feeds.

This test correctly verifies that submissions targeted at non-existent feeds are ignored, which is essential for maintaining data integrity.

backend/test/integration/database.test.ts (1)

1-1:

✅ Verification successful

Fix formatting issues flagged by Prettier

The pipeline has detected Prettier formatting issues in this file.

Run Prettier to fix formatting issues:


🏁 Script executed:

#!/bin/bash
# Fix formatting issues with Prettier
npx prettier --write backend/test/integration/database.test.ts

Length of output: 112


Prettier formatting issues resolved in backend/test/integration/database.test.ts

The Prettier command was successfully executed and applied the formatting fixes. No further action is required for this file.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/__tests__/submission.service.moderation.test.ts (1)

1-1:

✅ Verification successful

Fix formatting issues flagged by Prettier

The pipeline has detected Prettier formatting issues in this file.

Run Prettier to fix formatting issues:


🏁 Script executed:

#!/bin/bash
# Fix formatting issues with Prettier
npx prettier --write backend/src/__tests__/submission.service.moderation.test.ts

Length of output: 148


Prettier formatting issues in backend/src/__tests__/submission.service.moderation.test.ts have been resolved.
The file has been reformatted successfully using Prettier (completed in 142ms), so no further action is needed.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

backend/src/__tests__/twitter-client.test.ts (2)

281-282:

✅ Verification successful

Verify intentional removal of await

You've removed the await keyword from setLastCheckedTweetId calls at lines 281 and 442, suggesting the method is now synchronous. Make sure this change is intentional and aligns with the updated implementation.

Check if the setLastCheckedTweetId method is now synchronous:

Also applies to: 442-443


🏁 Script executed:

#!/bin/bash
# Check if setLastCheckedTweetId is synchronous or asynchronous
rg -A 5 "setLastCheckedTweetId" --type ts backend/src/services/twitter/client.ts

Length of output: 457


Intentional removal of await confirmed.
The shell script output shows that setLastCheckedTweetId in backend/src/services/twitter/client.ts is a synchronous method (i.e., it doesn't return a Promise and contains no asynchronous operations). The removal of the await keyword at lines 281 and 442 in the test file is therefore correct and intentional.


1-1:

✅ Verification successful

Fix formatting issues flagged by Prettier

The pipeline has detected Prettier formatting issues in this file.

Run Prettier to fix formatting issues:


🏁 Script executed:

#!/bin/bash
# Fix formatting issues with Prettier
npx prettier --write backend/src/__tests__/twitter-client.test.ts

Length of output: 118


Formatting Issues Fixed in twitter-client.test.ts

The Prettier run completed successfully, and the formatting issues in backend/src/__tests__/twitter-client.test.ts have been resolved.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

memory-bank/testingPlan.md (2)

3-15: Great introduction and thorough issue identification

This introduction effectively frames the need for a testing overhaul. The issues identified are comprehensive and align well with modern testing best practices for Node.js applications.


187-196: Good data management principles

The data management principles outlined here are excellent, particularly the emphasis on test isolation and having a clear data cleanup strategy.

🧰 Tools
🪛 LanguageTool

[duplication] ~187-~187: Possible typo: you repeated a word.
Context: ...Each test should act on its own records only - Only metadata and context data should get pr...

(ENGLISH_WORD_REPEAT_RULE)


[grammar] ~193-~193: Comparison is written ‘the same … as’.
Context: ... the DB schema using the same technique like production - Test for undesired side...

(THE_SAME_AS)

backend/src/services/submissions/submission.service.ts (2)

27-27: Consider adding a fallback for missing admin IDs.

This newly introduced await db.getTwitterCacheValue("admin_ids") call ensures that we properly await the async operation. If the cache is empty or returns an unexpected result, consider confirming that additional fallback logic or error handling is in place (for example, to fetch admin IDs from another source or skip certain behaviors).


249-257: Confirm timestamp consistency.

You are mixing originalTweet.timeParsed || new Date() for createdAt and always using new Date() for submittedAt. If the timestamps must reflect the original tweet’s timing, ensure both fields consistently rely on the same source or logic. Otherwise, clarify the difference (e.g., one is from tweet time, one from the moment the submission is saved).

backend/src/__tests__/integration/submission-distribution.test.ts (2)

27-41: Validate mocking strategy and possible expansions.

These mock definitions for DistributionService and ProcessorService are straightforward. If you plan to run more elaborate tests (e.g., verifying error handling or side effects), ensure that you add additional constraints or verifications within the mocks to confirm that methods are called with the correct parameters.


42-67: Consider more robust server initialization for test environments.

Using port 0 is a good approach to select a random free port. Keep in mind that if you need to run multiple parallel test suites, there might be concurrency to handle. Currently, your approach seems fine for single-run tests.

backend/src/services/twitter/schema.ts (1)

5-6: Ensure consistent date/time usage across the application.

You've migrated to timestamp columns in PostgreSQL. Confirm the rest of the codebase uses UTC or a consistent timezone. Otherwise, you might run into unexpected offsets or local-time mismatches when displaying or processing these fields.

backend/scripts/migrate-sqlite-to-postgres.js (1)

22-30: Confirm correct SQLite file path usage.

You conditionally use .db/submissions.sqlite if SQLITE_PATH is not set. Ensure this default path aligns with your environment. If the file doesn’t exist, the script simply exits. For clarity, you might prompt the user to create or point to a correct file path.

backend/test/component/approval-flow.test.ts (2)

1-9: Good test structure with appropriate imports.

The imports from Bun test library and supporting utility functions provide a solid foundation for the component test.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.


50-86: Well-structured approval test with proper mocking and verification.

This test effectively validates the moderator approval flow by:

  1. Setting up mock data and services
  2. Triggering the submission processing
  3. Verifying the status changes appropriately
backend/test/README.md (5)

1-11: Excellent introduction to testing categories and strategy.

The README provides a clear overview of the different types of tests and their purposes, which serves as a good reference for the team.


12-48: Comprehensive PostgreSQL testing setup documentation.

The documentation provides clear instructions for setting up and using the PostgreSQL testing environment, including commands for starting/stopping containers and running different test types.


49-83: Well-documented test environment configuration and data management.

The explanation of test environment configuration, test data seeding, and global setup/teardown processes is thorough and will help developers understand how the test infrastructure works.


84-122: Helpful example for database interaction testing.

The guidelines and example for testing database interactions are valuable for developers writing new tests. The example demonstrates good practices like data cleanup, transaction use, and helper usage.


123-129: Clear guidance on when to use mocks vs. real database.

The README concludes with useful advice on choosing between database mocks and real PostgreSQL for different types of tests, which promotes best practices in testing.

memory-bank/progress.md (3)

48-63: Updated progress tracking with PostgreSQL migration and testing infrastructure work.

The progress tracking has been appropriately updated to include the PostgreSQL migration, Docker-based development environment, and testing infrastructure overhaul. The status markers accurately reflect completed and in-progress work.


65-76: Well-documented next actions with clear completion status.

The next actions section provides a clear roadmap with properly marked completed items (using strikethrough and checkmarks) and remaining tasks for the PostgreSQL migration.


82-85: Honest assessment of testing limitations added to known issues.

Adding the note about testing limitations (heavy reliance on mocks, lack of component testing) shows awareness of technical debt and areas for improvement, which is important for project health.

backend/test/e2e/full-flow.test.ts (3)

1-44: Good E2E test setup with proper hooks.

The setup code initializes the test environment appropriately, disabling external network connections while allowing localhost connections, and includes proper cleanup.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.


45-108: Comprehensive E2E test for the full submission and approval flow.

This test effectively validates the entire flow from submission to distribution by:

  1. Setting up mock tweets and API responses
  2. Submitting a tweet and verifying it's pending
  3. Approving the submission and verifying status change
  4. Confirming distribution was called

The assertions check both HTTP response status codes and the actual data returned.


110-163: Thorough recap generation test with multiple submissions.

This test effectively validates the recap functionality by:

  1. Creating and approving multiple submissions
  2. Mocking the distribution service
  3. Triggering recap generation
  4. Verifying the distribution was called

The test provides good coverage of a complex feature.

Dockerfile (3)

1-10: Good base configuration for Bun environment

The migration from Node.js to Bun is well-implemented. Using Alpine as the base image helps keep the image size small, and enabling Corepack ensures consistent package manager versioning across environments.


10-21: Effective monorepo pruning strategy

The pruner stage is a great addition that optimizes the Docker build process by only including necessary packages from the monorepo. This approach will result in smaller, more efficient container images.


41-60: Secure production stage setup

Creating a non-root user for running the application follows security best practices. The selective copying of only necessary files from the builder stage keeps the production image clean and minimal.

backend/test/utils/test-data.ts (2)

1-26: Well-documented mock tweet factory function

The createMockTweet function is well-structured with comprehensive JSDoc comments and provides a good foundation for test data generation.


28-53: Comprehensive mock curator tweet implementation

The createMockCuratorTweet function correctly generates mock data for curator tweets with appropriate mentions and hashtags formatting.

memory-bank/activeContext.md (3)

1-56: Well-organized documentation of project focus areas

The documentation has been updated to accurately reflect the new focus on database migration and Turborepo conversion. The key considerations section effectively highlights the important aspects of these changes.


57-84: Clear documentation of architectural decisions

The updated architecture section provides a comprehensive explanation of the decision to adopt PostgreSQL with Drizzle ORM and Turborepo for monorepo management. This will be valuable for team members to understand the rationale behind these choices.


85-123: Good tracking of focus areas and progress

The documentation clearly tracks the current focus areas and next steps, with completed items appropriately marked. This provides a good roadmap for the team.

memory-bank/systemPatterns.md (2)

20-20: Great clarification on the database service.

The new description explicitly highlights PostgreSQL and Drizzle ORM, making the architecture clearer at a glance. Good move!


153-160: Good emphasis on PostgreSQL in key decisions.

Highlighting PostgreSQL as the top priority for scalability and performance aligns well with the rest of the migration changes. Great job clarifying Hono as the second key decision.

backend/src/services/db/schema.ts (4)

5-9: Switch to PostgreSQL imports looks correct.

Replacing SQLite-specific imports with pgTable, timestamp, and serial paves the way for PostgreSQL compatibility.


16-17: Correct use of timestamp fields.

Storing creation and update timestamps as proper PostgreSQL timestamps is best practice for date-time accuracy and database-level defaults.


83-83: serial is apt for auto-incrementing IDs.

Switching from integer or text to serial ensures seamless auto-increment behavior in PostgreSQL.


107-107: Appropriate change to timestamp.

Storing lastResetDate as a timestamp is preferable for queries and date-related operations.

backend/src/services/db/utils.ts (6)

1-5: Imports are well-structured.

Bringing in logger, async-retry, and the relevant PostgreSQL references is clean and straightforward.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.


6-15: Comprehensive list of retryable errors.

Defining transient errors upfront is a good strategy for robust error handling, especially for connection or network issues.


17-27: Write pool configuration seems reasonable.

Your default pool sizes, timeouts, and idle settings appear balanced for typical workloads.


29-39: Read pool configuration is well-considered.

Allocating more connections for reads typically improves throughput in read-heavy environments.


41-48: Clear retryable error detection.

Checking for known codes or nested errors provides clarity for diagnosing transient failures.


71-97: Robust execution with retry logic.

Bailing out on non-connection errors ensures you don’t retry unrecoverable states. This improves resilience.

backend/src/__tests__/mocks/db-service.mock.ts (1)

152-175: Neat override mechanism.
Overriding DatabaseService.getInstance with the mock ensures that all test code uses the mock DB service. This design is straightforward, and the restore function helps maintain a clean test environment.

package.json (2)

11-14: Monorepo workspaces look correct.
Defining “backend” and “frontend” as workspaces is consistent with a Turborepo-based layout.


16-38: Missing 'dev:pg' script reference.
The script "pg:dev" runs npm run docker:up && npm run dev:pg, but there's no dev:pg script defined here, which may cause errors. Please add the missing script or adjust the call.

Below is one possible fix if you intended to reuse the existing "dev" script:

-    "pg:dev": "npm run docker:up && npm run dev:pg",
+    "pg:dev": "npm run docker:up && npm run dev",
backend/package.json (2)

7-7: Local development and test scripts look fine.
Using nodemon with Rspack for quick rebuilds is standard, and the addition of separate test scripts (test:unit, test:component, test:integration, test:e2e) helps organize test runs.

Also applies to: 10-18


49-63: Dependency updates align with PostgreSQL migration.
Adding dependencies like pg, async-retry, and @typescript definitions for axios, nock, and pg is consistent with the new Postgres-based setup and advanced testing capabilities.

Also applies to: 82-89

memory-bank/techContext.md (3)

9-9: Great switch to PostgreSQL with Drizzle ORM.
This line clearly communicates the move from SQLite to PostgreSQL and the adoption of Drizzle ORM. No issues found here.


31-31: Corepack mention looks good.
Introducing Corepack is a beneficial choice for standardizing package manager versions. No further concerns.


176-182: Docker Compose and PostgreSQL container usage is well documented.
The new instructions for local development with Docker Compose seem clear and helpful. No changes needed.

backend/src/services/twitter/queries.ts (9)

1-1: Fix Prettier formatting issues.
The CI pipeline indicates that Prettier found style issues in this file. Please run prettier --write to resolve them.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.


2-2: Import from drizzle-orm/node-postgres is correct.
No concerns here; it aligns well with the PostgreSQL migration.


6-11: Logic to return the first row or null for getTwitterCookies.
This approach is straightforward. Ensure that the row order is stable.


14-33: setTwitterCookies transition to PostgreSQL is consistent.
The usage of .onConflictDoUpdate() suits upserts. The update of updatedAt with new Date() is also fine.


35-43: deleteTwitterCookies is clean and uses .execute().
No issues, straightforward delete operation.


46-52: getTwitterCacheValue returning first row or null.
Matches the pattern used in getTwitterCookies. No issues.


54-73: setTwitterCacheValue uses upsert logic with .onConflictDoUpdate().
This follows the same approach as setTwitterCookies. Looks consistent.


75-83: deleteTwitterCacheValue with .execute()
Straightforward deletion, no problems noticed.


85-87: clearTwitterCache truncation logic is simple.
Removes all rows. Should suffice for a cache table.

backend/scripts/init-db/01-init-schema.sql (11)

1-6: Schema initialization logic is clear.
This section documents the purpose of the script, referencing the Drizzle schema definitions. No issues here.


30-31: Indices for user_id and submitted_at.
Well done adding indexes for frequent queries. No issues.


34-42: submission_feeds table with composite primary key.
The foreign key constraints and cascade deletes look correct.


44-45: Index on feed_id.
Consistent indexing approach; good job.


58-61: Indices for moderation history.
This will help query performance. No further suggestions.


63-69: submission_counts table with integer count and text primary key.
Works for daily usage. Just ensure reset logic is handled.


71-72: Index on last_reset_date.
Helps with range queries on submission counts. Good approach.


74-81: feed_plugins table with composite PK.
Sensible design for feed-specific plugin data. Looks fine.


83-84: Indices on feed_plugins.
No concerns. Good indexing.


87-92: twitter_cookies table for storing cookies.
Sufficient for the migration to PostgreSQL. No issues noticed.


94-99: twitter_cache table for key-value storage.
Straightforward design for quick lookups. No issues.

backend/src/services/db/index.ts (40)

1-1: Prettier formatting check reminder.
The pipeline indicates code style issues in this file. Please run prettier --write to fix them.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.


2-3: Imports for PG and drizzle.
This aligns perfectly with the new PostgreSQL integration. No concerns.


6-11: Importing utility functions from ./utils.
Centralizing retry logic and default configs is a good practice.


25-29: Docstring clarifies the database service’s role.
Helpful for maintainers. No issues.


31-45: Singleton instantiation ensures a single DB service.
Prevents multiple pool instantiations. Implementation looks fine.


60-114: connect() method for read/write pool creation.
Good use of environment variables. The fallback logic DATABASE_WRITE_URL || DATABASE_URL is helpful. Keep an eye on concurrency if the environment changes at runtime.


115-130: setPoolDefaults method sets statement timeouts.
This is a good safeguard. No additional suggestions.


136-169: disconnect() logic with graceful shutdown.
Properly closes pools and sets timeouts for forced termination. Good approach.


171-183: ensureConnection() method checks readiness.
Clear separation of concerns. No changes needed.


185-201: executeWithRetry() wrapper.
Leverages the executeWithRetry utility. Good practice for transient errors.


206-223: Transactional support with BEGIN/COMMIT/ROLLBACK.
Allows atomic operations. Implementation is consistent with typical PostgreSQL usage.


225-229: saveSubmission delegates to queries.
Straightforward write operation with retry. Good.


231-235: saveModerationAction logic is similarly implemented.
No issues, follows the same pattern.


237-252: updateSubmissionFeedStatus method.
Uses a write operation for feed status updates. Implementation is consistent.


254-258: getSubmission returns the correct object or null.
Straightforward read logic.


260-266: getSubmissionByCuratorTweetId
Similar pattern to getSubmission. No issues.


268-272: getAllSubmissions optionally filters by status.
Looks correct.


274-286: getDailySubmissionCount includes cleanup of old entries.
Using two-step approach (cleanup then read) is logical.


288-292: incrementDailySubmissionCount
Straightforward write operation.


294-300: upsertFeeds
Good usage of the queries layer for batch feed updates.


302-310: saveSubmissionToFeed
Assigns a submission to a specific feed. No issues noticed.


312-316: getFeedsBySubmission
Consistently returns feed data. No further concerns.


318-322: removeFromSubmissionFeed
Handles record deletion gracefully with retries.


324-330: getSubmissionsByFeed
Logic mirrors other read operations.


333-337: getFeedPlugin
Returns plugin config or null. Looks fine.


339-347: upsertFeedPlugin
Performs an insert or update for feed plugins. Good approach.


350-363: setTwitterCookies with error handling.
Leverages withErrorHandling to ensure robust error reporting.


365-380: getTwitterCookies
Safe JSON parsing with a fallback null. Good pattern.


382-394: deleteTwitterCookies
Matches other delete patterns. No issues.


397-409: setTwitterCacheValue
Similar to setTwitterCookies, ensures writes with retries.


411-425: getTwitterCacheValue
Returns string or null, consistent with other methods.


427-439: deleteTwitterCacheValue
Straightforward removal, handled with error wrapping.


441-450: clearTwitterCache
Gets rid of all cache entries. Implementation is succinct.


452-461: getLeaderboard
Reads from queries.getLeaderboard() with error handling. Good addition.


463-485: getPostsCount and getCuratorsCount
Provides default of 0 on failure. Nice fallback.


487-525: healthCheck() method verifying read/write pools.
Great addition for readiness probes. The logs are descriptive.


528-530: Singleton instance dbInstance.
Effectively references the shared DB connection.


531-533: Export of db.
Allows external modules direct access to the singleton DB.


534-542: initializeDatabase()
Handles connect and logs success/failure. Good design.


544-547: getDatabase()
Simple helper to retrieve the same singleton instance. No issues.

backend/src/services/db/queries.ts (12)

83-101: LGTM for retrieving feeds by submission.
The flow looks correct: retrieving associated feeds for a submission is straightforward. No obvious issues.


103-122: Handle potential primary key conflicts.
In saveSubmission, there's no conflict handling (e.g., .onConflictDoNothing or .onConflictDoUpdate), so an existing submission with the same tweetId may cause an error. Confirm that this is the intended behavior or add conflict handling if needed.


124-139: Straightforward moderation history insertion.
Appending moderation actions to moderationHistory without conflict handling is logical since you likely want multiple moderation records per tweet. No immediate concerns found.


141-175: Evaluate potential duplicated row joins.
getModerationHistory left-joins with submissionFeeds. If a submission has multiple feed records, you could receive duplicate rows. Validate whether deduplication or grouping is needed.


177-198: Ensure zero-row updates are handled.
When no matching record exists in updateSubmissionFeedStatus, the update might silently affect 0 rows. If that case demands special handling (e.g., error or insert), consider verifying the row count or upserting.


200-271: Check for duplicate feed entries.
getSubmissionByCuratorTweetId aggregates feed-based data from multiple joins. If a submission extends across multiple feeds, ensure the final data structure is correct and does not inadvertently merge or duplicate moderation references.


273-344: Consistent data retrieval.
getSubmission mirrors logic in getSubmissionByCuratorTweetId. The approach looks correct for returning a single consolidated submission object. No additional issues flagged.


619-633: Straightforward removal from feed.
removeFromSubmissionFeed deletes by submission and feed IDs without extraneous queries. Implementation and error handling look good.


636-649: Confirm plugin retrieval behavior.
getFeedPlugin returns null if not found, which is appropriate. Implementation is concise and correct for a single key lookup.


651-672: Upsert feed plugin logic looks correct.
upsertFeedPlugin includes .onConflictDoUpdate and updates updatedAt. This is aligned with typical upsert patterns. Good job.


702-711: Curator counts approach is acceptable.
getCuratorsCount queries unique curator_id from submissions. Straightforward and correct, no issues flagged.


1-876: Address Prettier code style issues.
The pipeline indicates a Prettier formatting failure for this file. Please run prettier --write backend/src/services/db/queries.ts (or the appropriate command for your environment) to fix styling and pass the CI check.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

@@ -122,7 +122,7 @@ export class TwitterSourcePlugin {
) {
throw new Error("Invalid tweetId format");
}
await this.twitterService.setLastCheckedTweetId(body.tweetId);
this.twitterService.setLastCheckedTweetId(body.tweetId);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing await for asynchronous database operation

The await keyword has been removed from the call to setLastCheckedTweetId, but this method performs an asynchronous database operation. Without awaiting, the handler will return a response before the database update completes, and any errors that occur won't be caught properly.

-  this.twitterService.setLastCheckedTweetId(body.tweetId);
+  await this.twitterService.setLastCheckedTweetId(body.tweetId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.twitterService.setLastCheckedTweetId(body.tweetId);
await this.twitterService.setLastCheckedTweetId(body.tweetId);

Comment on lines 1 to 25
import { execSync } from "child_process";
import { join } from "path";

export default async () => {
console.time("global-teardown");

// Stop and remove the PostgreSQL container
try {
const dockerComposePath = join(__dirname, "docker-compose.yml");

// Only stop the container in CI environment to keep it running for local debugging
if (process.env.CI) {
console.log("Stopping PostgreSQL container...");
execSync(`docker-compose -f ${dockerComposePath} down -v`, { stdio: 'inherit' });
console.log("PostgreSQL container stopped and removed");
} else {
console.log("Skipping PostgreSQL container cleanup in local environment");
console.log("To stop the container manually, run: docker-compose -f backend/test/setup/docker-compose.yml down -v");
}
} catch (error) {
console.error("Error during teardown:", error);
}

console.timeEnd("global-teardown");
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix formatting issues and enhance error handling

The teardown script correctly handles CI vs local environments. However, there are some improvements needed:

The pipeline failure indicates Prettier formatting issues that need to be addressed. Run Prettier to fix formatting:

prettier --write backend/test/setup/global-teardown.ts

Enhance error handling to provide more specific error information and check if Docker Compose is available:

  try {
    const dockerComposePath = join(__dirname, "docker-compose.yml");
    
    // Only stop the container in CI environment to keep it running for local debugging
    if (process.env.CI) {
+     try {
+       // Check if docker-compose is available
+       execSync('docker-compose --version', { stdio: 'ignore' });
+     } catch (error) {
+       console.warn("Docker Compose not found, skipping container cleanup");
+       return;
+     }
      console.log("Stopping PostgreSQL container...");
      execSync(`docker-compose -f ${dockerComposePath} down -v`, { stdio: 'inherit' });
      console.log("PostgreSQL container stopped and removed");
    } else {
      console.log("Skipping PostgreSQL container cleanup in local environment");
      console.log("To stop the container manually, run: docker-compose -f backend/test/setup/docker-compose.yml down -v");
    }
  } catch (error) {
-   console.error("Error during teardown:", error);
+   console.error("Error during teardown:", error instanceof Error ? error.message : String(error));
  }
🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.


## Troubleshooting

See the detailed troubleshooting guide in `scripts/README.md`.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify troubleshooting guide reference

The documentation references a troubleshooting guide in scripts/README.md. Ensure this file exists and contains the relevant troubleshooting information.


🏁 Script executed:

#!/bin/bash
# Check if the referenced troubleshooting guide exists
if [ -f "scripts/README.md" ]; then
  echo "scripts/README.md exists"
  grep -A 10 -i "troubleshoot" scripts/README.md
else
  echo "scripts/README.md does not exist"
fi

Length of output: 105


Troubleshooting Guide Reference Issue Found

The reference to the troubleshooting guide at scripts/README.md is broken as the file does not exist. Please either add the missing scripts/README.md file with the relevant troubleshooting information or update the reference in backend/scripts/POSTGRES.md (line 94) to point to the correct file.

Comment on lines +146 to +147
await submissionService.startMentionsCheck();
await new Promise((resolve) => setTimeout(resolve, 100));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid using setTimeout for test synchronization

Using setTimeout with a hardcoded delay for test synchronization can be brittle and cause intermittent test failures.

Replace the setTimeout with a more predictable approach:

- await submissionService.startMentionsCheck();
- await new Promise((resolve) => setTimeout(resolve, 100));
+ await submissionService.startMentionsCheck();
+ // Create a helper that polls for completed state or uses events
+ await waitForProcessingToComplete(mockProcessorService);

// Add this helper function to your test utilities
function waitForProcessingToComplete(processorService, maxWaitTime = 1000) {
  const startTime = Date.now();
  return new Promise((resolve, reject) => {
    const checkProcessing = () => {
      // Either check a "processing completed" flag or use the length of processed items
      if (processorService.processedItems.length > 0 || 
          (processorService.isProcessingComplete && processorService.isProcessingComplete())) {
        resolve();
        return;
      }
      
      if (Date.now() - startTime > maxWaitTime) {
        reject(new Error('Timed out waiting for processing to complete'));
        return;
      }
      
      setTimeout(checkProcessing, 10);
    };
    
    checkProcessing();
  });
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await submissionService.startMentionsCheck();
await new Promise((resolve) => setTimeout(resolve, 100));
await submissionService.startMentionsCheck();
// Create a helper that polls for completed state or uses events
await waitForProcessingToComplete(mockProcessorService);
// Add this helper function to your test utilities
function waitForProcessingToComplete(processorService, maxWaitTime = 1000) {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const checkProcessing = () => {
// Either check a "processing completed" flag or use the length of processed items
if (processorService.processedItems.length > 0 ||
(processorService.isProcessingComplete && processorService.isProcessingComplete())) {
resolve();
return;
}
if (Date.now() - startTime > maxWaitTime) {
reject(new Error('Timed out waiting for processing to complete'));
return;
}
setTimeout(checkProcessing, 10);
};
checkProcessing();
});
}

Comment on lines 1 to 164
nock("http://localhost")
.post("/api/distribution")
.reply(200, { success: true });

// Act - Submit tweet
const submissionResponse = await apiClient.post("/api/test/twitter/mention", {
tweet: curatorTweet,
});

// Assert submission
expect(submissionResponse.status).toBe(200);

// Verify the submission was created
const submissionStatusResponse = await apiClient.get(
`/api/submission/single/${tweet.id}`,
);
expect(submissionStatusResponse.status).toBe(200);
expect(submissionStatusResponse.data).toMatchObject({
tweetId: tweet.id,
status: "pending",
});

// Act - Approve submission
const moderatorTweet = createMockModeratorTweet(curatorTweet.id, "approve");
const approvalResponse = await apiClient.post("/api/test/twitter/mention", {
tweet: moderatorTweet,
});

// Assert approval
expect(approvalResponse.status).toBe(200);

// Verify the submission was approved
const approvedSubmissionResponse = await apiClient.get(
`/api/submission/single/${tweet.id}`,
);
expect(approvedSubmissionResponse.status).toBe(200);
expect(approvedSubmissionResponse.data).toMatchObject({
tweetId: tweet.id,
status: "approved",
});

// Verify distribution was called
expect(nock.isDone()).toBe(true);
});

test("Recap generation and distribution flow", async () => {
// Arrange - Create multiple approved submissions
const tweets = [createMockTweet(), createMockTweet(), createMockTweet()];

// Mock Twitter API for all tweets
for (const tweet of tweets) {
nock("https://api.twitter.com")
.get(`/tweets/${tweet.id}`)
.reply(200, tweet);

// Submit and approve each tweet
const curatorTweet = createMockCuratorTweet(tweet.id);

await apiClient.post("/api/test/twitter/mention", {
tweet: curatorTweet,
});

// Mock the moderator list
nock("http://localhost")
.get(/\/api\/feed\/.*\/moderators/)
.reply(200, {
moderators: [
{
userId: "moderator_id",
username: "moderator",
},
],
});

const moderatorTweet = createMockModeratorTweet(
curatorTweet.id,
"approve",
);
await apiClient.post("/api/test/twitter/mention", {
tweet: moderatorTweet,
});
}

// Mock distribution service for recap
nock("http://localhost")
.post("/api/distribution/recap")
.reply(200, { success: true });

// Act - Generate recap
const recapResponse = await apiClient.post("/api/feed/test-feed/recap", {
timeframe: "daily",
});

// Assert
expect(recapResponse.status).toBe(200);

// Verify distribution was called
expect(nock.isDone()).toBe(true);
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix Prettier formatting issues.

The pipeline failure indicates formatting issues in this file. Run Prettier to ensure consistent code style.

npx prettier --write backend/test/e2e/full-flow.test.ts
🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Prettier formatting check failed. Code style issues found in this file. Run 'prettier --write' to fix code style issues.

Comment on lines +575 to 591
export async function getDailySubmissionCount(
db: NodePgDatabase<any>,
userId: string,
date: string,
): number {
const result = db
): Promise<number> {
const results = await db
.select({ count: submissionCounts.count })
.from(submissionCounts)
.where(
and(
eq(submissionCounts.userId, userId),
eq(submissionCounts.lastResetDate, date),
eq(submissionCounts.lastResetDate, sql.raw(`'${date}'`)),
),
)
.get();
);

return result?.count ?? 0;
return results.length > 0 ? results[0].count : 0;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Double-check string interpolation in date comparison.
Similarly, getDailySubmissionCount uses sql.raw(\'${date}'`). Use parameter binding or typed date comparisons to mitigate injection risks if date` can originate from untrusted sources.

Comment on lines +565 to 573
export async function cleanupOldSubmissionCounts(
db: NodePgDatabase<any>,
date: string,
) {
return db
): Promise<void> {
await db
.delete(submissionCounts)
.where(sql`${submissionCounts.lastResetDate} < ${date}`)
.run();
.where(sql`${submissionCounts.lastResetDate} < ${sql.raw(`'${date}'`)}`)
.execute();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Parametrize date to avoid injection.
Using sql.raw(\'${date}'`)incleanupOldSubmissionCountscan be risky ifdate` is untrusted input. Switch to parameter binding or verified typed values to prevent potential SQL injection.

Apply this change for safer code:

- .where(sql`${submissionCounts.lastResetDate} < ${sql.raw(`'${date}'`)}`)
+ .where(sql`${submissionCounts.lastResetDate} < ${date}`)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function cleanupOldSubmissionCounts(
db: NodePgDatabase<any>,
date: string,
) {
return db
): Promise<void> {
await db
.delete(submissionCounts)
.where(sql`${submissionCounts.lastResetDate} < ${date}`)
.run();
.where(sql`${submissionCounts.lastResetDate} < ${sql.raw(`'${date}'`)}`)
.execute();
}
export async function cleanupOldSubmissionCounts(
db: NodePgDatabase<any>,
date: string,
): Promise<void> {
await db
.delete(submissionCounts)
.where(sql`${submissionCounts.lastResetDate} < ${date}`)
.execute();
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (2)
backend/src/services/db/queries.ts (2)

602-626: ⚠️ Potential issue

SQL injection vulnerability in date comparison for daily submission count.

The same pattern of unsafe string interpolation appears here as well.

Apply this change for safety:

- WHEN ${submissionCounts.lastResetDate} < ${sql.raw(`'${today}'`)} THEN 1
+ WHEN ${submissionCounts.lastResetDate} < ${today} THEN 1

724-803: 🛠️ Refactor suggestion

Inefficient database querying pattern in getLeaderboard.

The function makes a separate database query for each curator inside a loop, which could cause performance issues as the number of curators grows.

Refactor to use a single JOIN query instead of multiple queries in a loop:

- for (const curator of curators) {
-   const curatorFeedsResult = await db.execute(sql`
-     SELECT 
-       sf.feed_id AS feedid,
-      COUNT(DISTINCT sf.submission_id) AS count
-     FROM 
-       submission_feeds sf
-     JOIN 
-       submissions s ON sf.submission_id = s.tweet_id
-     WHERE 
-       s.curator_id = ${curator.curatorId}
-     GROUP BY 
-       sf.feed_id
-   `);
-
-   // Convert to FeedSubmissionCount array with total counts
-   const feedSubmissions: FeedSubmissionCount[] = curatorFeedsResult.rows.map(
-     (row) => ({
-       feedId: String(row.feedid),
-       count: Number(row.count),
-       totalInFeed: feedTotalsMap.get(String(row.feedid)) || 0,
-     }),
-   );
-   
-   // Sort by count (highest first)
-   feedSubmissions.sort((a, b) => b.count - a.count);
-
-   result.push({
-     curatorId: curator.curatorId,
-     curatorUsername: curator.curatorUsername,
-     submissionCount: curator.submissionCount,
-     feedSubmissions,
-   });
- }

+ // Get all curator-feed submissions in one query
+ const allCuratorFeedsResult = await db.execute(sql`
+   SELECT 
+     s.curator_id AS curatorid,
+     sf.feed_id AS feedid,
+     COUNT(DISTINCT sf.submission_id) AS count
+   FROM 
+     submission_feeds sf
+   JOIN 
+     submissions s ON sf.submission_id = s.tweet_id
+   GROUP BY 
+     s.curator_id, sf.feed_id
+ `);
+
+ // Create a map of curator_id -> feedSubmissions
+ const curatorFeedsMap = new Map<string, FeedSubmissionCount[]>();
+ 
+ for (const row of allCuratorFeedsResult.rows) {
+   const curatorId = String(row.curatorid);
+   const feedId = String(row.feedid);
+   const count = Number(row.count);
+   
+   if (!curatorFeedsMap.has(curatorId)) {
+     curatorFeedsMap.set(curatorId, []);
+   }
+   
+   curatorFeedsMap.get(curatorId)!.push({
+     feedId,
+     count,
+     totalInFeed: feedTotalsMap.get(feedId) || 0
+   });
+ }
+
+ // Build the final result
+ for (const curator of curators) {
+   const feedSubmissions = curatorFeedsMap.get(curator.curatorId) || [];
+   // Sort by count (highest first)
+   feedSubmissions.sort((a, b) => b.count - a.count);
+   
+   result.push({
+     curatorId: curator.curatorId,
+     curatorUsername: curator.curatorUsername,
+     submissionCount: curator.submissionCount,
+     feedSubmissions,
+   });
+ }
♻️ Duplicate comments (3)
backend/test/setup/global-setup.ts (1)

176-180: 🛠️ Refactor suggestion

Move pool.end() to a finally block to prevent resource leaks.

When an error is thrown within the try block, the pool.end() call at line 176 will be skipped, potentially leaving the database connection open and causing resource leaks.

Apply this change to ensure the connection is always closed:

  } catch (error) {
    console.error("Error setting up test environment:", error);
    throw error; // Rethrow to fail the tests if setup fails
+  } finally {
+    // Ensure the pool is closed even if an error occurs
+    if (pool) {
+      await pool.end();
+    }
  }
backend/src/services/db/queries.ts (2)

574-581: ⚠️ Potential issue

Potential SQL injection vulnerability in date comparison.

Using string interpolation with sql.raw() for date values creates a SQL injection risk.

Use parameterized queries instead:

- .where(sql`${submissionCounts.lastResetDate} < ${sql.raw(`'${date}'`)}`)
+ .where(sql`${submissionCounts.lastResetDate} < ${date}`)

584-596: ⚠️ Potential issue

Another SQL injection risk in date comparison.

Similar to the previous issue, using string interpolation with sql.raw() for date values is unsafe.

Fix this with proper parameterization:

- eq(submissionCounts.lastResetDate, sql.raw(`'${date}'`)),
+ eq(submissionCounts.lastResetDate, date),
🧹 Nitpick comments (7)
backend/src/utils/sanitize.ts (1)

22-30: Strong improvement with recursive sanitization.

The recursive approach to handling nested JSON strings is an excellent enhancement. This handles the case where JSON has been stringified multiple times, making the function much more robust for real-world scenarios.

One minor suggestion to consider:

// If the result is still a string that looks like JSON, recursively parse it
if (typeof parsed === "string") {
+  // Recursively sanitize to handle multi-level stringification
  return sanitizeJson(parsed);
}
backend/test/integration/database.test.ts (1)

251-277: Enhance concurrent operations test with state verification.

While this test ensures operations can run concurrently without errors, it doesn't verify the resulting state of the database.

Consider adding assertions to verify the database state after concurrent operations:

  // Assert - No errors should be thrown
  await Promise.all(operations);
+ 
+ // Verify the final state after concurrent operations
+ const updatedSubmission = await db.getSubmission(submission.tweetId);
+ const feeds = await db.getFeedsBySubmission(submission.tweetId);
+ const feedSubmissions = await db.getSubmissionsByFeed(feedId);
+ 
+ expect(feeds.length).toBe(1);
+ expect(feeds[0].status).toBe(SubmissionStatus.APPROVED);
+ expect(feedSubmissions.some(s => s.tweetId === submission.tweetId)).toBe(true);
backend/scripts/migrate-sqlite-to-postgres.js (2)

70-90: Consider a batch or chunk-based migration strategy for large tables.

Reading and inserting all submissions at once may cause performance or memory issues for extremely large datasets. Adopting a chunk-based approach can help avoid memory overload and improve scalability.


243-247: Re-examine exit on error in production environments.

Calling process.exit(1) immediately terminates the process, which might be acceptable for a one-off migration script. However, if the script is repurposed as part of an automated pipeline, a more graceful error-handling approach could allow for retries or partial successes.

backend/src/__tests__/mocks/db-service.mock.ts (1)

164-178: Beware of global state in unit tests.

Overriding DatabaseService.getInstance at the global level can cause race conditions if tests run concurrently. Consider scoping or restoring it in setup/teardown hooks to avoid interference across test suites.

backend/src/services/db/queries.ts (2)

741-746: Use type assertions consistently for database results.

The current approach uses String() and Number() conversions. While functional, this is inconsistent with the TypeScript approach used elsewhere.

Consider using type assertions that better align with TypeScript patterns:

- const curators = curatorsResult.rows.map((row) => ({
-   curatorId: String(row.curatorid),
-   curatorUsername: String(row.curatorusername),
-   submissionCount: Number(row.submissioncount),
- }));
+ const curators = curatorsResult.rows.map((row) => ({
+   curatorId: row.curatorid as string,
+   curatorUsername: row.curatorusername as string,
+   submissionCount: parseInt(row.submissioncount as string, 10),
+ }));

123-125: Avoid using as any type assertions.

Using as any bypasses TypeScript's type checking, which can lead to runtime errors.

Define proper types for the database schema or use more specific type assertions:

- } as any)
+ } as Omit<typeof submissions.$inferInsert, 'updatedAt'>)

Also applies to: 140-141

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d432685 and a864dc4.

📒 Files selected for processing (24)
  • backend/drizzle.config.ts (1 hunks)
  • backend/scripts/migrate-sqlite-to-postgres.js (1 hunks)
  • backend/src/__tests__/integration/submission-distribution.test.ts (1 hunks)
  • backend/src/__tests__/mocks/db-service.mock.ts (1 hunks)
  • backend/src/__tests__/setup-test-env.ts (1 hunks)
  • backend/src/__tests__/submission.service.moderation.test.ts (1 hunks)
  • backend/src/__tests__/twitter-client.test.ts (7 hunks)
  • backend/src/index.ts (3 hunks)
  • backend/src/services/db/index.ts (2 hunks)
  • backend/src/services/db/queries.ts (26 hunks)
  • backend/src/services/db/utils.ts (1 hunks)
  • backend/src/services/distribution/distribution.service.ts (1 hunks)
  • backend/src/services/submissions/submission.service.ts (6 hunks)
  • backend/src/services/twitter/client.ts (1 hunks)
  • backend/src/services/twitter/queries.ts (3 hunks)
  • backend/src/types/bun-test.d.ts (1 hunks)
  • backend/src/utils/sanitize.ts (1 hunks)
  • backend/test/component/approval-flow.test.ts (1 hunks)
  • backend/test/component/submission-flow.test.ts (1 hunks)
  • backend/test/e2e/full-flow.test.ts (1 hunks)
  • backend/test/integration/database.test.ts (1 hunks)
  • backend/test/setup/global-setup.ts (1 hunks)
  • backend/test/setup/global-teardown.ts (1 hunks)
  • backend/test/utils/test-helpers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (14)
  • backend/src/services/distribution/distribution.service.ts
  • backend/src/index.ts
  • backend/drizzle.config.ts
  • backend/test/setup/global-teardown.ts
  • backend/src/tests/submission.service.moderation.test.ts
  • backend/src/tests/integration/submission-distribution.test.ts
  • backend/src/services/twitter/client.ts
  • backend/src/tests/setup-test-env.ts
  • backend/test/utils/test-helpers.ts
  • backend/src/services/submissions/submission.service.ts
  • backend/test/e2e/full-flow.test.ts
  • backend/src/types/bun-test.d.ts
  • backend/src/services/twitter/queries.ts
  • backend/src/tests/twitter-client.test.ts
🧰 Additional context used
🧬 Code Definitions (4)
backend/test/component/approval-flow.test.ts (2)
backend/test/utils/test-helpers.ts (3) (3)
  • setupTestServer (10-15)
  • cleanupTestServer (21-24)
  • mockTwitterSearchTimeline (31-98)
backend/test/utils/test-data.ts (3) (3)
  • createMockTweet (10-26)
  • createMockCuratorTweet (34-53)
  • createMockModeratorTweet (61-80)
backend/src/__tests__/mocks/db-service.mock.ts (1)
backend/src/services/db/index.ts (1) (1)
  • DatabaseService (30-543)
backend/src/services/db/index.ts (1)
backend/src/services/db/utils.ts (4) (4)
  • DEFAULT_WRITE_POOL_CONFIG (32-39)
  • DEFAULT_READ_POOL_CONFIG (44-51)
  • executeWithRetry (96-113)
  • withErrorHandling (123-151)
backend/src/services/db/queries.ts (4)
backend/src/services/db/index.ts (13) (13)
  • upsertFeeds (302-308)
  • db (549-549)
  • saveSubmissionToFeed (310-318)
  • getFeedsBySubmission (320-324)
  • saveSubmission (231-235)
  • saveModerationAction (237-241)
  • updateSubmissionFeedStatus (243-258)
  • getSubmissionByCuratorTweetId (266-272)
  • getSubmission (260-264)
  • getAllSubmissions (274-280)
  • getDailySubmissionCount (282-294)
  • incrementDailySubmissionCount (296-300)
  • getSubmissionsByFeed (335-344)
backend/src/services/db/schema.ts (8) (8)
  • feeds (31-36)
  • SubmissionStatus (20-24)
  • SubmissionStatus (26-27)
  • submissions (38-56)
  • submissionFeeds (58-78)
  • moderationHistory (80-100)
  • submissionCounts (102-111)
  • feedPlugins (113-128)
backend/src/types/twitter.ts (6) (6)
  • SubmissionStatus (56-60)
  • SubmissionStatus (62-63)
  • SubmissionFeed (65-70)
  • TwitterSubmission (8-21)
  • Moderation (29-37)
  • TwitterSubmissionWithFeedData (23-27)
frontend/src/lib/api.ts (2) (2)
  • LeaderboardEntry (90-95)
  • FeedSubmissionCount (84-88)
🔇 Additional comments (34)
backend/src/utils/sanitize.ts (3)

11-13: Excellent improvement to handle BOM characters.

Adding BOM removal is a great enhancement that prevents parsing issues when processing JSON from various sources. The byte order mark can cause unexpected behavior in JSON parsing, and removing it proactively improves robustness.


15-15: Good follow-through using the cleaned string.

Consistently using the BOM-free cleanString for all subsequent operations ensures that the sanitization process is thorough and maintains data integrity.


18-20: Well-implemented support for double stringified JSON.

The addition of a check for quoted JSON strings enhances the function's ability to handle data that has been serialized multiple times. This is a common scenario in systems that pass data through multiple services or storage layers.

backend/test/component/submission-flow.test.ts (4)

18-45: LGTM: Well-structured test setup with proper isolation.

The test suite correctly initializes the server, manages API client, and disables external network requests while allowing localhost connections. The cleanup in both beforeEach and afterEach hooks ensures proper isolation between tests.


46-67: LGTM: Thorough test for tweet submission verification.

This test case effectively verifies that a submitted tweet is saved with the correct "pending" status. The mock setup and verification logic are well-structured.


69-95: LGTM: Comprehensive test for multi-feed submission.

The test properly verifies that submissions to multiple feeds are correctly saved with the appropriate status for each feed.


97-130: LGTM: Complete test for moderator auto-approval.

This test correctly verifies the automatic approval of submissions when the curator is also a moderator. Good use of mocking to simulate the moderator status check.

backend/test/component/approval-flow.test.ts (3)

22-48: LGTM: Proper test lifecycle hooks.

The setup and teardown hooks ensure clean test environment. Disabling external network requests while allowing localhost is a good security practice.


50-86: LGTM: Thorough test for moderator approval flow.

The test correctly verifies that a moderator can approve a submission, changing its status to "approved".


88-125: LGTM: Comprehensive test for submission rejection.

This test properly verifies the rejection flow when a moderator rejects a submission.

backend/test/integration/database.test.ts (8)

14-62: LGTM: Thorough database setup for integration testing.

The setup process properly initializes the database connection, performs health checks, and verifies table existence before running tests. Good error handling with detailed logging.


64-78: LGTM: Clean resource management in test teardown.

The afterAll hook properly closes both the PostgreSQL pool and database service connection, with appropriate error handling.


80-91: LGTM: Effective test isolation.

The beforeEach hook ensures a clean state for each test by deleting all relevant data from the test tables.


93-108: LGTM: Well-structured basic CRUD test.

This test effectively verifies the basic save and retrieve functionality for submissions.


110-139: LGTM: Comprehensive status update test.

The test properly validates that submission feed statuses can be updated correctly.


140-169: LGTM: Complete retrieval test.

This test thoroughly verifies that submissions can be retrieved by feed.


171-221: LGTM: Robust filtering test.

The test effectively verifies retrieval of all submissions and filtering by status.


223-249: LGTM: Comprehensive feed operations test.

This test properly validates adding and removing submissions from feeds.

backend/scripts/migrate-sqlite-to-postgres.js (1)

38-38: Avoid logging full connection string to protect sensitive credentials.

Please revisit the recommendation to mask or obfuscate the password. Logging the entire connection string can potentially expose secrets in logs.

backend/src/services/db/utils.ts (2)

57-61: Validate coverage of all retryable scenarios.

Currently, isRetryableError checks for an error code property, but some network or driver errors might not follow the same pattern. Confirm that critical transient errors (e.g., SSL handshake failures) are covered or handle them separately.


66-85: Good use of a structured retry policy.

The configurable retry strategy with exponential backoff provides flexibility for transient failures and helps prevent flooding the database with rapid-fire retries. This is a robust approach.

backend/src/__tests__/mocks/db-service.mock.ts (1)

108-113: Note the mock transaction doesn’t replicate real atomic behavior.

Since it just executes operations without true commit/rollback, code relying on strict transactional semantics might behave differently under tests.

backend/src/services/db/index.ts (11)

1-11: Well-structured imports and utility function integration.

The code properly imports PostgreSQL-specific types and functionality from pg and drizzle-orm, while also importing utility functions for error handling and retry logic. This sets a good foundation for the PostgreSQL migration.


26-29: Good class documentation.

The class documentation clearly indicates the purpose of the service and mentions the read/write separation pattern, which is a PostgreSQL best practice.


33-40: Excellent separation of read and write pools.

The separate connection pools for read and write operations allow for better performance optimization, with the ability to handle reads and writes differently. This is a significant improvement over the previous SQLite implementation and follows PostgreSQL best practices.


64-118: Robust connection establishment with proper error handling.

The connect method properly sets up both read and write pools with appropriate defaults, adds event listeners for error handling, and initializes Drizzle instances. The use of environment variables with fallbacks is also well implemented.


123-136: Well-implemented pool defaults configuration.

This private method ensures consistent configuration across pool connections, particularly for setting statement timeouts which is crucial for preventing long-running queries.


142-175: Thorough connection cleanup with failsafe mechanism.

The disconnect method includes both graceful shutdown and a timeout-based force close, ensuring resources are properly released even if the graceful shutdown fails. This prevents connection leaks.


198-206: Effective use of retry logic for database operations.

The executeWithRetry method elegantly encapsulates retry logic for transient errors, leveraging the imported utility function. This improves resilience against temporary database connectivity issues.


212-229: Properly implemented transaction support.

The transaction method correctly handles the transaction lifecycle (BEGIN, COMMIT, ROLLBACK) and ensures the client is always released, even in error scenarios.


231-258: Consistent method implementation pattern across database operations.

All CRUD operations follow a consistent pattern using executeWithRetry with proper async/await, clearly indicating whether they're read or write operations. This uniformity makes the code more maintainable.

Also applies to: 260-300, 335-344


504-542: Valuable health check implementation for monitoring database performance.

The healthCheck method provides important metrics about database connectivity and performance, which will be essential for monitoring in a production PostgreSQL environment.


545-564: Clean singleton management and initialization.

The singleton pattern is properly implemented with both automatic initialization (db instance) and explicit initialization (initializeDatabase), which provides flexibility for different usage scenarios.

backend/src/services/db/queries.ts (1)

21-45: Good transaction handling for bulk operations.

The upsertFeeds function properly uses a transaction to ensure atomic operations when inserting multiple feeds, which is critical for data consistency.

Comment on lines +127 to +170
test("When a non-moderator tries to approve a submission, it should be ignored", async () => {
// Arrange
const tweet = createMockTweet();
const curatorTweet = createMockCuratorTweet(tweet.id);

// Mock Twitter API for the original tweet
nock("https://api.twitter.com")
.get(`/tweets/${tweet.id}`)
.reply(200, tweet);

mockTwitterSearchTimeline([tweet, curatorTweet]);

// Trigger the checkMentions method to process the submission
await server.context.submissionService["checkMentions"]();

// Mock the moderator list to return empty (non-moderator)
nock("http://localhost")
.get(/\/api\/feed\/.*\/moderators/)
.reply(200, {
moderators: [],
});

// Create a non-moderator tweet for approval
const nonModeratorTweet = {
...createMockModeratorTweet(curatorTweet.id, "approve"),
username: "non_moderator",
userId: "non_moderator_id",
};

mockTwitterSearchTimeline([nonModeratorTweet]);

// Trigger the checkMentions method again to process the non-moderator tweet
await server.context.submissionService["checkMentions"]();

// Verify the submission was not approved (still pending)
const submissionResponse = await apiClient.get(
`/api/submission/single/${tweet.id}`,
);
expect(submissionResponse.status).toBe(200);
expect(submissionResponse.data).toMatchObject({
tweetId: tweet.id,
status: "pending", // Still pending, not approved
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Update mock in non-moderator approval test for consistency.

The test for non-moderator approval attempts is missing the Tweet and CuratorTweet in the mockTwitterSearchTimeline call at line 156.

- mockTwitterSearchTimeline([nonModeratorTweet]);
+ mockTwitterSearchTimeline([tweet, curatorTweet, nonModeratorTweet]);

This ensures the test environment more accurately reflects real-world conditions where all tweets would be present in the timeline.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("When a non-moderator tries to approve a submission, it should be ignored", async () => {
// Arrange
const tweet = createMockTweet();
const curatorTweet = createMockCuratorTweet(tweet.id);
// Mock Twitter API for the original tweet
nock("https://api.twitter.com")
.get(`/tweets/${tweet.id}`)
.reply(200, tweet);
mockTwitterSearchTimeline([tweet, curatorTweet]);
// Trigger the checkMentions method to process the submission
await server.context.submissionService["checkMentions"]();
// Mock the moderator list to return empty (non-moderator)
nock("http://localhost")
.get(/\/api\/feed\/.*\/moderators/)
.reply(200, {
moderators: [],
});
// Create a non-moderator tweet for approval
const nonModeratorTweet = {
...createMockModeratorTweet(curatorTweet.id, "approve"),
username: "non_moderator",
userId: "non_moderator_id",
};
mockTwitterSearchTimeline([nonModeratorTweet]);
// Trigger the checkMentions method again to process the non-moderator tweet
await server.context.submissionService["checkMentions"]();
// Verify the submission was not approved (still pending)
const submissionResponse = await apiClient.get(
`/api/submission/single/${tweet.id}`,
);
expect(submissionResponse.status).toBe(200);
expect(submissionResponse.data).toMatchObject({
tweetId: tweet.id,
status: "pending", // Still pending, not approved
});
});
test("When a non-moderator tries to approve a submission, it should be ignored", async () => {
// Arrange
const tweet = createMockTweet();
const curatorTweet = createMockCuratorTweet(tweet.id);
// Mock Twitter API for the original tweet
nock("https://api.twitter.com")
.get(`/tweets/${tweet.id}`)
.reply(200, tweet);
mockTwitterSearchTimeline([tweet, curatorTweet]);
// Trigger the checkMentions method to process the submission
await server.context.submissionService["checkMentions"]();
// Mock the moderator list to return empty (non-moderator)
nock("http://localhost")
.get(/\/api\/feed\/.*\/moderators/)
.reply(200, {
moderators: [],
});
// Create a non-moderator tweet for approval
const nonModeratorTweet = {
...createMockModeratorTweet(curatorTweet.id, "approve"),
username: "non_moderator",
userId: "non_moderator_id",
};
mockTwitterSearchTimeline([tweet, curatorTweet, nonModeratorTweet]);
// Trigger the checkMentions method again to process the non-moderator tweet
await server.context.submissionService["checkMentions"]();
// Verify the submission was not approved (still pending)
const submissionResponse = await apiClient.get(
`/api/submission/single/${tweet.id}`,
);
expect(submissionResponse.status).toBe(200);
expect(submissionResponse.data).toMatchObject({
tweetId: tweet.id,
status: "pending", // Still pending, not approved
});
});

Comment on lines +123 to +151
export async function withErrorHandling<T>(
operation: () => Promise<T>,
context: {
operationName: string;
errorMessage?: string;
additionalContext?: Record<string, any>;
},
defaultValue?: T,
): Promise<T> {
try {
return await operation();
} catch (error: any) {
const { operationName, errorMessage, additionalContext } = context;

logger.error(`Failed to ${operationName}:`, {
error: error.message,
code: error.code,
...additionalContext,
});

if (defaultValue !== undefined) {
return defaultValue;
}

throw new Error(
errorMessage || `Failed to ${operationName}: ${error.message}`,
);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Preserve original stack traces when rethrowing errors.

Using a new Error object without the original error as its cause may lose the original stack trace. Consider using the cause option (if supported) or appending error.stack to retain more debugging information.

- throw new Error(
-   errorMessage || `Failed to ${operationName}: ${error.message}`,
- );
+ throw new Error(
+   errorMessage || `Failed to ${operationName}: ${error.message}`,
+   { cause: error },
+ );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function withErrorHandling<T>(
operation: () => Promise<T>,
context: {
operationName: string;
errorMessage?: string;
additionalContext?: Record<string, any>;
},
defaultValue?: T,
): Promise<T> {
try {
return await operation();
} catch (error: any) {
const { operationName, errorMessage, additionalContext } = context;
logger.error(`Failed to ${operationName}:`, {
error: error.message,
code: error.code,
...additionalContext,
});
if (defaultValue !== undefined) {
return defaultValue;
}
throw new Error(
errorMessage || `Failed to ${operationName}: ${error.message}`,
);
}
}
export async function withErrorHandling<T>(
operation: () => Promise<T>,
context: {
operationName: string;
errorMessage?: string;
additionalContext?: Record<string, any>;
},
defaultValue?: T,
): Promise<T> {
try {
return await operation();
} catch (error: any) {
const { operationName, errorMessage, additionalContext } = context;
logger.error(`Failed to ${operationName}:`, {
error: error.message,
code: error.code,
...additionalContext,
});
if (defaultValue !== undefined) {
return defaultValue;
}
throw new Error(
errorMessage || `Failed to ${operationName}: ${error.message}`,
{ cause: error },
);
}
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
.github/workflows/test.yml (2)

24-31: Docker Compose for Tests

The Docker Compose action step is well configured:

  • The compose-file points to the appropriate Docker Compose file ("./backend/test/setup/docker-compose.yml").
  • The service "postgres_test" is specified, and the flags --detach and --volumes ensure that services run in the background while cleaning up volumes post execution.

Ensure that the service name matches exactly with the one defined in your Docker Compose file. A brief inline comment linking this to the backend's PostgreSQL configuration could further enhance clarity.


32-35: PostgreSQL Readiness Check

This step leverages a custom script (./backend/test/wait-for-db.sh) to wait for PostgreSQL to be ready:

  • Using chmod +x ensures the script is executable.
  • The script is then executed with parameters localhost and port 54321.

Make sure the script handles potential timeouts or errors gracefully, and consider adding logging for successful connection or error cases to aid in debugging.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a864dc4 and 2c450a8.

📒 Files selected for processing (1)
  • .github/workflows/test.yml (1 hunks)
🔇 Additional comments (6)
.github/workflows/test.yml (6)

1-7: Workflow Trigger and Metadata Setup

The workflow is correctly named "Tests" and is configured to trigger on both pull requests and pushes to the main branch. This setup aligns well with a CI/CD strategy for ensuring code quality on merge.


8-12: Job Configuration and Environment

The job named "test" is configured to run on ubuntu-latest, which is appropriate for the testing environment. The structure under the jobs key is clear and sets a solid foundation for the subsequent steps.


13-14: Checkout Step

The checkout step using actions/checkout@v4 is implemented correctly, ensuring that the repository code is available to the workflow in later steps.


16-20: Bun Setup

The step to set up Bun with oven-sh/setup-bun@v1 and specifying the latest version is clear and adheres to the migration objectives. This ensures that the environment is correctly primed for managing dependencies with Bun.


21-22: Dependency Installation

Running bun install is straightforward and fits perfectly in the context of using Bun for dependency management. This step should successfully install all required packages.


37-46: Test Execution and Environment Variables

The final step changes directory to backend and runs multiple test suites (unit, component, integration, and E2E) using bun test. The environment variables NODE_ENV and DATABASE_URL are correctly set for the test scenario:

  • The PostgreSQL connection URL uses port 54321, which is consistent with the earlier waiting step.

Verify that each test suite is designed to exit with appropriate status codes upon failure to ensure that the CI pipeline accurately reflects the test outcomes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
Dockerfile (3)

8-15: Pruner Stage – Turbo Installation and Repository Copying
The pruner stage installs Turbo globally and copies the entire repository, which is necessary for the subsequent pruning operation. To safeguard against potential future breakages, consider pinning Turbo to a specific version instead of using @latest.


55-57: Production Dependencies Installation
Installing only production dependencies for the backend with cd backend && bun install --production is correctly implemented. As a minor improvement, consider setting a dedicated WORKDIR for the backend before running the command to enhance clarity.


61-66: Network Exposure and Environment Settings
The container exposes port 3000 appropriately, and resetting NODE_ENV=production in this stage reinforces secure defaults. If Railway’s deployment requires a dynamic port configuration, consider making the port configurable via an environment variable.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c450a8 and 3a088c3.

📒 Files selected for processing (1)
  • Dockerfile (1 hunks)
🔇 Additional comments (7)
Dockerfile (7)

1-7: Base Stage Setup and Bun Environment Variables Configuration
The base stage using oven/bun:1.0.27-alpine is correctly defined, and the environment variables (BUN_HOME and PATH) are set to initialize Bun’s runtime. Just ensure that the PATH concatenation ("$BUN_HOME:$PATH") reliably picks up the Bun binaries as intended.


16-20: Pruner Stage – Telemetry Disabling and Monorepo Pruning
Disabling telemetry and pruning the monorepo to include only the backend and frontend is clearly implemented. Please verify that the output directories (e.g., /app/out/json/ and /app/out/full/) produced by the pruning commands exactly match the paths used in the builder stage.


21-39: Builder Stage – Dependency Installation and Build Process
The builder stage copies the pruned configuration files and installs dependencies with the --frozen-lockfile flag, ensuring consistency. The build command (bun run build) leverages the production environment set via NODE_ENV="production". Confirm that the pruned output from the pruner stage is aligned with what the build expects.


40-46: Production Stage – Establishing a Secure Runtime Environment
Creating a non-root user (app) and setting the working directory to /app ensures a secure container runtime environment. This segment correctly replaces the older LiteFS-dependent configuration.


47-54: Production Stage – Copying Essential Application Files
The file copying commands selectively bring in critical files from the builder stage while ensuring proper ownership. Note that curate.config.json is being copied directly from the local context rather than from the builder stage. Please confirm that this behavior is intentional.


58-60: Switching to Non-root User for Enhanced Security
The USER app directive properly ensures that the container runs under a non-root account, which is a best practice for production environments.


67-69: Application Start Command Update
Replacing the previous LiteFS entrypoint with CMD ["bun", "run", "--cwd", "backend", "start"] effectively aligns with the new Bun-based runtime setup. This change ensures that the application starts correctly in the production environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Task]: Migrate from Fly.io/SQLite to Railway/PostgreSQL
1 participant