@nativesquare/soma 0.16.4 → 0.16.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/garmin.d.ts.map +1 -1
- package/dist/client/garmin.js +2 -0
- package/dist/client/garmin.js.map +1 -1
- package/dist/client/index.d.ts +150 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +30 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +3 -3
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +127 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/garmin/schemas/deregistration.d.ts +8 -0
- package/dist/component/garmin/schemas/deregistration.d.ts.map +1 -0
- package/dist/component/garmin/schemas/deregistration.js +12 -0
- package/dist/component/garmin/schemas/deregistration.js.map +1 -0
- package/dist/component/garmin/webhooks.d.ts +14 -0
- package/dist/component/garmin/webhooks.d.ts.map +1 -1
- package/dist/component/garmin/webhooks.js +65 -0
- package/dist/component/garmin/webhooks.js.map +1 -1
- package/dist/component/private.d.ts +90 -0
- package/dist/component/private.d.ts.map +1 -1
- package/dist/component/public.d.ts +118 -6
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +91 -21
- package/dist/component/public.js.map +1 -1
- package/dist/component/schema.d.ts +111 -1
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/utils.d.ts +27 -0
- package/dist/component/utils.d.ts.map +1 -1
- package/dist/component/utils.js +64 -0
- package/dist/component/utils.js.map +1 -1
- package/dist/component/validators/connection.d.ts +160 -0
- package/dist/component/validators/connection.d.ts.map +1 -1
- package/dist/component/validators/connection.js +16 -0
- package/dist/component/validators/connection.js.map +1 -1
- package/dist/component/validators/shared.d.ts +1 -1
- package/dist/component/validators/shared.d.ts.map +1 -1
- package/dist/component/validators/shared.js.map +1 -1
- package/dist/validators.d.ts +80 -0
- package/dist/validators.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/garmin.ts +3 -1
- package/src/client/index.ts +34 -0
- package/src/client/types.ts +4 -3
- package/src/component/_generated/api.ts +2 -0
- package/src/component/_generated/component.ts +49 -0
- package/src/component/garmin/schemas/deregistration.ts +14 -0
- package/src/component/garmin/webhooks.ts +84 -0
- package/src/component/healthkit/public.ts +597 -597
- package/src/component/public.ts +130 -21
- package/src/component/utils.ts +116 -22
- package/src/component/validators/connection.ts +18 -0
- package/src/component/validators/shared.ts +1 -0
|
@@ -8,6 +8,36 @@ export declare const getActiveConnections: import("convex/server").RegisteredQue
|
|
|
8
8
|
providerUserId?: string | undefined;
|
|
9
9
|
active?: boolean | undefined;
|
|
10
10
|
lastDataUpdate?: string | undefined;
|
|
11
|
+
stats?: {
|
|
12
|
+
body: {
|
|
13
|
+
count: number;
|
|
14
|
+
oldest: string | null;
|
|
15
|
+
};
|
|
16
|
+
daily: {
|
|
17
|
+
count: number;
|
|
18
|
+
oldest: string | null;
|
|
19
|
+
};
|
|
20
|
+
menstruation: {
|
|
21
|
+
count: number;
|
|
22
|
+
oldest: string | null;
|
|
23
|
+
};
|
|
24
|
+
sleep: {
|
|
25
|
+
count: number;
|
|
26
|
+
oldest: string | null;
|
|
27
|
+
};
|
|
28
|
+
activities: {
|
|
29
|
+
count: number;
|
|
30
|
+
oldest: string | null;
|
|
31
|
+
};
|
|
32
|
+
nutrition: {
|
|
33
|
+
count: number;
|
|
34
|
+
oldest: string | null;
|
|
35
|
+
};
|
|
36
|
+
plannedWorkouts: {
|
|
37
|
+
count: number;
|
|
38
|
+
oldest: string | null;
|
|
39
|
+
};
|
|
40
|
+
} | undefined;
|
|
11
41
|
userId: string;
|
|
12
42
|
provider: string;
|
|
13
43
|
}[]>>;
|
|
@@ -23,6 +53,36 @@ export declare const getConnectionByProvider: import("convex/server").Registered
|
|
|
23
53
|
providerUserId?: string | undefined;
|
|
24
54
|
active?: boolean | undefined;
|
|
25
55
|
lastDataUpdate?: string | undefined;
|
|
56
|
+
stats?: {
|
|
57
|
+
body: {
|
|
58
|
+
count: number;
|
|
59
|
+
oldest: string | null;
|
|
60
|
+
};
|
|
61
|
+
daily: {
|
|
62
|
+
count: number;
|
|
63
|
+
oldest: string | null;
|
|
64
|
+
};
|
|
65
|
+
menstruation: {
|
|
66
|
+
count: number;
|
|
67
|
+
oldest: string | null;
|
|
68
|
+
};
|
|
69
|
+
sleep: {
|
|
70
|
+
count: number;
|
|
71
|
+
oldest: string | null;
|
|
72
|
+
};
|
|
73
|
+
activities: {
|
|
74
|
+
count: number;
|
|
75
|
+
oldest: string | null;
|
|
76
|
+
};
|
|
77
|
+
nutrition: {
|
|
78
|
+
count: number;
|
|
79
|
+
oldest: string | null;
|
|
80
|
+
};
|
|
81
|
+
plannedWorkouts: {
|
|
82
|
+
count: number;
|
|
83
|
+
oldest: string | null;
|
|
84
|
+
};
|
|
85
|
+
} | undefined;
|
|
26
86
|
userId: string;
|
|
27
87
|
provider: string;
|
|
28
88
|
} | null>>;
|
|
@@ -39,6 +99,36 @@ export declare const getConnectionByProviderUserId: import("convex/server").Regi
|
|
|
39
99
|
providerUserId?: string | undefined;
|
|
40
100
|
active?: boolean | undefined;
|
|
41
101
|
lastDataUpdate?: string | undefined;
|
|
102
|
+
stats?: {
|
|
103
|
+
body: {
|
|
104
|
+
count: number;
|
|
105
|
+
oldest: string | null;
|
|
106
|
+
};
|
|
107
|
+
daily: {
|
|
108
|
+
count: number;
|
|
109
|
+
oldest: string | null;
|
|
110
|
+
};
|
|
111
|
+
menstruation: {
|
|
112
|
+
count: number;
|
|
113
|
+
oldest: string | null;
|
|
114
|
+
};
|
|
115
|
+
sleep: {
|
|
116
|
+
count: number;
|
|
117
|
+
oldest: string | null;
|
|
118
|
+
};
|
|
119
|
+
activities: {
|
|
120
|
+
count: number;
|
|
121
|
+
oldest: string | null;
|
|
122
|
+
};
|
|
123
|
+
nutrition: {
|
|
124
|
+
count: number;
|
|
125
|
+
oldest: string | null;
|
|
126
|
+
};
|
|
127
|
+
plannedWorkouts: {
|
|
128
|
+
count: number;
|
|
129
|
+
oldest: string | null;
|
|
130
|
+
};
|
|
131
|
+
} | undefined;
|
|
42
132
|
userId: string;
|
|
43
133
|
provider: string;
|
|
44
134
|
} | null>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"private.d.ts","sourceRoot":"","sources":["../../src/component/private.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAO,EAAE,EAAE,MAAM,2BAA2B,CAAC;AAMzD;;GAEG;AACH,eAAO,MAAM,oBAAoB
|
|
1
|
+
{"version":3,"file":"private.d.ts","sourceRoot":"","sources":["../../src/component/private.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAO,EAAE,EAAE,MAAM,2BAA2B,CAAC;AAMzD;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAM/B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAalC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAexC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;iBAU/B,CAAC;AAMH,eAAO,MAAM,iBAAiB;;;;;iBAe5B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;UAoB1B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;iBAa7B,CAAC;AAIH;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;iBA4BtB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;UAqBpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,YAAY;;iBAgBvB,CAAC;AAkBH,eAAO,MAAM,+BAA+B;;;;;;kBAQ1B,EAAE,CAAC,aAAa,CAAC;iBAClB,MAAM;GAwErB,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Id } from "./_generated/dataModel.js";
|
|
1
2
|
/**
|
|
2
3
|
* Connect a user to a wearable provider.
|
|
3
4
|
*
|
|
@@ -30,6 +31,36 @@ export declare const getConnection: import("convex/server").RegisteredQuery<"pub
|
|
|
30
31
|
providerUserId?: string | undefined;
|
|
31
32
|
active?: boolean | undefined;
|
|
32
33
|
lastDataUpdate?: string | undefined;
|
|
34
|
+
stats?: {
|
|
35
|
+
body: {
|
|
36
|
+
count: number;
|
|
37
|
+
oldest: string | null;
|
|
38
|
+
};
|
|
39
|
+
daily: {
|
|
40
|
+
count: number;
|
|
41
|
+
oldest: string | null;
|
|
42
|
+
};
|
|
43
|
+
menstruation: {
|
|
44
|
+
count: number;
|
|
45
|
+
oldest: string | null;
|
|
46
|
+
};
|
|
47
|
+
sleep: {
|
|
48
|
+
count: number;
|
|
49
|
+
oldest: string | null;
|
|
50
|
+
};
|
|
51
|
+
activities: {
|
|
52
|
+
count: number;
|
|
53
|
+
oldest: string | null;
|
|
54
|
+
};
|
|
55
|
+
nutrition: {
|
|
56
|
+
count: number;
|
|
57
|
+
oldest: string | null;
|
|
58
|
+
};
|
|
59
|
+
plannedWorkouts: {
|
|
60
|
+
count: number;
|
|
61
|
+
oldest: string | null;
|
|
62
|
+
};
|
|
63
|
+
} | undefined;
|
|
33
64
|
userId: string;
|
|
34
65
|
provider: string;
|
|
35
66
|
} | null>>;
|
|
@@ -46,9 +77,60 @@ export declare const getConnectionByProvider: import("convex/server").Registered
|
|
|
46
77
|
providerUserId?: string | undefined;
|
|
47
78
|
active?: boolean | undefined;
|
|
48
79
|
lastDataUpdate?: string | undefined;
|
|
80
|
+
stats?: {
|
|
81
|
+
body: {
|
|
82
|
+
count: number;
|
|
83
|
+
oldest: string | null;
|
|
84
|
+
};
|
|
85
|
+
daily: {
|
|
86
|
+
count: number;
|
|
87
|
+
oldest: string | null;
|
|
88
|
+
};
|
|
89
|
+
menstruation: {
|
|
90
|
+
count: number;
|
|
91
|
+
oldest: string | null;
|
|
92
|
+
};
|
|
93
|
+
sleep: {
|
|
94
|
+
count: number;
|
|
95
|
+
oldest: string | null;
|
|
96
|
+
};
|
|
97
|
+
activities: {
|
|
98
|
+
count: number;
|
|
99
|
+
oldest: string | null;
|
|
100
|
+
};
|
|
101
|
+
nutrition: {
|
|
102
|
+
count: number;
|
|
103
|
+
oldest: string | null;
|
|
104
|
+
};
|
|
105
|
+
plannedWorkouts: {
|
|
106
|
+
count: number;
|
|
107
|
+
oldest: string | null;
|
|
108
|
+
};
|
|
109
|
+
} | undefined;
|
|
49
110
|
userId: string;
|
|
50
111
|
provider: string;
|
|
51
112
|
} | null>>;
|
|
113
|
+
/**
|
|
114
|
+
* Get per-table row counts and oldest-data timestamps for a user–provider pair.
|
|
115
|
+
*
|
|
116
|
+
* Stats are denormalized onto the connection document and maintained
|
|
117
|
+
* automatically by ingest mutations, so this is an O(1) read regardless of
|
|
118
|
+
* how much data has been ingested.
|
|
119
|
+
*
|
|
120
|
+
* `oldest` is a lower bound on the earliest `metadata.start_time` (or
|
|
121
|
+
* `metadata.planned_date` for planned workouts). It can theoretically go
|
|
122
|
+
* stale if a provider mutates `start_time` on an existing record to a later
|
|
123
|
+
* value — this would also break the existing dedup strategy, so treat it as
|
|
124
|
+
* pathological.
|
|
125
|
+
*
|
|
126
|
+
* Returns `null` if the user has never connected to that provider. When a
|
|
127
|
+
* connection exists but no data has been ingested, every table returns
|
|
128
|
+
* `{ count: 0, oldest: null }` — callers never need to guard optional keys.
|
|
129
|
+
*/
|
|
130
|
+
export declare const getProviderStats: import("convex/server").RegisteredQuery<"public", {
|
|
131
|
+
userId: string;
|
|
132
|
+
provider: string;
|
|
133
|
+
}, Promise<import("./utils.js").ConnectionStats | null>>;
|
|
52
134
|
/**
|
|
53
135
|
* List all connections for a user (active and inactive).
|
|
54
136
|
*/
|
|
@@ -60,6 +142,36 @@ export declare const listConnections: import("convex/server").RegisteredQuery<"p
|
|
|
60
142
|
providerUserId?: string | undefined;
|
|
61
143
|
active?: boolean | undefined;
|
|
62
144
|
lastDataUpdate?: string | undefined;
|
|
145
|
+
stats?: {
|
|
146
|
+
body: {
|
|
147
|
+
count: number;
|
|
148
|
+
oldest: string | null;
|
|
149
|
+
};
|
|
150
|
+
daily: {
|
|
151
|
+
count: number;
|
|
152
|
+
oldest: string | null;
|
|
153
|
+
};
|
|
154
|
+
menstruation: {
|
|
155
|
+
count: number;
|
|
156
|
+
oldest: string | null;
|
|
157
|
+
};
|
|
158
|
+
sleep: {
|
|
159
|
+
count: number;
|
|
160
|
+
oldest: string | null;
|
|
161
|
+
};
|
|
162
|
+
activities: {
|
|
163
|
+
count: number;
|
|
164
|
+
oldest: string | null;
|
|
165
|
+
};
|
|
166
|
+
nutrition: {
|
|
167
|
+
count: number;
|
|
168
|
+
oldest: string | null;
|
|
169
|
+
};
|
|
170
|
+
plannedWorkouts: {
|
|
171
|
+
count: number;
|
|
172
|
+
oldest: string | null;
|
|
173
|
+
};
|
|
174
|
+
} | undefined;
|
|
63
175
|
userId: string;
|
|
64
176
|
provider: string;
|
|
65
177
|
}[]>>;
|
|
@@ -336,7 +448,7 @@ export declare const ingestActivity: import("convex/server").RegisteredMutation<
|
|
|
336
448
|
summary_id: string;
|
|
337
449
|
upload_type: number;
|
|
338
450
|
};
|
|
339
|
-
}, Promise<
|
|
451
|
+
}, Promise<Id<"activities">>>;
|
|
340
452
|
/**
|
|
341
453
|
* Ingest a sleep session record.
|
|
342
454
|
*
|
|
@@ -487,7 +599,7 @@ export declare const ingestSleep: import("convex/server").RegisteredMutation<"pu
|
|
|
487
599
|
end_time: string;
|
|
488
600
|
upload_type: number;
|
|
489
601
|
};
|
|
490
|
-
}, Promise<
|
|
602
|
+
}, Promise<Id<"sleep">>>;
|
|
491
603
|
/**
|
|
492
604
|
* Ingest a body metrics record.
|
|
493
605
|
*
|
|
@@ -675,7 +787,7 @@ export declare const ingestBody: import("convex/server").RegisteredMutation<"pub
|
|
|
675
787
|
start_time: string;
|
|
676
788
|
end_time: string;
|
|
677
789
|
};
|
|
678
|
-
}, Promise<
|
|
790
|
+
}, Promise<Id<"body">>>;
|
|
679
791
|
/**
|
|
680
792
|
* Ingest a daily activity summary record.
|
|
681
793
|
*
|
|
@@ -909,7 +1021,7 @@ export declare const ingestDaily: import("convex/server").RegisteredMutation<"pu
|
|
|
909
1021
|
end_time: string;
|
|
910
1022
|
upload_type: number;
|
|
911
1023
|
};
|
|
912
|
-
}, Promise<
|
|
1024
|
+
}, Promise<Id<"daily">>>;
|
|
913
1025
|
/**
|
|
914
1026
|
* Ingest a nutrition record.
|
|
915
1027
|
*
|
|
@@ -1067,7 +1179,7 @@ export declare const ingestNutrition: import("convex/server").RegisteredMutation
|
|
|
1067
1179
|
start_time: string;
|
|
1068
1180
|
end_time: string;
|
|
1069
1181
|
};
|
|
1070
|
-
}, Promise<
|
|
1182
|
+
}, Promise<Id<"nutrition">>>;
|
|
1071
1183
|
/**
|
|
1072
1184
|
* Ingest a menstruation record.
|
|
1073
1185
|
*
|
|
@@ -3390,7 +3502,7 @@ export declare const ingestPlannedWorkout: import("convex/server").RegisteredMut
|
|
|
3390
3502
|
provider_workout_id?: string | undefined;
|
|
3391
3503
|
provider_schedule_id?: string | undefined;
|
|
3392
3504
|
};
|
|
3393
|
-
}, Promise<
|
|
3505
|
+
}, Promise<Id<"plannedWorkouts">>>;
|
|
3394
3506
|
/**
|
|
3395
3507
|
* List planned workout records for a user, optionally filtered by planned date range.
|
|
3396
3508
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/component/public.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/component/public.ts"],"names":[],"mappings":"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"}
|
package/dist/component/public.js
CHANGED
|
@@ -9,6 +9,8 @@ import { sleepValidator } from "./validators/sleep.js";
|
|
|
9
9
|
import { menstruationValidator } from "./validators/menstruation.js";
|
|
10
10
|
import { nutritionValidator } from "./validators/nutrition.js";
|
|
11
11
|
import { plannedWorkoutValidator } from "./validators/plannedWorkout.js";
|
|
12
|
+
import { statsValidator } from "./validators/connection.js";
|
|
13
|
+
import { normalizeStats, updateStatsOnIngest } from "./utils.js";
|
|
12
14
|
// ─── Return Validators ──────────────────────────────────────────────────────
|
|
13
15
|
const connectionDoc = v.object({
|
|
14
16
|
_id: v.id("connections"),
|
|
@@ -18,6 +20,7 @@ const connectionDoc = v.object({
|
|
|
18
20
|
providerUserId: v.optional(v.string()),
|
|
19
21
|
active: v.optional(v.boolean()),
|
|
20
22
|
lastDataUpdate: v.optional(v.string()),
|
|
23
|
+
stats: v.optional(statsValidator),
|
|
21
24
|
});
|
|
22
25
|
// ─── Connect / Disconnect ───────────────────────────────────────────────────
|
|
23
26
|
/**
|
|
@@ -110,6 +113,39 @@ export const getConnectionByProvider = query({
|
|
|
110
113
|
.first();
|
|
111
114
|
},
|
|
112
115
|
});
|
|
116
|
+
/**
|
|
117
|
+
* Get per-table row counts and oldest-data timestamps for a user–provider pair.
|
|
118
|
+
*
|
|
119
|
+
* Stats are denormalized onto the connection document and maintained
|
|
120
|
+
* automatically by ingest mutations, so this is an O(1) read regardless of
|
|
121
|
+
* how much data has been ingested.
|
|
122
|
+
*
|
|
123
|
+
* `oldest` is a lower bound on the earliest `metadata.start_time` (or
|
|
124
|
+
* `metadata.planned_date` for planned workouts). It can theoretically go
|
|
125
|
+
* stale if a provider mutates `start_time` on an existing record to a later
|
|
126
|
+
* value — this would also break the existing dedup strategy, so treat it as
|
|
127
|
+
* pathological.
|
|
128
|
+
*
|
|
129
|
+
* Returns `null` if the user has never connected to that provider. When a
|
|
130
|
+
* connection exists but no data has been ingested, every table returns
|
|
131
|
+
* `{ count: 0, oldest: null }` — callers never need to guard optional keys.
|
|
132
|
+
*/
|
|
133
|
+
export const getProviderStats = query({
|
|
134
|
+
args: {
|
|
135
|
+
userId: v.string(),
|
|
136
|
+
provider: v.string(),
|
|
137
|
+
},
|
|
138
|
+
returns: v.union(v.null(), statsValidator),
|
|
139
|
+
handler: async (ctx, args) => {
|
|
140
|
+
const connection = await ctx.db
|
|
141
|
+
.query("connections")
|
|
142
|
+
.withIndex("by_userId_provider", (q) => q.eq("userId", args.userId).eq("provider", args.provider))
|
|
143
|
+
.first();
|
|
144
|
+
if (!connection)
|
|
145
|
+
return null;
|
|
146
|
+
return normalizeStats(connection.stats);
|
|
147
|
+
},
|
|
148
|
+
});
|
|
113
149
|
/**
|
|
114
150
|
* List all connections for a user (active and inactive).
|
|
115
151
|
*/
|
|
@@ -185,11 +221,16 @@ export const ingestActivity = mutation({
|
|
|
185
221
|
.eq("connectionId", args.connectionId)
|
|
186
222
|
.eq("metadata.summary_id", args.metadata.summary_id))
|
|
187
223
|
.first();
|
|
224
|
+
let id;
|
|
188
225
|
if (existing) {
|
|
189
226
|
await ctx.db.patch(existing._id, args);
|
|
190
|
-
|
|
227
|
+
id = existing._id;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
id = await ctx.db.insert("activities", args);
|
|
191
231
|
}
|
|
192
|
-
|
|
232
|
+
await updateStatsOnIngest(ctx, args.connectionId, "activities", args.metadata.start_time, !existing);
|
|
233
|
+
return id;
|
|
193
234
|
},
|
|
194
235
|
});
|
|
195
236
|
/**
|
|
@@ -203,19 +244,25 @@ export const ingestSleep = mutation({
|
|
|
203
244
|
returns: v.id("sleep"),
|
|
204
245
|
handler: async (ctx, args) => {
|
|
205
246
|
const summaryId = args.metadata.summary_id;
|
|
247
|
+
let existing = null;
|
|
206
248
|
if (summaryId) {
|
|
207
|
-
|
|
249
|
+
existing = await ctx.db
|
|
208
250
|
.query("sleep")
|
|
209
251
|
.withIndex("by_connectionId_summaryId", (q) => q
|
|
210
252
|
.eq("connectionId", args.connectionId)
|
|
211
253
|
.eq("metadata.summary_id", summaryId))
|
|
212
254
|
.first();
|
|
213
|
-
if (existing) {
|
|
214
|
-
await ctx.db.patch(existing._id, args);
|
|
215
|
-
return existing._id;
|
|
216
|
-
}
|
|
217
255
|
}
|
|
218
|
-
|
|
256
|
+
let id;
|
|
257
|
+
if (existing) {
|
|
258
|
+
await ctx.db.patch(existing._id, args);
|
|
259
|
+
id = existing._id;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
id = await ctx.db.insert("sleep", args);
|
|
263
|
+
}
|
|
264
|
+
await updateStatsOnIngest(ctx, args.connectionId, "sleep", args.metadata.start_time, !existing);
|
|
265
|
+
return id;
|
|
219
266
|
},
|
|
220
267
|
});
|
|
221
268
|
/**
|
|
@@ -234,11 +281,16 @@ export const ingestBody = mutation({
|
|
|
234
281
|
.eq("metadata.start_time", args.metadata.start_time)
|
|
235
282
|
.eq("metadata.end_time", args.metadata.end_time))
|
|
236
283
|
.first();
|
|
284
|
+
let id;
|
|
237
285
|
if (existing) {
|
|
238
286
|
await ctx.db.patch(existing._id, args);
|
|
239
|
-
|
|
287
|
+
id = existing._id;
|
|
240
288
|
}
|
|
241
|
-
|
|
289
|
+
else {
|
|
290
|
+
id = await ctx.db.insert("body", args);
|
|
291
|
+
}
|
|
292
|
+
await updateStatsOnIngest(ctx, args.connectionId, "body", args.metadata.start_time, !existing);
|
|
293
|
+
return id;
|
|
242
294
|
},
|
|
243
295
|
});
|
|
244
296
|
/**
|
|
@@ -257,11 +309,16 @@ export const ingestDaily = mutation({
|
|
|
257
309
|
.eq("metadata.start_time", args.metadata.start_time)
|
|
258
310
|
.eq("metadata.end_time", args.metadata.end_time))
|
|
259
311
|
.first();
|
|
312
|
+
let id;
|
|
260
313
|
if (existing) {
|
|
261
314
|
await ctx.db.patch(existing._id, args);
|
|
262
|
-
|
|
315
|
+
id = existing._id;
|
|
263
316
|
}
|
|
264
|
-
|
|
317
|
+
else {
|
|
318
|
+
id = await ctx.db.insert("daily", args);
|
|
319
|
+
}
|
|
320
|
+
await updateStatsOnIngest(ctx, args.connectionId, "daily", args.metadata.start_time, !existing);
|
|
321
|
+
return id;
|
|
265
322
|
},
|
|
266
323
|
});
|
|
267
324
|
/**
|
|
@@ -280,11 +337,16 @@ export const ingestNutrition = mutation({
|
|
|
280
337
|
.eq("metadata.start_time", args.metadata.start_time)
|
|
281
338
|
.eq("metadata.end_time", args.metadata.end_time))
|
|
282
339
|
.first();
|
|
340
|
+
let id;
|
|
283
341
|
if (existing) {
|
|
284
342
|
await ctx.db.patch(existing._id, args);
|
|
285
|
-
|
|
343
|
+
id = existing._id;
|
|
286
344
|
}
|
|
287
|
-
|
|
345
|
+
else {
|
|
346
|
+
id = await ctx.db.insert("nutrition", args);
|
|
347
|
+
}
|
|
348
|
+
await updateStatsOnIngest(ctx, args.connectionId, "nutrition", args.metadata.start_time, !existing);
|
|
349
|
+
return id;
|
|
288
350
|
},
|
|
289
351
|
});
|
|
290
352
|
/**
|
|
@@ -296,7 +358,9 @@ export const ingestMenstruation = mutation({
|
|
|
296
358
|
args: menstruationValidator,
|
|
297
359
|
returns: v.id("menstruation"),
|
|
298
360
|
handler: async (ctx, args) => {
|
|
299
|
-
|
|
361
|
+
const id = await ctx.db.insert("menstruation", args);
|
|
362
|
+
await updateStatsOnIngest(ctx, args.connectionId, "menstruation", args.metadata.start_time, true);
|
|
363
|
+
return id;
|
|
300
364
|
},
|
|
301
365
|
});
|
|
302
366
|
/**
|
|
@@ -727,18 +791,24 @@ export const ingestPlannedWorkout = mutation({
|
|
|
727
791
|
returns: v.id("plannedWorkouts"),
|
|
728
792
|
handler: async (ctx, args) => {
|
|
729
793
|
const metadataId = args.metadata.id;
|
|
794
|
+
let existing = null;
|
|
730
795
|
if (metadataId) {
|
|
731
796
|
const results = await ctx.db
|
|
732
797
|
.query("plannedWorkouts")
|
|
733
798
|
.withIndex("by_connectionId", (q) => q.eq("connectionId", args.connectionId))
|
|
734
799
|
.collect();
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
800
|
+
existing = results.find((r) => r.metadata.id === metadataId) ?? null;
|
|
801
|
+
}
|
|
802
|
+
let id;
|
|
803
|
+
if (existing) {
|
|
804
|
+
await ctx.db.patch(existing._id, args);
|
|
805
|
+
id = existing._id;
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
id = await ctx.db.insert("plannedWorkouts", args);
|
|
740
809
|
}
|
|
741
|
-
|
|
810
|
+
await updateStatsOnIngest(ctx, args.connectionId, "plannedWorkouts", args.metadata.planned_date, !existing);
|
|
811
|
+
return id;
|
|
742
812
|
},
|
|
743
813
|
});
|
|
744
814
|
/**
|