@@ -28,9 +28,14 @@ class DatabaseSeedingService {
28
28
Future <void > createTables () async {
29
29
_log.info ('Starting database schema creation...' );
30
30
try {
31
- await _connection.transaction ((ctx) async {
31
+ // Manually handle the transaction with BEGIN/COMMIT/ROLLBACK.
32
+ await _connection.execute ('BEGIN' );
33
+
34
+ try {
32
35
_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 ('''
34
39
CREATE TABLE IF NOT EXISTS users (
35
40
id TEXT PRIMARY KEY,
36
41
email TEXT UNIQUE,
@@ -41,7 +46,7 @@ class DatabaseSeedingService {
41
46
''' );
42
47
43
48
_log.fine ('Creating "app_config" table...' );
44
- await ctx .execute ('''
49
+ await _connection .execute ('''
45
50
CREATE TABLE IF NOT EXISTS app_config (
46
51
id TEXT PRIMARY KEY,
47
52
user_preference_limits JSONB NOT NULL,
@@ -51,7 +56,7 @@ class DatabaseSeedingService {
51
56
''' );
52
57
53
58
_log.fine ('Creating "categories" table...' );
54
- await ctx .execute ('''
59
+ await _connection .execute ('''
55
60
CREATE TABLE IF NOT EXISTS categories (
56
61
id TEXT PRIMARY KEY,
57
62
name TEXT NOT NULL UNIQUE,
@@ -61,7 +66,7 @@ class DatabaseSeedingService {
61
66
''' );
62
67
63
68
_log.fine ('Creating "sources" table...' );
64
- await ctx .execute ('''
69
+ await _connection .execute ('''
65
70
CREATE TABLE IF NOT EXISTS sources (
66
71
id TEXT PRIMARY KEY,
67
72
name TEXT NOT NULL UNIQUE,
@@ -71,7 +76,7 @@ class DatabaseSeedingService {
71
76
''' );
72
77
73
78
_log.fine ('Creating "countries" table...' );
74
- await ctx .execute ('''
79
+ await _connection .execute ('''
75
80
CREATE TABLE IF NOT EXISTS countries (
76
81
id TEXT PRIMARY KEY,
77
82
name TEXT NOT NULL UNIQUE,
@@ -82,7 +87,7 @@ class DatabaseSeedingService {
82
87
''' );
83
88
84
89
_log.fine ('Creating "headlines" table...' );
85
- await ctx .execute ('''
90
+ await _connection .execute ('''
86
91
CREATE TABLE IF NOT EXISTS headlines (
87
92
id TEXT PRIMARY KEY,
88
93
title TEXT NOT NULL,
@@ -99,7 +104,7 @@ class DatabaseSeedingService {
99
104
''' );
100
105
101
106
_log.fine ('Creating "user_app_settings" table...' );
102
- await ctx .execute ('''
107
+ await _connection .execute ('''
103
108
CREATE TABLE IF NOT EXISTS user_app_settings (
104
109
id TEXT PRIMARY KEY,
105
110
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@@ -111,7 +116,7 @@ class DatabaseSeedingService {
111
116
''' );
112
117
113
118
_log.fine ('Creating "user_content_preferences" table...' );
114
- await ctx .execute ('''
119
+ await _connection .execute ('''
115
120
CREATE TABLE IF NOT EXISTS user_content_preferences (
116
121
id TEXT PRIMARY KEY,
117
122
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@@ -123,7 +128,13 @@ class DatabaseSeedingService {
123
128
updated_at TIMESTAMPTZ
124
129
);
125
130
''' );
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
+ }
127
138
_log.info ('Database schema creation completed successfully.' );
128
139
} on Object catch (e, st) {
129
140
_log.severe (
@@ -137,4 +148,86 @@ class DatabaseSeedingService {
137
148
);
138
149
}
139
150
}
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
+ }
140
233
}
0 commit comments