• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wger-project / flutter / 30854254088

03 Aug 2026 09:21PM UTC coverage: 40.895% (-15.0%) from 55.93%
30854254088

Pull #1260

github

web-flow
Merge d93794de5 into e3affdd4f
Pull Request #1260: WIP: health sync

9980 of 24404 relevant lines covered (40.89%)

1.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/lib/database/powersync/tables/measurements.dart
1
/*
2
 * This file is part of wger Workout Manager <https://github.com/wger-project>.
3
 * Copyright (c)  2026 wger Team
4
 *
5
 * wger Workout Manager is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU Affero General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Affero General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Affero General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

19
import 'package:drift/drift.dart';
20
import 'package:powersync/powersync.dart' as ps;
21
import 'package:wger/database/converters/json_map_converter.dart';
22
import 'package:wger/database/converters/measurement_chart_type_converter.dart';
23
import 'package:wger/database/converters/measurement_metric_type_converter.dart';
24
import 'package:wger/database/converters/utc_datetime_converter.dart';
25
import 'package:wger/features/measurements/models/measurement_category.dart';
26
import 'package:wger/features/measurements/models/measurement_entry.dart';
27

28
@UseRowClass(MeasurementCategory)
29
class MeasurementCategoryTable extends Table {
30
  @override
×
31
  String get tableName => 'measurements_category';
32

33
  TextColumn get id => text().clientDefault(() => ps.uuid.v7())();
×
34

35
  TextColumn get name => text()();
×
36
  TextColumn get unit => text()();
×
37
  TextColumn get metricType =>
×
38
      text().named('metric_type').map(const MeasurementMetricTypeConverter())();
×
39

40
  /// Chart the user picked, NULL for the one derived from the metric type.
41
  TextColumn get chartType =>
×
42
      text().named('chart_type').nullable().map(const MeasurementChartTypeConverter())();
×
43

44
  /// Multi-value groups: parent category id (max. one level of nesting; only
45
  /// leaf categories carry entries).
46
  TextColumn get parentId =>
×
47
      text().named('parent_id').nullable().references(MeasurementCategoryTable, #id)();
×
48

49
  /// Position in the category list; for children, the position within the group
50
  IntColumn get order => integer().withDefault(const Constant(0))();
×
51

52
  /// Server-managed official category (max. one per metric type and user).
53
  /// Body weight entries live in the official `body_weight` category.
54
  BoolColumn get isOfficial => boolean().named('is_official').withDefault(const Constant(false))();
×
55
}
56

57
const PowersyncMeasurementCategoryTable = ps.Table(
58
  'measurements_category',
59
  [
60
    ps.Column.text('name'),
61
    ps.Column.text('unit'),
62
    ps.Column.text('metric_type'),
63
    ps.Column.text('chart_type'),
64
    ps.Column.text('parent_id'),
65
    ps.Column.integer('order'),
66
    // Postgres boolean; integer so the view yields 0/1, not the string '0'
67
    ps.Column.integer('is_official'),
68
  ],
69
  indexes: [
70
    ps.Index('parent_idx', [ps.IndexedColumn('parent_id')]),
71
  ],
72
);
73

74
@UseRowClass(MeasurementEntry)
75
class MeasurementEntryTable extends Table {
76
  @override
×
77
  String get tableName => 'measurements_measurement';
78

79
  TextColumn get id => text().clientDefault(() => ps.uuid.v7())();
×
80
  TextColumn get categoryId =>
×
81
      text().named('category_id').references(MeasurementCategoryTable, #id)();
×
82

83
  DateTimeColumn get date => dateTime().map(const UtcDateTimeConverter())();
×
84
  RealColumn get value => real()();
×
85
  TextColumn get notes => text()();
×
86

87
  /// Where the reading came from; one of the server's `source` values:
88
  /// `user` for manual entries or a health platform (`apple`, `google`).
89
  TextColumn get source => text().withDefault(const Constant('user'))();
×
90

91
  /// Platform record UUID, used to deduplicate re-imports. `null` for manual
92
  /// entries.
93
  TextColumn get externalId => text().named('external_id').nullable()();
×
94

95
  /// Per-entry metadata (server JSONField). The `unit` key holds the unit the
96
  /// value was entered in; absent, the category unit applies.
97
  TextColumn get extraData => text().named('extra_data').map(const JsonMapConverter()).nullable()();
×
98
}
99

100
const PowersyncMeasurementEntryTable = ps.Table(
101
  'measurements_measurement',
102
  [
103
    ps.Column.text('category_id'),
104
    ps.Column.text('date'),
105
    ps.Column.real('value'),
106
    ps.Column.text('notes'),
107
    ps.Column.text('source'),
108
    ps.Column.text('external_id'),
109
    // JSON object, synced and stored as text
110
    ps.Column.text('extra_data'),
111
  ],
112
  indexes: [
113
    // Entries are read per category and bounded by date (the charts read a
114
    // range, the lists sort by it). The date is stored as an ISO string, which
115
    // sorts the same way the timestamps do, so a range is one index scan
116
    ps.Index('category_idx', [
117
      ps.IndexedColumn('category_id'),
118
      ps.IndexedColumn('date'),
119
    ]),
120
    ps.Index('external_id_idx', [ps.IndexedColumn('external_id')]),
121
  ],
122
);
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc