@@ -28,9 +28,14 @@ class DatabaseSeedingService {
2828 Future <void > createTables () async {
2929 _log.info ('Starting database schema creation...' );
3030 try {
31- await _connection.transaction ((ctx) async {
31+ // Manually handle the transaction with BEGIN/COMMIT/ROLLBACK.
32+ await _connection.execute ('BEGIN' );
33+
34+ try {
3235 _log.fine ('Creating "users" table...' );
33- await ctx.execute ('''
36+ // All statements are executed on the main connection within the
37+ // manual transaction.
38+ await _connection.execute ('''
3439 CREATE TABLE IF NOT EXISTS users (
3540 id TEXT PRIMARY KEY,
3641 email TEXT UNIQUE,
@@ -41,7 +46,7 @@ class DatabaseSeedingService {
4146 ''' );
4247
4348 _log.fine ('Creating "app_config" table...' );
44- await ctx .execute ('''
49+ await _connection .execute ('''
4550 CREATE TABLE IF NOT EXISTS app_config (
4651 id TEXT PRIMARY KEY,
4752 user_preference_limits JSONB NOT NULL,
@@ -51,7 +56,7 @@ class DatabaseSeedingService {
5156 ''' );
5257
5358 _log.fine ('Creating "categories" table...' );
54- await ctx .execute ('''
59+ await _connection .execute ('''
5560 CREATE TABLE IF NOT EXISTS categories (
5661 id TEXT PRIMARY KEY,
5762 name TEXT NOT NULL UNIQUE,
@@ -61,7 +66,7 @@ class DatabaseSeedingService {
6166 ''' );
6267
6368 _log.fine ('Creating "sources" table...' );
64- await ctx .execute ('''
69+ await _connection .execute ('''
6570 CREATE TABLE IF NOT EXISTS sources (
6671 id TEXT PRIMARY KEY,
6772 name TEXT NOT NULL UNIQUE,
@@ -71,7 +76,7 @@ class DatabaseSeedingService {
7176 ''' );
7277
7378 _log.fine ('Creating "countries" table...' );
74- await ctx .execute ('''
79+ await _connection .execute ('''
7580 CREATE TABLE IF NOT EXISTS countries (
7681 id TEXT PRIMARY KEY,
7782 name TEXT NOT NULL UNIQUE,
@@ -82,7 +87,7 @@ class DatabaseSeedingService {
8287 ''' );
8388
8489 _log.fine ('Creating "headlines" table...' );
85- await ctx .execute ('''
90+ await _connection .execute ('''
8691 CREATE TABLE IF NOT EXISTS headlines (
8792 id TEXT PRIMARY KEY,
8893 title TEXT NOT NULL,
@@ -99,7 +104,7 @@ class DatabaseSeedingService {
99104 ''' );
100105
101106 _log.fine ('Creating "user_app_settings" table...' );
102- await ctx .execute ('''
107+ await _connection .execute ('''
103108 CREATE TABLE IF NOT EXISTS user_app_settings (
104109 id TEXT PRIMARY KEY,
105110 user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@@ -111,7 +116,7 @@ class DatabaseSeedingService {
111116 ''' );
112117
113118 _log.fine ('Creating "user_content_preferences" table...' );
114- await ctx .execute ('''
119+ await _connection .execute ('''
115120 CREATE TABLE IF NOT EXISTS user_content_preferences (
116121 id TEXT PRIMARY KEY,
117122 user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@@ -123,7 +128,13 @@ class DatabaseSeedingService {
123128 updated_at TIMESTAMPTZ
124129 );
125130 ''' );
126- });
131+
132+ await _connection.execute ('COMMIT' );
133+ } catch (e) {
134+ // If any query inside the transaction fails, roll back.
135+ await _connection.execute ('ROLLBACK' );
136+ rethrow ; // Re-throw the original error
137+ }
127138 _log.info ('Database schema creation completed successfully.' );
128139 } on Object catch (e, st) {
129140 _log.severe (
@@ -137,4 +148,86 @@ class DatabaseSeedingService {
137148 );
138149 }
139150 }
151+
152+ /// Seeds the database with global fixture data (categories, sources, etc.).
153+ ///
154+ /// This method is idempotent, using `ON CONFLICT DO NOTHING` to prevent
155+ /// errors if the data already exists. It runs within a single transaction.
156+ Future <void > seedGlobalFixtureData () async {
157+ _log.info ('Seeding global fixture data...' );
158+ try {
159+ await _connection.execute ('BEGIN' );
160+ try {
161+ // Seed Categories
162+ _log.fine ('Seeding categories...' );
163+ for (final data in categoriesFixturesData) {
164+ final category = Category .fromJson (data);
165+ await _connection.execute (
166+ Sql .named (
167+ 'INSERT INTO categories (id, name) VALUES (@id, @name) '
168+ 'ON CONFLICT (id) DO NOTHING' ,
169+ ),
170+ parameters: category.toJson (),
171+ );
172+ }
173+
174+ // Seed Sources
175+ _log.fine ('Seeding sources...' );
176+ for (final data in sourcesFixturesData) {
177+ final source = Source .fromJson (data);
178+ await _connection.execute (
179+ Sql .named (
180+ 'INSERT INTO sources (id, name) VALUES (@id, @name) '
181+ 'ON CONFLICT (id) DO NOTHING' ,
182+ ),
183+ parameters: source.toJson (),
184+ );
185+ }
186+
187+ // Seed Countries
188+ _log.fine ('Seeding countries...' );
189+ for (final data in countriesFixturesData) {
190+ final country = Country .fromJson (data);
191+ await _connection.execute (
192+ Sql .named (
193+ 'INSERT INTO countries (id, name, code) '
194+ 'VALUES (@id, @name, @code) ON CONFLICT (id) DO NOTHING' ,
195+ ),
196+ parameters: country.toJson (),
197+ );
198+ }
199+
200+ // Seed Headlines
201+ _log.fine ('Seeding headlines...' );
202+ for (final data in headlinesFixturesData) {
203+ final headline = Headline .fromJson (data);
204+ await _connection.execute (
205+ Sql .named (
206+ 'INSERT INTO headlines (id, title, source_id, category_id, '
207+ 'image_url, url, published_at, description, content) '
208+ 'VALUES (@id, @title, @sourceId, @categoryId, @imageUrl, @url, '
209+ '@publishedAt, @description, @content) '
210+ 'ON CONFLICT (id) DO NOTHING' ,
211+ ),
212+ parameters: headline.toJson (),
213+ );
214+ }
215+
216+ await _connection.execute ('COMMIT' );
217+ _log.info ('Global fixture data seeding completed successfully.' );
218+ } catch (e) {
219+ await _connection.execute ('ROLLBACK' );
220+ rethrow ;
221+ }
222+ } on Object catch (e, st) {
223+ _log.severe (
224+ 'An error occurred during global fixture data seeding.' ,
225+ e,
226+ st,
227+ );
228+ throw OperationFailedException (
229+ 'Failed to seed global fixture data: $e ' ,
230+ );
231+ }
232+ }
140233}
0 commit comments