Aucune description

db.ts 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import * as SQLite from 'expo-sqlite';
  2. export const dbPromise = SQLite.openDatabaseAsync('smartfarming.db');
  3. type ColumnSpec = {
  4. name: string;
  5. type: string;
  6. };
  7. const userProfileColumns: ColumnSpec[] = [
  8. { name: 'id', type: 'INTEGER PRIMARY KEY NOT NULL' },
  9. { name: 'name', type: 'TEXT' },
  10. { name: 'farm_name', type: 'TEXT' },
  11. { name: 'location', type: 'TEXT' },
  12. { name: 'photo_uri', type: 'TEXT' },
  13. { name: 'language', type: 'TEXT' },
  14. { name: 'currency', type: 'TEXT' },
  15. { name: 'created_at', type: 'TEXT' },
  16. { name: 'updated_at', type: 'TEXT' },
  17. ];
  18. const fieldColumns: ColumnSpec[] = [
  19. { name: 'id', type: 'INTEGER PRIMARY KEY NOT NULL' },
  20. { name: 'name', type: 'TEXT' },
  21. { name: 'area_ha', type: 'REAL' },
  22. { name: 'boundary_geojson', type: 'TEXT' },
  23. { name: 'notes', type: 'TEXT' },
  24. { name: 'photo_uri', type: 'TEXT' },
  25. { name: 'created_at', type: 'TEXT' },
  26. { name: 'updated_at', type: 'TEXT' },
  27. ];
  28. const cropColumns: ColumnSpec[] = [
  29. { name: 'id', type: 'INTEGER PRIMARY KEY NOT NULL' },
  30. { name: 'field_id', type: 'INTEGER' },
  31. { name: 'crop_name', type: 'TEXT' },
  32. { name: 'variety', type: 'TEXT' },
  33. { name: 'planting_date', type: 'TEXT' },
  34. { name: 'expected_harvest_date', type: 'TEXT' },
  35. { name: 'photo_uri', type: 'TEXT' },
  36. ];
  37. const harvestColumns: ColumnSpec[] = [
  38. { name: 'id', type: 'INTEGER PRIMARY KEY NOT NULL' },
  39. { name: 'field_id', type: 'INTEGER' },
  40. { name: 'crop_id', type: 'INTEGER' },
  41. { name: 'harvested_at', type: 'TEXT' },
  42. { name: 'quantity', type: 'REAL' },
  43. { name: 'unit', type: 'TEXT' },
  44. { name: 'notes', type: 'TEXT' },
  45. { name: 'photo_uri', type: 'TEXT' },
  46. { name: 'created_at', type: 'TEXT' },
  47. ];
  48. const saleColumns: ColumnSpec[] = [
  49. { name: 'id', type: 'INTEGER PRIMARY KEY NOT NULL' },
  50. { name: 'harvest_id', type: 'INTEGER' },
  51. { name: 'field_id', type: 'INTEGER' },
  52. { name: 'crop_id', type: 'INTEGER' },
  53. { name: 'sold_at', type: 'TEXT' },
  54. { name: 'quantity', type: 'REAL' },
  55. { name: 'unit', type: 'TEXT' },
  56. { name: 'price', type: 'REAL' },
  57. { name: 'buyer', type: 'TEXT' },
  58. { name: 'notes', type: 'TEXT' },
  59. { name: 'created_at', type: 'TEXT' },
  60. ];
  61. const costColumns: ColumnSpec[] = [
  62. { name: 'id', type: 'INTEGER PRIMARY KEY NOT NULL' },
  63. { name: 'field_id', type: 'INTEGER' },
  64. { name: 'crop_id', type: 'INTEGER' },
  65. { name: 'category', type: 'TEXT' },
  66. { name: 'amount', type: 'REAL' },
  67. { name: 'currency', type: 'TEXT' },
  68. { name: 'vendor', type: 'TEXT' },
  69. { name: 'notes', type: 'TEXT' },
  70. { name: 'spent_at', type: 'TEXT' },
  71. { name: 'photo_uri', type: 'TEXT' },
  72. { name: 'created_at', type: 'TEXT' },
  73. ];
  74. export async function initCoreTables() {
  75. const db = await dbPromise;
  76. await db.execAsync(
  77. [
  78. 'CREATE TABLE IF NOT EXISTS user_profile (id INTEGER PRIMARY KEY NOT NULL, name TEXT, farm_name TEXT, location TEXT, photo_uri TEXT, language TEXT, currency TEXT, created_at TEXT, updated_at TEXT);',
  79. 'CREATE TABLE IF NOT EXISTS fields (id INTEGER PRIMARY KEY NOT NULL, name TEXT, area_ha REAL, boundary_geojson TEXT, notes TEXT, photo_uri TEXT, created_at TEXT, updated_at TEXT);',
  80. 'CREATE TABLE IF NOT EXISTS crops (id INTEGER PRIMARY KEY NOT NULL, field_id INTEGER, crop_name TEXT, variety TEXT, planting_date TEXT, expected_harvest_date TEXT, photo_uri TEXT, FOREIGN KEY(field_id) REFERENCES fields(id));',
  81. 'CREATE TABLE IF NOT EXISTS observations (id INTEGER PRIMARY KEY NOT NULL, field_id INTEGER, crop_id INTEGER, obs_type TEXT, note TEXT, severity INTEGER, lat REAL, lng REAL, observed_at TEXT, FOREIGN KEY(field_id) REFERENCES fields(id), FOREIGN KEY(crop_id) REFERENCES crops(id));',
  82. 'CREATE TABLE IF NOT EXISTS images (id INTEGER PRIMARY KEY NOT NULL, observation_id INTEGER, uri TEXT, thumbnail_uri TEXT, width INTEGER, height INTEGER, created_at TEXT, FOREIGN KEY(observation_id) REFERENCES observations(id));',
  83. 'CREATE TABLE IF NOT EXISTS ml_inferences (id INTEGER PRIMARY KEY NOT NULL, image_id INTEGER, model_name TEXT, model_version TEXT, top_label TEXT, top_score REAL, scores_json TEXT, created_at TEXT, FOREIGN KEY(image_id) REFERENCES images(id));',
  84. 'CREATE TABLE IF NOT EXISTS sensors (id INTEGER PRIMARY KEY NOT NULL, name TEXT, type TEXT, unit TEXT, field_id INTEGER, location TEXT, created_at TEXT, FOREIGN KEY(field_id) REFERENCES fields(id));',
  85. 'CREATE TABLE IF NOT EXISTS sensor_readings (id INTEGER PRIMARY KEY NOT NULL, sensor_id INTEGER, value REAL, observed_at TEXT, FOREIGN KEY(sensor_id) REFERENCES sensors(id));',
  86. 'CREATE TABLE IF NOT EXISTS daily_tasks (id INTEGER PRIMARY KEY NOT NULL, name TEXT, description TEXT, default_time TEXT, is_active INTEGER DEFAULT 1, created_at TEXT);',
  87. 'CREATE TABLE IF NOT EXISTS daily_task_entries (id INTEGER PRIMARY KEY NOT NULL, task_id INTEGER, field_id INTEGER, notes TEXT, status TEXT, completed_at TEXT, created_at TEXT, meta_json TEXT, FOREIGN KEY(task_id) REFERENCES daily_tasks(id), FOREIGN KEY(field_id) REFERENCES fields(id));',
  88. 'CREATE TABLE IF NOT EXISTS harvests (id INTEGER PRIMARY KEY NOT NULL, field_id INTEGER, crop_id INTEGER, harvested_at TEXT, quantity REAL, unit TEXT, notes TEXT, photo_uri TEXT, created_at TEXT, FOREIGN KEY(field_id) REFERENCES fields(id), FOREIGN KEY(crop_id) REFERENCES crops(id));',
  89. 'CREATE TABLE IF NOT EXISTS sales (id INTEGER PRIMARY KEY NOT NULL, harvest_id INTEGER, field_id INTEGER, crop_id INTEGER, sold_at TEXT, quantity REAL, unit TEXT, price REAL, buyer TEXT, notes TEXT, created_at TEXT, FOREIGN KEY(harvest_id) REFERENCES harvests(id), FOREIGN KEY(field_id) REFERENCES fields(id), FOREIGN KEY(crop_id) REFERENCES crops(id));',
  90. 'CREATE TABLE IF NOT EXISTS costs (id INTEGER PRIMARY KEY NOT NULL, field_id INTEGER, crop_id INTEGER, category TEXT, amount REAL, currency TEXT, vendor TEXT, notes TEXT, spent_at TEXT, photo_uri TEXT, created_at TEXT, FOREIGN KEY(field_id) REFERENCES fields(id), FOREIGN KEY(crop_id) REFERENCES crops(id));',
  91. 'CREATE TABLE IF NOT EXISTS field_media (id INTEGER PRIMARY KEY NOT NULL, field_id INTEGER, uri TEXT, media_type TEXT, created_at TEXT, FOREIGN KEY(field_id) REFERENCES fields(id));',
  92. 'CREATE TABLE IF NOT EXISTS crop_media (id INTEGER PRIMARY KEY NOT NULL, crop_id INTEGER, uri TEXT, media_type TEXT, created_at TEXT, FOREIGN KEY(crop_id) REFERENCES crops(id));',
  93. 'CREATE TABLE IF NOT EXISTS harvest_media (id INTEGER PRIMARY KEY NOT NULL, harvest_id INTEGER, uri TEXT, media_type TEXT, created_at TEXT, FOREIGN KEY(harvest_id) REFERENCES harvests(id));',
  94. 'CREATE TABLE IF NOT EXISTS sale_media (id INTEGER PRIMARY KEY NOT NULL, sale_id INTEGER, uri TEXT, media_type TEXT, created_at TEXT, FOREIGN KEY(sale_id) REFERENCES sales(id));',
  95. 'CREATE TABLE IF NOT EXISTS cost_media (id INTEGER PRIMARY KEY NOT NULL, cost_id INTEGER, uri TEXT, media_type TEXT, created_at TEXT, FOREIGN KEY(cost_id) REFERENCES costs(id));',
  96. 'CREATE TABLE IF NOT EXISTS task_entry_media (id INTEGER PRIMARY KEY NOT NULL, entry_id INTEGER, uri TEXT, media_type TEXT, created_at TEXT, FOREIGN KEY(entry_id) REFERENCES daily_task_entries(id));',
  97. 'CREATE INDEX IF NOT EXISTS idx_observations_field_id ON observations(field_id);',
  98. 'CREATE INDEX IF NOT EXISTS idx_observations_crop_id ON observations(crop_id);',
  99. 'CREATE INDEX IF NOT EXISTS idx_images_observation_id ON images(observation_id);',
  100. 'CREATE INDEX IF NOT EXISTS idx_ml_inferences_image_id ON ml_inferences(image_id);',
  101. 'CREATE INDEX IF NOT EXISTS idx_sensors_field_id ON sensors(field_id);',
  102. 'CREATE INDEX IF NOT EXISTS idx_sensor_readings_sensor_id ON sensor_readings(sensor_id);',
  103. 'CREATE INDEX IF NOT EXISTS idx_daily_task_entries_task_id ON daily_task_entries(task_id);',
  104. 'CREATE INDEX IF NOT EXISTS idx_daily_task_entries_field_id ON daily_task_entries(field_id);',
  105. 'CREATE INDEX IF NOT EXISTS idx_harvests_field_id ON harvests(field_id);',
  106. 'CREATE INDEX IF NOT EXISTS idx_harvests_crop_id ON harvests(crop_id);',
  107. 'CREATE INDEX IF NOT EXISTS idx_sales_harvest_id ON sales(harvest_id);',
  108. 'CREATE INDEX IF NOT EXISTS idx_sales_crop_id ON sales(crop_id);',
  109. 'CREATE INDEX IF NOT EXISTS idx_costs_field_id ON costs(field_id);',
  110. 'CREATE INDEX IF NOT EXISTS idx_costs_crop_id ON costs(crop_id);',
  111. 'CREATE INDEX IF NOT EXISTS idx_field_media_field_id ON field_media(field_id);',
  112. 'CREATE INDEX IF NOT EXISTS idx_crop_media_crop_id ON crop_media(crop_id);',
  113. 'CREATE INDEX IF NOT EXISTS idx_harvest_media_harvest_id ON harvest_media(harvest_id);',
  114. 'CREATE INDEX IF NOT EXISTS idx_sale_media_sale_id ON sale_media(sale_id);',
  115. 'CREATE INDEX IF NOT EXISTS idx_cost_media_cost_id ON cost_media(cost_id);',
  116. 'CREATE INDEX IF NOT EXISTS idx_task_entry_media_entry_id ON task_entry_media(entry_id);',
  117. ].join('\n')
  118. );
  119. await ensureColumns(db, 'user_profile', userProfileColumns);
  120. await ensureColumns(db, 'fields', fieldColumns);
  121. await ensureColumns(db, 'crops', cropColumns);
  122. await ensureColumns(db, 'harvests', harvestColumns);
  123. await ensureColumns(db, 'sales', saleColumns);
  124. await ensureColumns(db, 'costs', costColumns);
  125. await db.execAsync(
  126. `INSERT INTO field_media (field_id, uri, media_type, created_at)
  127. SELECT f.id, f.photo_uri, 'image', f.created_at
  128. FROM fields f
  129. WHERE f.photo_uri IS NOT NULL AND TRIM(f.photo_uri) <> ''
  130. AND NOT EXISTS (
  131. SELECT 1 FROM field_media fm WHERE fm.field_id = f.id
  132. );`
  133. );
  134. await db.execAsync(
  135. `INSERT INTO crop_media (crop_id, uri, media_type, created_at)
  136. SELECT c.id, c.photo_uri, 'image', datetime('now')
  137. FROM crops c
  138. WHERE c.photo_uri IS NOT NULL AND TRIM(c.photo_uri) <> ''
  139. AND NOT EXISTS (
  140. SELECT 1 FROM crop_media cm WHERE cm.crop_id = c.id
  141. );`
  142. );
  143. await db.execAsync(
  144. `INSERT INTO harvest_media (harvest_id, uri, media_type, created_at)
  145. SELECT h.id, h.photo_uri, 'image', h.created_at
  146. FROM harvests h
  147. WHERE h.photo_uri IS NOT NULL AND TRIM(h.photo_uri) <> ''
  148. AND NOT EXISTS (
  149. SELECT 1 FROM harvest_media hm WHERE hm.harvest_id = h.id
  150. );`
  151. );
  152. await db.execAsync(
  153. `INSERT INTO cost_media (cost_id, uri, media_type, created_at)
  154. SELECT c.id, c.photo_uri, 'image', c.created_at
  155. FROM costs c
  156. WHERE c.photo_uri IS NOT NULL AND TRIM(c.photo_uri) <> ''
  157. AND NOT EXISTS (
  158. SELECT 1 FROM cost_media cm WHERE cm.cost_id = c.id
  159. );`
  160. );
  161. }
  162. async function ensureColumns(
  163. db: SQLite.SQLiteDatabase,
  164. table: string,
  165. columns: ColumnSpec[]
  166. ) {
  167. const existing = await db.getAllAsync<{ name: string }>(
  168. `PRAGMA table_info(${table});`
  169. );
  170. const names = new Set(existing.map((col) => col.name));
  171. for (const column of columns) {
  172. if (!names.has(column.name)) {
  173. await db.execAsync(
  174. `ALTER TABLE ${table} ADD COLUMN ${column.name} ${column.type};`
  175. );
  176. }
  177. }
  178. }