Skip to content

Feat postgres integration #11

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

Merged
merged 18 commits into from
Jul 6, 2025
Merged

Feat postgres integration #11

merged 18 commits into from
Jul 6, 2025

Conversation

fulleni
Copy link
Member

@fulleni fulleni commented Jul 6, 2025

Status

READY/IN DEVELOPMENT/HOLD

Description

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🛠️ Bug fix (non-breaking change which fixes an issue)
  • ❌ Breaking change (fix or feature that would cause existing functionality to change)
  • 🧹 Code refactor
  • ✅ Build configuration change
  • 📝 Documentation
  • 🗑️ Chore

fulleni added 18 commits July 6, 2025 09:36
Removes all repository and service instantiation from the root middleware. This is a preparatory step for centralizing dependency injection in `server.dart`. The API is temporarily broken by this change.
- Fixes a critical error in `server.dart` where `Endpoint.uri` was called, which is not available in the current `postgres` package version. The `DATABASE_URL` is now parsed manually to create the `Endpoint`.
- Initializes all data repositories (`User`, `UserAppSettings`, `UserContentPreferences`, `AppConfig`) in addition to the existing ones.
- Wraps the main handler with `provider` middleware for all repositories and `Uuid`, making them available for dependency injection throughout the application.
This commit updates `server.dart` to instantiate all application services, such as AuthService and DashboardSummaryService, injecting the necessary repository dependencies.

These services are then provided to the application via the Dart Frog provider middleware, completing the centralized dependency injection setup.
Updates `routes/_middleware.dart` to reflect the new dependency injection architecture. The middleware now consumes the `Uuid` provider from the context to generate a request ID.

The chain is simplified to apply only the essential global middleware: request ID generation, request logging, and error handling.
Adds the foundational `DatabaseSeedingService` class. This service will be responsible for all database schema creation and data seeding operations.
Adds the `createTables` method to the `DatabaseSeedingService`. This method defines and executes the SQL `CREATE TABLE IF NOT EXISTS` statements for all application models within a single transaction.

The schema includes primary keys, foreign keys with cascading deletes, and appropriate data types like `JSONB` and `TIMESTAMPTZ`.
- Implements the `seedGlobalFixtureData` method in `DatabaseSeedingService` to populate the database with initial categories, sources, countries, and headlines from the shared fixtures.
- Uses `ON CONFLICT DO NOTHING` to make the seeding process idempotent.
- Fixes the `createTables` method by replacing the problematic `.transaction()` helper with a manual `BEGIN`/`COMMIT`/`ROLLBACK` block for compatibility and robustness.
Adds the `seedInitialAdminAndConfig` method to `DatabaseSeedingService`. This method inserts the default `AppConfig` and the initial admin `User` from the shared fixtures into the database.

The operation is idempotent, using `ON CONFLICT DO NOTHING` to prevent overwriting existing data on subsequent server starts.
Fixes a bug in `DatabaseSeedingService` where the code was incorrectly attempting to access properties on a `User` object as if it were a `Map`. The logic now correctly accesses the `.roles` property and uses the `User` object from the fixture list directly.
Updates the `seedInitialAdminAndConfig` method to create and insert the default `UserAppSettings` and `UserContentPreferences` records for the seeded admin user.

This fixes a bug where the admin user would lack these necessary associated records, ensuring consistency with the user creation logic in `AuthService`.
Updates `server.dart` to instantiate and run the `DatabaseSeedingService` after the database connection is established.

This ensures that tables are created and initial data (global fixtures, admin user, app config) is seeded on every server start. The idempotent nature of the seeding operations makes this process safe and reliable for both development and first-time production deployments.
Enhances the README.md to reflect the new PostgreSQL database requirement.

- Adds PostgreSQL to the prerequisites.
- Adds a new "Configuration" section explaining the `DATABASE_URL` environment variable.
- Updates the "Run the development server" description to remove references to in-memory repositories and mention the new database seeding process.
@fulleni fulleni merged commit f27e209 into main Jul 6, 2025
1 check failed
@fulleni fulleni deleted the feat_postgres_integration branch July 6, 2025 10:19
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @fulleni, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request fundamentally shifts the application's data persistence strategy from an in-memory solution to a robust PostgreSQL database. It introduces comprehensive logic for connecting to PostgreSQL, automatically creating the necessary schema, and seeding initial data on server startup. This change ensures data durability and prepares the application for production environments, while also centralizing the initialization of all data-related services and repositories.

Highlights

  • Database Migration: The application's data persistence layer has been migrated from an in-memory solution to PostgreSQL, ensuring data durability and preparing for production environments.
  • Automated Database Setup: A new DatabaseSeedingService has been introduced to automatically create necessary database tables and seed initial/fixture data idempotently on server startup. This simplifies first-time setup and ensures database consistency.
  • Centralized Dependency Management: All data repositories and core application services are now initialized with PostgreSQL clients and provided via Dart Frog's dependency injection mechanism within a new server.dart file. This centralizes bootstrapping logic.
  • Environment Configuration: The application now requires a DATABASE_URL environment variable to establish a connection with the PostgreSQL database, enhancing configurability and security.
  • Middleware Simplification: The main _middleware.dart file has been significantly streamlined by moving complex service and repository instantiations to the server.dart bootstrap process, making the middleware chain cleaner and more focused on request processing.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request is a significant and well-executed architectural improvement, migrating the application from an in-memory data store to a persistent PostgreSQL database. The introduction of a dedicated server setup file, environment configuration, and a database seeding service greatly enhances the project's structure and maintainability.

I've identified two critical issues that must be addressed: one is a typo in pubspec.yaml that will break dependency fetching, and the other is a fragile implementation of database URL parsing that can be made more robust and simpler. Once these are fixed, the PR will be in excellent shape.

Comment on lines +71 to +93
final dbUri = Uri.parse(EnvironmentConfig.databaseUrl);
String? username;
String? password;
if (dbUri.userInfo.isNotEmpty) {
final parts = dbUri.userInfo.split(':');
username = Uri.decodeComponent(parts.first);
if (parts.length > 1) {
password = Uri.decodeComponent(parts.last);
}
}

_connection = await Connection.open(
Endpoint(
host: dbUri.host,
port: dbUri.port,
database: dbUri.path.substring(1), // Remove leading '/'
username: username,
password: password,
),
// Using `require` is a more secure default. For local development against
// a non-SSL database, this may need to be changed to `SslMode.disable`.
settings: const ConnectionSettings(sslMode: SslMode.require),
);

Choose a reason for hiding this comment

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

critical

The current manual parsing of the database connection URI is fragile. Specifically, dbUri.userInfo.split(':') will not correctly handle passwords that contain a colon (:).

To make the parsing more robust and simplify the code, you should use the Endpoint.fromUri factory constructor provided by the postgres package. It correctly handles all components of a standard PostgreSQL connection URI, including complex usernames and passwords.

  final endpoint = Endpoint.fromUri(Uri.parse(EnvironmentConfig.databaseUrl));

  _connection = await Connection.open(
    endpoint,
    // Using `require` is a more secure default. For local development against
    // a non-SSL database, this may need to be changed to `SslMode.disable`.
    settings: const ConnectionSettings(sslMode: SslMode.require),
  );

git:
url: https://github.com/headlines-toolkit/ht-data-inmemory.git
url: https://github.com/headlines-toolkit/ht-data-postgres.git

Choose a reason for hiding this comment

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

critical

There is a trailing space at the end of the git URL for ht_data_postgres. This will cause the dart pub get command to fail when trying to fetch the dependency.

      url: https://github.com/headlines-toolkit/ht-data-postgres.git

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.

1 participant