gymmonk-schema 0.24.0 → 0.26.0
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/announcement.d.ts +112 -7
- package/dist/announcement.d.ts.map +1 -1
- package/dist/announcement.js +88 -13
- package/dist/announcement.js.map +1 -1
- package/dist/center.d.ts +16 -2
- package/dist/center.d.ts.map +1 -1
- package/dist/center.js +19 -0
- package/dist/center.js.map +1 -1
- package/dist/gym-registration.d.ts +3 -0
- package/dist/gym-registration.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/location.d.ts +110 -0
- package/dist/location.d.ts.map +1 -0
- package/dist/location.js +133 -0
- package/dist/location.js.map +1 -0
- package/dist/onboarding.d.ts +3 -0
- package/dist/onboarding.d.ts.map +1 -1
- package/dist/organisation.d.ts +24 -4
- package/dist/organisation.d.ts.map +1 -1
- package/dist/organisation.js +9 -0
- package/dist/organisation.js.map +1 -1
- package/dist/session.d.ts +3 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +8 -2
- package/dist/session.js.map +1 -1
- package/package.json +1 -1
package/dist/announcement.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* gymmonk-schema — Owner announcements
|
|
3
3
|
* ====================================
|
|
4
|
-
* Broadcasts an owner sends to a center's members (now or scheduled).
|
|
5
|
-
* as notifications + push.
|
|
4
|
+
* Broadcasts an owner sends to a center's members and staff (now or scheduled).
|
|
5
|
+
* Delivered as notifications + push.
|
|
6
|
+
*
|
|
7
|
+
* SINGLE SOURCE OF TRUTH. The listing query, the update body and the
|
|
8
|
+
* past-schedule guard lived in gymmonk-backend and gymmonk-web-client as two
|
|
9
|
+
* hand-kept mirrors before this. They are here so a field can only be added
|
|
10
|
+
* once: zod STRIPS unknown keys, so a mirror that drifts does not fail loudly,
|
|
11
|
+
* it silently drops the field somewhere between the browser and the database.
|
|
6
12
|
*
|
|
7
13
|
* @module gymmonk-schema/announcement
|
|
8
14
|
*/
|
|
@@ -12,9 +18,23 @@ export declare const announcementIconSchema: z.ZodEnum<{
|
|
|
12
18
|
warning: "warning";
|
|
13
19
|
}>;
|
|
14
20
|
export type AnnouncementIcon = z.infer<typeof announcementIconSchema>;
|
|
21
|
+
/**
|
|
22
|
+
* An announcement is a PRESENT EVENT.
|
|
23
|
+
*
|
|
24
|
+
* `scheduled` is gone. A broadcast now goes out the moment it is created, which
|
|
25
|
+
* removes the whole apparatus that existed to make "later" work: the due sweep,
|
|
26
|
+
* the claim protocol, the past-date guard, and the Cloud Scheduler job that had
|
|
27
|
+
* to wake a scale-to-zero service every minute for it to mean anything. That
|
|
28
|
+
* job never existed in production, so every scheduled announcement was in fact
|
|
29
|
+
* delivered "the next time somebody opened the app" — the feature promised a
|
|
30
|
+
* time and could not keep it.
|
|
31
|
+
*
|
|
32
|
+
* `draft` stays, and is not a contradiction: a draft is not a queued
|
|
33
|
+
* announcement, it is an unsent note. Sending it is still a present event, it
|
|
34
|
+
* just happens when the owner presses the button rather than when they save.
|
|
35
|
+
*/
|
|
15
36
|
export declare const announcementStatusSchema: z.ZodEnum<{
|
|
16
37
|
draft: "draft";
|
|
17
|
-
scheduled: "scheduled";
|
|
18
38
|
sent: "sent";
|
|
19
39
|
}>;
|
|
20
40
|
export type AnnouncementStatus = z.infer<typeof announcementStatusSchema>;
|
|
@@ -29,15 +49,21 @@ export declare const announcementSchema: z.ZodObject<{
|
|
|
29
49
|
body: z.ZodString;
|
|
30
50
|
status: z.ZodEnum<{
|
|
31
51
|
draft: "draft";
|
|
32
|
-
scheduled: "scheduled";
|
|
33
52
|
sent: "sent";
|
|
34
53
|
}>;
|
|
35
|
-
scheduledAt: z.ZodNullable<z.ZodISODateTime>;
|
|
36
54
|
sentAt: z.ZodNullable<z.ZodISODateTime>;
|
|
55
|
+
deliveredCount: z.ZodNullable<z.ZodNumber>;
|
|
37
56
|
createdAt: z.ZodISODateTime;
|
|
38
57
|
updatedAt: z.ZodISODateTime;
|
|
39
58
|
}, z.core.$strip>;
|
|
40
59
|
export type Announcement = z.infer<typeof announcementSchema>;
|
|
60
|
+
/**
|
|
61
|
+
* Write one and it goes out, unless it is explicitly saved as a draft.
|
|
62
|
+
*
|
|
63
|
+
* No `scheduledAt`, and no refinements: with delivery immediate there is
|
|
64
|
+
* nothing left to cross-validate. The two that used to be here both existed to
|
|
65
|
+
* police a future time that no longer exists.
|
|
66
|
+
*/
|
|
41
67
|
export declare const createAnnouncementBodySchema: z.ZodObject<{
|
|
42
68
|
icon: z.ZodDefault<z.ZodEnum<{
|
|
43
69
|
megaphone: "megaphone";
|
|
@@ -45,12 +71,91 @@ export declare const createAnnouncementBodySchema: z.ZodObject<{
|
|
|
45
71
|
}>>;
|
|
46
72
|
title: z.ZodString;
|
|
47
73
|
body: z.ZodString;
|
|
48
|
-
scheduledAt: z.ZodOptional<z.ZodISODateTime>;
|
|
49
74
|
status: z.ZodDefault<z.ZodEnum<{
|
|
50
75
|
draft: "draft";
|
|
51
|
-
scheduled: "scheduled";
|
|
52
76
|
sent: "sent";
|
|
53
77
|
}>>;
|
|
54
78
|
}, z.core.$strip>;
|
|
55
79
|
export type CreateAnnouncementBody = z.infer<typeof createAnnouncementBodySchema>;
|
|
80
|
+
/** The create body plus the center it belongs to, as the route receives it. */
|
|
81
|
+
export declare const createAnnouncementWithCenterSchema: z.ZodObject<{
|
|
82
|
+
icon: z.ZodDefault<z.ZodEnum<{
|
|
83
|
+
megaphone: "megaphone";
|
|
84
|
+
warning: "warning";
|
|
85
|
+
}>>;
|
|
86
|
+
title: z.ZodString;
|
|
87
|
+
body: z.ZodString;
|
|
88
|
+
status: z.ZodDefault<z.ZodEnum<{
|
|
89
|
+
draft: "draft";
|
|
90
|
+
sent: "sent";
|
|
91
|
+
}>>;
|
|
92
|
+
centerId: z.ZodString;
|
|
93
|
+
}, z.core.$strip>;
|
|
94
|
+
export type CreateAnnouncementWithCenterBody = z.infer<typeof createAnnouncementWithCenterSchema>;
|
|
95
|
+
/**
|
|
96
|
+
* Edit a draft, and send it.
|
|
97
|
+
*
|
|
98
|
+
* Every field is optional because this is a PATCH: an owner fixing a typo sends
|
|
99
|
+
* the title alone. `status: 'sent'` is how a draft finally goes out, which is
|
|
100
|
+
* the whole reason this exists — before it there was no update endpoint at all,
|
|
101
|
+
* so "Save as Draft" wrote a row that could only ever be deleted.
|
|
102
|
+
*/
|
|
103
|
+
export declare const updateAnnouncementBodySchema: z.ZodObject<{
|
|
104
|
+
icon: z.ZodOptional<z.ZodEnum<{
|
|
105
|
+
megaphone: "megaphone";
|
|
106
|
+
warning: "warning";
|
|
107
|
+
}>>;
|
|
108
|
+
title: z.ZodOptional<z.ZodString>;
|
|
109
|
+
body: z.ZodOptional<z.ZodString>;
|
|
110
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
111
|
+
draft: "draft";
|
|
112
|
+
sent: "sent";
|
|
113
|
+
}>>;
|
|
114
|
+
}, z.core.$strip>;
|
|
115
|
+
export type UpdateAnnouncementBody = z.infer<typeof updateAnnouncementBodySchema>;
|
|
116
|
+
/**
|
|
117
|
+
* How the list is ordered. `newest` by default, because the screen's job is
|
|
118
|
+
* "what did we just send".
|
|
119
|
+
*
|
|
120
|
+
* There was a third, `upcoming`, ordering by the scheduled time. It went with
|
|
121
|
+
* scheduling.
|
|
122
|
+
*/
|
|
123
|
+
export declare const announcementSortSchema: z.ZodEnum<{
|
|
124
|
+
newest: "newest";
|
|
125
|
+
oldest: "oldest";
|
|
126
|
+
}>;
|
|
127
|
+
export type AnnouncementSort = z.infer<typeof announcementSortSchema>;
|
|
128
|
+
/** `all` is a real choice, not an absent filter, so the UI can round-trip it. */
|
|
129
|
+
export declare const announcementStatusFilterSchema: z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodEnum<{
|
|
130
|
+
draft: "draft";
|
|
131
|
+
sent: "sent";
|
|
132
|
+
}>]>;
|
|
133
|
+
export type AnnouncementStatusFilter = z.infer<typeof announcementStatusFilterSchema>;
|
|
134
|
+
/**
|
|
135
|
+
* The listing query.
|
|
136
|
+
*
|
|
137
|
+
* Everything arrives as a STRING off the query string, so `paginationSchema`
|
|
138
|
+
* coerces page and limit. Without the coercion `page=2` fails validation and
|
|
139
|
+
* the client gets a 400 for a link it built correctly.
|
|
140
|
+
*/
|
|
141
|
+
export declare const listAnnouncementsQuerySchema: z.ZodObject<{
|
|
142
|
+
page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
143
|
+
limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
144
|
+
centerId: z.ZodString;
|
|
145
|
+
q: z.ZodOptional<z.ZodString>;
|
|
146
|
+
status: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodEnum<{
|
|
147
|
+
draft: "draft";
|
|
148
|
+
sent: "sent";
|
|
149
|
+
}>]>>;
|
|
150
|
+
sort: z.ZodDefault<z.ZodEnum<{
|
|
151
|
+
newest: "newest";
|
|
152
|
+
oldest: "oldest";
|
|
153
|
+
}>>;
|
|
154
|
+
}, z.core.$strip>;
|
|
155
|
+
export type ListAnnouncementsQuery = z.infer<typeof listAnnouncementsQuerySchema>;
|
|
156
|
+
/** Just the center, for routes that identify the row by id but still scope it. */
|
|
157
|
+
export declare const centerScopeQuerySchema: z.ZodObject<{
|
|
158
|
+
centerId: z.ZodString;
|
|
159
|
+
}, z.core.$strip>;
|
|
160
|
+
export type CenterScopeQuery = z.infer<typeof centerScopeQuerySchema>;
|
|
56
161
|
//# sourceMappingURL=announcement.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"announcement.d.ts","sourceRoot":"","sources":["../src/announcement.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"announcement.d.ts","sourceRoot":"","sources":["../src/announcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,sBAAsB;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB;;;EAA4B,CAAC;AAClE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAI1E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;iBAmB7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;iBAKvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,+EAA+E;AAC/E,eAAO,MAAM,kCAAkC;;;;;;;;;;;;iBAE7C,CAAC;AACH,MAAM,MAAM,gCAAgC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAIlG;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;iBAKvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAIlF;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB;;;EAA+B,CAAC;AACnE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,iFAAiF;AACjF,eAAO,MAAM,8BAA8B;;;IAAwD,CAAC;AACpG,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAEtF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;iBAMvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,kFAAkF;AAClF,eAAO,MAAM,sBAAsB;;iBAAyC,CAAC;AAC7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
|
package/dist/announcement.js
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* gymmonk-schema — Owner announcements
|
|
3
3
|
* ====================================
|
|
4
|
-
* Broadcasts an owner sends to a center's members (now or scheduled).
|
|
5
|
-
* as notifications + push.
|
|
4
|
+
* Broadcasts an owner sends to a center's members and staff (now or scheduled).
|
|
5
|
+
* Delivered as notifications + push.
|
|
6
|
+
*
|
|
7
|
+
* SINGLE SOURCE OF TRUTH. The listing query, the update body and the
|
|
8
|
+
* past-schedule guard lived in gymmonk-backend and gymmonk-web-client as two
|
|
9
|
+
* hand-kept mirrors before this. They are here so a field can only be added
|
|
10
|
+
* once: zod STRIPS unknown keys, so a mirror that drifts does not fail loudly,
|
|
11
|
+
* it silently drops the field somewhere between the browser and the database.
|
|
6
12
|
*
|
|
7
13
|
* @module gymmonk-schema/announcement
|
|
8
14
|
*/
|
|
9
15
|
import { z } from 'zod';
|
|
10
|
-
import { isoDateTimeSchema, objectIdSchema } from './common.js';
|
|
16
|
+
import { isoDateTimeSchema, objectIdSchema, paginationSchema } from './common.js';
|
|
11
17
|
export const announcementIconSchema = z.enum(['megaphone', 'warning']);
|
|
12
|
-
|
|
18
|
+
/**
|
|
19
|
+
* An announcement is a PRESENT EVENT.
|
|
20
|
+
*
|
|
21
|
+
* `scheduled` is gone. A broadcast now goes out the moment it is created, which
|
|
22
|
+
* removes the whole apparatus that existed to make "later" work: the due sweep,
|
|
23
|
+
* the claim protocol, the past-date guard, and the Cloud Scheduler job that had
|
|
24
|
+
* to wake a scale-to-zero service every minute for it to mean anything. That
|
|
25
|
+
* job never existed in production, so every scheduled announcement was in fact
|
|
26
|
+
* delivered "the next time somebody opened the app" — the feature promised a
|
|
27
|
+
* time and could not keep it.
|
|
28
|
+
*
|
|
29
|
+
* `draft` stays, and is not a contradiction: a draft is not a queued
|
|
30
|
+
* announcement, it is an unsent note. Sending it is still a present event, it
|
|
31
|
+
* just happens when the owner presses the button rather than when they save.
|
|
32
|
+
*/
|
|
33
|
+
export const announcementStatusSchema = z.enum(['draft', 'sent']);
|
|
13
34
|
// ─── Entity ──────────────────────────────────────────────────────────────────
|
|
14
35
|
export const announcementSchema = z.object({
|
|
15
36
|
id: objectIdSchema,
|
|
@@ -18,23 +39,77 @@ export const announcementSchema = z.object({
|
|
|
18
39
|
title: z.string(),
|
|
19
40
|
body: z.string(),
|
|
20
41
|
status: announcementStatusSchema,
|
|
21
|
-
scheduledAt: isoDateTimeSchema.nullable(),
|
|
22
42
|
sentAt: isoDateTimeSchema.nullable(),
|
|
43
|
+
/**
|
|
44
|
+
* How many people it actually reached, once delivery has been attempted.
|
|
45
|
+
*
|
|
46
|
+
* `null` means "not attempted yet", which is NOT the same as `0` ("attempted,
|
|
47
|
+
* nobody to tell"). An owner broadcasting to an empty roster and an owner
|
|
48
|
+
* whose broadcast has not run yet are different situations, and collapsing
|
|
49
|
+
* both to zero would leave the screen unable to say which.
|
|
50
|
+
*/
|
|
51
|
+
deliveredCount: z.number().int().nullable(),
|
|
23
52
|
createdAt: isoDateTimeSchema,
|
|
24
53
|
updatedAt: isoDateTimeSchema,
|
|
25
54
|
});
|
|
26
55
|
// ─── Create body ─────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Write one and it goes out, unless it is explicitly saved as a draft.
|
|
58
|
+
*
|
|
59
|
+
* No `scheduledAt`, and no refinements: with delivery immediate there is
|
|
60
|
+
* nothing left to cross-validate. The two that used to be here both existed to
|
|
61
|
+
* police a future time that no longer exists.
|
|
62
|
+
*/
|
|
63
|
+
export const createAnnouncementBodySchema = z.object({
|
|
29
64
|
icon: announcementIconSchema.default('megaphone'),
|
|
30
65
|
title: z.string().trim().min(1, 'Title is required').max(120),
|
|
31
66
|
body: z.string().trim().min(1, 'Message is required').max(250),
|
|
32
|
-
/** Required when `status` is `scheduled`; otherwise sent immediately. */
|
|
33
|
-
scheduledAt: isoDateTimeSchema.optional(),
|
|
34
67
|
status: announcementStatusSchema.default('sent'),
|
|
35
|
-
})
|
|
36
|
-
.refine((v) => v.status !== 'scheduled' || v.scheduledAt != null, {
|
|
37
|
-
message: 'Pick a schedule time',
|
|
38
|
-
path: ['scheduledAt'],
|
|
39
68
|
});
|
|
69
|
+
/** The create body plus the center it belongs to, as the route receives it. */
|
|
70
|
+
export const createAnnouncementWithCenterSchema = createAnnouncementBodySchema.extend({
|
|
71
|
+
centerId: objectIdSchema,
|
|
72
|
+
});
|
|
73
|
+
// ─── Update body ─────────────────────────────────────────────────────────────
|
|
74
|
+
/**
|
|
75
|
+
* Edit a draft, and send it.
|
|
76
|
+
*
|
|
77
|
+
* Every field is optional because this is a PATCH: an owner fixing a typo sends
|
|
78
|
+
* the title alone. `status: 'sent'` is how a draft finally goes out, which is
|
|
79
|
+
* the whole reason this exists — before it there was no update endpoint at all,
|
|
80
|
+
* so "Save as Draft" wrote a row that could only ever be deleted.
|
|
81
|
+
*/
|
|
82
|
+
export const updateAnnouncementBodySchema = z.object({
|
|
83
|
+
icon: announcementIconSchema.optional(),
|
|
84
|
+
title: z.string().trim().min(1, 'Title is required').max(120).optional(),
|
|
85
|
+
body: z.string().trim().min(1, 'Message is required').max(250).optional(),
|
|
86
|
+
status: announcementStatusSchema.optional(),
|
|
87
|
+
});
|
|
88
|
+
// ─── Listing query ───────────────────────────────────────────────────────────
|
|
89
|
+
/**
|
|
90
|
+
* How the list is ordered. `newest` by default, because the screen's job is
|
|
91
|
+
* "what did we just send".
|
|
92
|
+
*
|
|
93
|
+
* There was a third, `upcoming`, ordering by the scheduled time. It went with
|
|
94
|
+
* scheduling.
|
|
95
|
+
*/
|
|
96
|
+
export const announcementSortSchema = z.enum(['newest', 'oldest']);
|
|
97
|
+
/** `all` is a real choice, not an absent filter, so the UI can round-trip it. */
|
|
98
|
+
export const announcementStatusFilterSchema = z.union([z.literal('all'), announcementStatusSchema]);
|
|
99
|
+
/**
|
|
100
|
+
* The listing query.
|
|
101
|
+
*
|
|
102
|
+
* Everything arrives as a STRING off the query string, so `paginationSchema`
|
|
103
|
+
* coerces page and limit. Without the coercion `page=2` fails validation and
|
|
104
|
+
* the client gets a 400 for a link it built correctly.
|
|
105
|
+
*/
|
|
106
|
+
export const listAnnouncementsQuerySchema = paginationSchema.extend({
|
|
107
|
+
centerId: objectIdSchema,
|
|
108
|
+
/** Free text over title and body. Trimmed; empty means "no search". */
|
|
109
|
+
q: z.string().trim().max(120).optional(),
|
|
110
|
+
status: announcementStatusFilterSchema.default('all'),
|
|
111
|
+
sort: announcementSortSchema.default('newest'),
|
|
112
|
+
});
|
|
113
|
+
/** Just the center, for routes that identify the row by id but still scope it. */
|
|
114
|
+
export const centerScopeQuerySchema = z.object({ centerId: objectIdSchema });
|
|
40
115
|
//# sourceMappingURL=announcement.js.map
|
package/dist/announcement.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"announcement.js","sourceRoot":"","sources":["../src/announcement.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"announcement.js","sourceRoot":"","sources":["../src/announcement.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Owner announcements\n * ====================================\n * Broadcasts an owner sends to a center's members and staff (now or scheduled).\n * Delivered as notifications + push.\n *\n * SINGLE SOURCE OF TRUTH. The listing query, the update body and the\n * past-schedule guard lived in gymmonk-backend and gymmonk-web-client as two\n * hand-kept mirrors before this. They are here so a field can only be added\n * once: zod STRIPS unknown keys, so a mirror that drifts does not fail loudly,\n * it silently drops the field somewhere between the browser and the database.\n *\n * @module gymmonk-schema/announcement\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema, paginationSchema } from './common.js';\n\nexport const announcementIconSchema = z.enum(['megaphone', 'warning']);\nexport type AnnouncementIcon = z.infer<typeof announcementIconSchema>;\n\n/**\n * An announcement is a PRESENT EVENT.\n *\n * `scheduled` is gone. A broadcast now goes out the moment it is created, which\n * removes the whole apparatus that existed to make \"later\" work: the due sweep,\n * the claim protocol, the past-date guard, and the Cloud Scheduler job that had\n * to wake a scale-to-zero service every minute for it to mean anything. That\n * job never existed in production, so every scheduled announcement was in fact\n * delivered \"the next time somebody opened the app\" — the feature promised a\n * time and could not keep it.\n *\n * `draft` stays, and is not a contradiction: a draft is not a queued\n * announcement, it is an unsent note. Sending it is still a present event, it\n * just happens when the owner presses the button rather than when they save.\n */\nexport const announcementStatusSchema = z.enum(['draft', 'sent']);\nexport type AnnouncementStatus = z.infer<typeof announcementStatusSchema>;\n\n// ─── Entity ──────────────────────────────────────────────────────────────────\n\nexport const announcementSchema = z.object({\n id: objectIdSchema,\n centerId: objectIdSchema,\n icon: announcementIconSchema,\n title: z.string(),\n body: z.string(),\n status: announcementStatusSchema,\n sentAt: isoDateTimeSchema.nullable(),\n /**\n * How many people it actually reached, once delivery has been attempted.\n *\n * `null` means \"not attempted yet\", which is NOT the same as `0` (\"attempted,\n * nobody to tell\"). An owner broadcasting to an empty roster and an owner\n * whose broadcast has not run yet are different situations, and collapsing\n * both to zero would leave the screen unable to say which.\n */\n deliveredCount: z.number().int().nullable(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Announcement = z.infer<typeof announcementSchema>;\n\n// ─── Create body ─────────────────────────────────────────────────────────────\n\n/**\n * Write one and it goes out, unless it is explicitly saved as a draft.\n *\n * No `scheduledAt`, and no refinements: with delivery immediate there is\n * nothing left to cross-validate. The two that used to be here both existed to\n * police a future time that no longer exists.\n */\nexport const createAnnouncementBodySchema = z.object({\n icon: announcementIconSchema.default('megaphone'),\n title: z.string().trim().min(1, 'Title is required').max(120),\n body: z.string().trim().min(1, 'Message is required').max(250),\n status: announcementStatusSchema.default('sent'),\n});\nexport type CreateAnnouncementBody = z.infer<typeof createAnnouncementBodySchema>;\n\n/** The create body plus the center it belongs to, as the route receives it. */\nexport const createAnnouncementWithCenterSchema = createAnnouncementBodySchema.extend({\n centerId: objectIdSchema,\n});\nexport type CreateAnnouncementWithCenterBody = z.infer<typeof createAnnouncementWithCenterSchema>;\n\n// ─── Update body ─────────────────────────────────────────────────────────────\n\n/**\n * Edit a draft, and send it.\n *\n * Every field is optional because this is a PATCH: an owner fixing a typo sends\n * the title alone. `status: 'sent'` is how a draft finally goes out, which is\n * the whole reason this exists — before it there was no update endpoint at all,\n * so \"Save as Draft\" wrote a row that could only ever be deleted.\n */\nexport const updateAnnouncementBodySchema = z.object({\n icon: announcementIconSchema.optional(),\n title: z.string().trim().min(1, 'Title is required').max(120).optional(),\n body: z.string().trim().min(1, 'Message is required').max(250).optional(),\n status: announcementStatusSchema.optional(),\n});\nexport type UpdateAnnouncementBody = z.infer<typeof updateAnnouncementBodySchema>;\n\n// ─── Listing query ───────────────────────────────────────────────────────────\n\n/**\n * How the list is ordered. `newest` by default, because the screen's job is\n * \"what did we just send\".\n *\n * There was a third, `upcoming`, ordering by the scheduled time. It went with\n * scheduling.\n */\nexport const announcementSortSchema = z.enum(['newest', 'oldest']);\nexport type AnnouncementSort = z.infer<typeof announcementSortSchema>;\n\n/** `all` is a real choice, not an absent filter, so the UI can round-trip it. */\nexport const announcementStatusFilterSchema = z.union([z.literal('all'), announcementStatusSchema]);\nexport type AnnouncementStatusFilter = z.infer<typeof announcementStatusFilterSchema>;\n\n/**\n * The listing query.\n *\n * Everything arrives as a STRING off the query string, so `paginationSchema`\n * coerces page and limit. Without the coercion `page=2` fails validation and\n * the client gets a 400 for a link it built correctly.\n */\nexport const listAnnouncementsQuerySchema = paginationSchema.extend({\n centerId: objectIdSchema,\n /** Free text over title and body. Trimmed; empty means \"no search\". */\n q: z.string().trim().max(120).optional(),\n status: announcementStatusFilterSchema.default('all'),\n sort: announcementSortSchema.default('newest'),\n});\nexport type ListAnnouncementsQuery = z.infer<typeof listAnnouncementsQuerySchema>;\n\n/** Just the center, for routes that identify the row by id but still scope it. */\nexport const centerScopeQuerySchema = z.object({ centerId: objectIdSchema });\nexport type CenterScopeQuery = z.infer<typeof centerScopeQuerySchema>;\n"]}
|
package/dist/center.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export type CenterStatus = z.infer<typeof centerStatusSchema>;
|
|
|
37
37
|
export declare const gymCodeSchema: z.ZodString;
|
|
38
38
|
export type GymCode = z.infer<typeof gymCodeSchema>;
|
|
39
39
|
export declare const createCenterBodySchema: z.ZodObject<{
|
|
40
|
+
checkInRadiusM: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
mapLinkUrl: z.ZodOptional<z.ZodString>;
|
|
40
42
|
name: z.ZodString;
|
|
41
43
|
description: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
42
44
|
types: z.ZodArray<z.ZodEnum<{
|
|
@@ -137,6 +139,8 @@ export declare const createCenterBodySchema: z.ZodObject<{
|
|
|
137
139
|
}, z.core.$strip>;
|
|
138
140
|
export type CreateCenterBody = z.infer<typeof createCenterBodySchema>;
|
|
139
141
|
export declare const updateCenterBodySchema: z.ZodObject<{
|
|
142
|
+
checkInRadiusM: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
143
|
+
mapLinkUrl: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
140
144
|
name: z.ZodOptional<z.ZodString>;
|
|
141
145
|
description: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
|
|
142
146
|
types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
@@ -237,6 +241,18 @@ export declare const updateCenterBodySchema: z.ZodObject<{
|
|
|
237
241
|
}, z.core.$strip>;
|
|
238
242
|
export type UpdateCenterBody = z.infer<typeof updateCenterBodySchema>;
|
|
239
243
|
export declare const centerSchema: z.ZodObject<{
|
|
244
|
+
checkInRadiusM: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
245
|
+
createdAt: z.ZodISODateTime;
|
|
246
|
+
updatedAt: z.ZodISODateTime;
|
|
247
|
+
mapLink: z.ZodOptional<z.ZodObject<{
|
|
248
|
+
url: z.ZodString;
|
|
249
|
+
lat: z.ZodOptional<z.ZodNumber>;
|
|
250
|
+
lng: z.ZodOptional<z.ZodNumber>;
|
|
251
|
+
}, z.core.$strip>>;
|
|
252
|
+
location: z.ZodOptional<z.ZodObject<{
|
|
253
|
+
type: z.ZodLiteral<"Point">;
|
|
254
|
+
coordinates: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
255
|
+
}, z.core.$strip>>;
|
|
240
256
|
id: z.ZodString;
|
|
241
257
|
organisationId: z.ZodString;
|
|
242
258
|
code: z.ZodString;
|
|
@@ -342,8 +358,6 @@ export declare const centerSchema: z.ZodObject<{
|
|
|
342
358
|
inactive: "inactive";
|
|
343
359
|
}>;
|
|
344
360
|
memberCount: z.ZodNumber;
|
|
345
|
-
createdAt: z.ZodISODateTime;
|
|
346
|
-
updatedAt: z.ZodISODateTime;
|
|
347
361
|
}, z.core.$strip>;
|
|
348
362
|
export type Center = z.infer<typeof centerSchema>;
|
|
349
363
|
/**
|
package/dist/center.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"center.d.ts","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"center.d.ts","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB,yDAAyD;AACzD,eAAO,MAAM,YAAY,8IAaf,CAAC;AACX,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;EAAuB,CAAC;AACrD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,eAAO,MAAM,kBAAkB;;;EAAiC,CAAC;AACjE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;GAGG;AACH,eAAO,MAAM,aAAa,aAGwB,CAAC;AACnD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAIpD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqCjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAItE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BvB,CAAC;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAIlD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB;;;;iBAI5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D,yEAAyE;AACzE,eAAO,MAAM,eAAe;;;;;;;;;iBAkB1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,eAAO,MAAM,oBAAoB;;;;;;;;iBAwB7B,CAAC;AACL,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAKpE,eAAO,MAAM,qBAAqB;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
package/dist/center.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
import { isoDateTimeSchema, objectIdSchema, optionalEmailSchema, optionalPhoneSchema, optionalTrimmedString, phoneSchema, } from './common.js';
|
|
12
|
+
import { mapLinkInputSchema, placePinSchema } from './location.js';
|
|
12
13
|
import { addressSchema, weeklyHoursSchema } from './shared.js';
|
|
13
14
|
// ─── Center type ─────────────────────────────────────────────────────────────
|
|
14
15
|
/** The activity types a center offers (multi-select). */
|
|
@@ -61,6 +62,20 @@ export const createCenterBodySchema = z.object({
|
|
|
61
62
|
hours: weeklyHoursSchema,
|
|
62
63
|
/** Uploaded gallery image URLs. */
|
|
63
64
|
photos: z.array(z.string().trim().max(2000)).max(20).default([]),
|
|
65
|
+
/**
|
|
66
|
+
* A pasted Google Maps link. The server reads the coordinates out of it; the
|
|
67
|
+
* client never sends them.
|
|
68
|
+
*
|
|
69
|
+
* This is the pin attendance is measured against, and it is NOT the same as
|
|
70
|
+
* `address.coords`: that is an area centroid resolved from master data, which
|
|
71
|
+
* can be a whole district's midpoint and is useless for proximity.
|
|
72
|
+
*/
|
|
73
|
+
...mapLinkInputSchema.shape,
|
|
74
|
+
/**
|
|
75
|
+
* How close a member must be for a check-in to count, metres. Optional — the
|
|
76
|
+
* server applies its own default when a gym has not chosen one.
|
|
77
|
+
*/
|
|
78
|
+
checkInRadiusM: z.number().int().min(50).max(5000).optional(),
|
|
64
79
|
});
|
|
65
80
|
export const updateCenterBodySchema = createCenterBodySchema.partial();
|
|
66
81
|
// ─── Entity (API response) ───────────────────────────────────────────────────
|
|
@@ -84,6 +99,10 @@ export const centerSchema = z.object({
|
|
|
84
99
|
status: centerStatusSchema,
|
|
85
100
|
/** Denormalised active-member count for roster headers. */
|
|
86
101
|
memberCount: z.number().int().nonnegative(),
|
|
102
|
+
// The resolved pin. Absent until an owner pastes a Maps link, which is why
|
|
103
|
+
// attendance is unenforceable rather than failing for a gym without one.
|
|
104
|
+
...placePinSchema.shape,
|
|
105
|
+
checkInRadiusM: z.number().int().nullable().optional(),
|
|
87
106
|
createdAt: isoDateTimeSchema,
|
|
88
107
|
updatedAt: isoDateTimeSchema,
|
|
89
108
|
});
|
package/dist/center.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"center.js","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Center (branch)\n * ================================\n * A physical gym location belonging to an organisation. Carries the public gym\n * code members use to join, opening hours, equipment scope, and contact info.\n * v1 memberships are single-center, so a Center is the unit a member joins.\n *\n * @module gymmonk-schema/center\n */\n\nimport { z } from 'zod';\nimport {\n isoDateTimeSchema,\n objectIdSchema,\n optionalEmailSchema,\n optionalPhoneSchema,\n optionalTrimmedString,\n phoneSchema,\n} from './common.js';\nimport { addressSchema, weeklyHoursSchema } from './shared.js';\n\n// ─── Center type ─────────────────────────────────────────────────────────────\n\n/** The activity types a center offers (multi-select). */\nexport const CENTER_TYPES = [\n 'gym',\n 'crossfit',\n 'yoga',\n 'pilates',\n 'zumba',\n 'cardio',\n 'strength',\n 'mma',\n 'boxing',\n 'swimming',\n 'aerobics',\n 'personal-training',\n] as const;\nexport const centerTypeSchema = z.enum(CENTER_TYPES);\nexport type CenterType = z.infer<typeof centerTypeSchema>;\n\n// ─── Status ──────────────────────────────────────────────────────────────────\n\nexport const centerStatusSchema = z.enum(['active', 'inactive']);\nexport type CenterStatus = z.infer<typeof centerStatusSchema>;\n\n/**\n * The public gym code a member enters at onboarding to join, `GYM-YYYY-NNN`.\n * Server-generated and unique; never accepted in a create body.\n */\nexport const gymCodeSchema = z\n .string()\n .trim()\n .regex(/^GYM-\\d{4}-\\d{3,}$/, 'Invalid gym code');\nexport type GymCode = z.infer<typeof gymCodeSchema>;\n\n// ─── Create / update bodies ──────────────────────────────────────────────────\n\nexport const createCenterBodySchema = z.object({\n name: z.string().trim().min(1, 'Center name is required').max(160),\n description: optionalTrimmedString(2000),\n types: z.array(centerTypeSchema).min(1, 'Pick at least one type'),\n address: addressSchema,\n /** Usable floor area in square feet. */\n floorAreaSqft: z.number().int().min(0).max(1_000_000).optional(),\n /**\n * How many people the branch holds at once.\n *\n * The denominator behind \"how full are we\" — the centre detail screen shows\n * it beside the active-member count, and crowd-load has no meaning without\n * it. Optional: a gym that has never measured it should not be blocked from\n * saving the rest of its details.\n */\n capacity: z.number().int().min(1).max(100_000).optional(),\n primaryPhone: phoneSchema,\n secondaryPhone: optionalPhoneSchema,\n email: optionalEmailSchema,\n website: optionalTrimmedString(200),\n hours: weeklyHoursSchema,\n /** Uploaded gallery image URLs. */\n photos: z.array(z.string().trim().max(2000)).max(20).default([]),\n});\nexport type CreateCenterBody = z.infer<typeof createCenterBodySchema>;\n\nexport const updateCenterBodySchema = createCenterBodySchema.partial();\nexport type UpdateCenterBody = z.infer<typeof updateCenterBodySchema>;\n\n// ─── Entity (API response) ───────────────────────────────────────────────────\n\nexport const centerSchema = z.object({\n id: objectIdSchema,\n organisationId: objectIdSchema,\n /** Public join code, `GYM-YYYY-NNN`. */\n code: gymCodeSchema,\n name: z.string(),\n description: z.string().nullable(),\n types: z.array(centerTypeSchema),\n address: addressSchema,\n floorAreaSqft: z.number().int().nullable(),\n capacity: z.number().int().nullable(),\n primaryPhone: z.string(),\n secondaryPhone: z.string().nullable(),\n email: z.string().nullable(),\n website: z.string().nullable(),\n hours: weeklyHoursSchema,\n photos: z.array(z.string()),\n status: centerStatusSchema,\n /** Denormalised active-member count for roster headers. */\n memberCount: z.number().int().nonnegative(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Center = z.infer<typeof centerSchema>;\n\n// ─── Center stats (owner detail header) ──────────────────────────────────────\n\n/**\n * The three-up header on a centre's detail screen.\n *\n * Computed, never stored — `activeMembers` counts live memberships and\n * `monthlyRevenueInr` sums this month's successful payments, so the card can\n * never drift from the ledger the way a denormalised total would. `capacity`\n * is the one stored value, and is null until the owner fills it in.\n */\nexport const centerStatsSchema = z.object({\n capacity: z.number().int().nullable(),\n activeMembers: z.number().int().nonnegative(),\n monthlyRevenueInr: z.number().nonnegative(),\n});\nexport type CenterStats = z.infer<typeof centerStatsSchema>;\n\n// ─── Gym directory (member \"find a gym\") ─────────────────────────────────────\n\n/** A lightweight public directory result for the \"find a gym\" screen. */\nexport const nearbyGymSchema = z.object({\n id: objectIdSchema,\n code: gymCodeSchema,\n name: z.string(),\n /** The narrowest place name we hold (locality → district → state). */\n area: z.string(),\n district: z.string().nullable(),\n state: z.string().nullable(),\n /**\n * Kilometres from the coordinates the caller supplied.\n *\n * NULL, not 0, when it cannot be computed — the caller sent no position, or\n * the gym's address carries no coordinates. Zero is a real distance (\"you are\n * standing in it\") and using it as the unknown value put every un-geocoded\n * gym at the top of a nearest-first list.\n */\n distanceKm: z.number().nonnegative().nullable(),\n memberCount: z.number().int().nonnegative().optional(),\n});\nexport type NearbyGym = z.infer<typeof nearbyGymSchema>;\n\n/**\n * The gym directory has THREE ways in, and they differ in how far they reach.\n *\n * `code` exact public gym id — matches anywhere in the country, because\n * a member holding the id already knows which gym they mean and\n * may well be joining one in another state.\n * `q` name search — deliberately narrowed to ONE STATE. Gym names\n * repeat heavily (\"Fitness First\"), so a nationwide name search\n * returns a list nobody can pick from.\n * `districtId` browse — the gyms around them, nearest first when they share\n * their position.\n *\n * `stateId` / `districtId` are master-area ids (`area.ts`), matched against the\n * address `ancestry` array every center stores.\n */\nexport const GYM_SEARCH_MAX_RESULTS = 20;\n\nexport const gymSearchQuerySchema = z\n .object({\n /** Exact public gym id, e.g. `GYM-2025-001`. Unscoped by geography. */\n code: optionalTrimmedString(40),\n /** Name fragment. Requires `stateId`. */\n q: optionalTrimmedString(120),\n stateId: optionalTrimmedString(40),\n districtId: optionalTrimmedString(40),\n lat: z.coerce.number().min(-90).max(90).optional(),\n lng: z.coerce.number().min(-180).max(180).optional(),\n limit: z.coerce\n .number()\n .int()\n .min(1)\n .max(GYM_SEARCH_MAX_RESULTS)\n .default(GYM_SEARCH_MAX_RESULTS),\n })\n .refine((v) => !v.q || Boolean(v.stateId), {\n message: 'Pick a state to search gyms by name',\n path: ['stateId'],\n })\n .refine((v) => Boolean(v.code || v.q || v.stateId || v.districtId), {\n message: 'Enter a gym id, or pick where to look',\n path: ['stateId'],\n });\nexport type GymSearchQuery = z.infer<typeof gymSearchQuerySchema>;\n\n/**\n * The directory response.\n *\n * `truncated` says the district holds more gyms than were returned, which is\n * what the screen turns into \"showing the 20 nearest — enter a gym id if yours\n * is not here\" rather than letting the member assume their gym is absent.\n */\nexport const gymSearchResultSchema = z.object({\n items: z.array(nearbyGymSchema),\n total: z.number().int().nonnegative(),\n truncated: z.boolean(),\n});\nexport type GymSearchResult = z.infer<typeof gymSearchResultSchema>;\n\n// ─── Retained: nearby-gyms query ─────────────────────────────────────────────\n// Kept for the gym-search route the owner console's repo still serves. See the\n// note in `onboarding.ts`.\nexport const nearbyGymsQuerySchema = z.object({\n lat: z.coerce.number().min(-90).max(90).optional(),\n lng: z.coerce.number().min(-180).max(180).optional(),\n q: optionalTrimmedString(120),\n});\nexport type NearbyGymsQuery = z.infer<typeof nearbyGymsQuerySchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"center.js","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Center (branch)\n * ================================\n * A physical gym location belonging to an organisation. Carries the public gym\n * code members use to join, opening hours, equipment scope, and contact info.\n * v1 memberships are single-center, so a Center is the unit a member joins.\n *\n * @module gymmonk-schema/center\n */\n\nimport { z } from 'zod';\nimport {\n isoDateTimeSchema,\n objectIdSchema,\n optionalEmailSchema,\n optionalPhoneSchema,\n optionalTrimmedString,\n phoneSchema,\n} from './common.js';\nimport { mapLinkInputSchema, placePinSchema } from './location.js';\nimport { addressSchema, weeklyHoursSchema } from './shared.js';\n\n// ─── Center type ─────────────────────────────────────────────────────────────\n\n/** The activity types a center offers (multi-select). */\nexport const CENTER_TYPES = [\n 'gym',\n 'crossfit',\n 'yoga',\n 'pilates',\n 'zumba',\n 'cardio',\n 'strength',\n 'mma',\n 'boxing',\n 'swimming',\n 'aerobics',\n 'personal-training',\n] as const;\nexport const centerTypeSchema = z.enum(CENTER_TYPES);\nexport type CenterType = z.infer<typeof centerTypeSchema>;\n\n// ─── Status ──────────────────────────────────────────────────────────────────\n\nexport const centerStatusSchema = z.enum(['active', 'inactive']);\nexport type CenterStatus = z.infer<typeof centerStatusSchema>;\n\n/**\n * The public gym code a member enters at onboarding to join, `GYM-YYYY-NNN`.\n * Server-generated and unique; never accepted in a create body.\n */\nexport const gymCodeSchema = z\n .string()\n .trim()\n .regex(/^GYM-\\d{4}-\\d{3,}$/, 'Invalid gym code');\nexport type GymCode = z.infer<typeof gymCodeSchema>;\n\n// ─── Create / update bodies ──────────────────────────────────────────────────\n\nexport const createCenterBodySchema = z.object({\n name: z.string().trim().min(1, 'Center name is required').max(160),\n description: optionalTrimmedString(2000),\n types: z.array(centerTypeSchema).min(1, 'Pick at least one type'),\n address: addressSchema,\n /** Usable floor area in square feet. */\n floorAreaSqft: z.number().int().min(0).max(1_000_000).optional(),\n /**\n * How many people the branch holds at once.\n *\n * The denominator behind \"how full are we\" — the centre detail screen shows\n * it beside the active-member count, and crowd-load has no meaning without\n * it. Optional: a gym that has never measured it should not be blocked from\n * saving the rest of its details.\n */\n capacity: z.number().int().min(1).max(100_000).optional(),\n primaryPhone: phoneSchema,\n secondaryPhone: optionalPhoneSchema,\n email: optionalEmailSchema,\n website: optionalTrimmedString(200),\n hours: weeklyHoursSchema,\n /** Uploaded gallery image URLs. */\n photos: z.array(z.string().trim().max(2000)).max(20).default([]),\n /**\n * A pasted Google Maps link. The server reads the coordinates out of it; the\n * client never sends them.\n *\n * This is the pin attendance is measured against, and it is NOT the same as\n * `address.coords`: that is an area centroid resolved from master data, which\n * can be a whole district's midpoint and is useless for proximity.\n */\n ...mapLinkInputSchema.shape,\n /**\n * How close a member must be for a check-in to count, metres. Optional — the\n * server applies its own default when a gym has not chosen one.\n */\n checkInRadiusM: z.number().int().min(50).max(5000).optional(),\n});\nexport type CreateCenterBody = z.infer<typeof createCenterBodySchema>;\n\nexport const updateCenterBodySchema = createCenterBodySchema.partial();\nexport type UpdateCenterBody = z.infer<typeof updateCenterBodySchema>;\n\n// ─── Entity (API response) ───────────────────────────────────────────────────\n\nexport const centerSchema = z.object({\n id: objectIdSchema,\n organisationId: objectIdSchema,\n /** Public join code, `GYM-YYYY-NNN`. */\n code: gymCodeSchema,\n name: z.string(),\n description: z.string().nullable(),\n types: z.array(centerTypeSchema),\n address: addressSchema,\n floorAreaSqft: z.number().int().nullable(),\n capacity: z.number().int().nullable(),\n primaryPhone: z.string(),\n secondaryPhone: z.string().nullable(),\n email: z.string().nullable(),\n website: z.string().nullable(),\n hours: weeklyHoursSchema,\n photos: z.array(z.string()),\n status: centerStatusSchema,\n /** Denormalised active-member count for roster headers. */\n memberCount: z.number().int().nonnegative(),\n // The resolved pin. Absent until an owner pastes a Maps link, which is why\n // attendance is unenforceable rather than failing for a gym without one.\n ...placePinSchema.shape,\n checkInRadiusM: z.number().int().nullable().optional(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Center = z.infer<typeof centerSchema>;\n\n// ─── Center stats (owner detail header) ──────────────────────────────────────\n\n/**\n * The three-up header on a centre's detail screen.\n *\n * Computed, never stored — `activeMembers` counts live memberships and\n * `monthlyRevenueInr` sums this month's successful payments, so the card can\n * never drift from the ledger the way a denormalised total would. `capacity`\n * is the one stored value, and is null until the owner fills it in.\n */\nexport const centerStatsSchema = z.object({\n capacity: z.number().int().nullable(),\n activeMembers: z.number().int().nonnegative(),\n monthlyRevenueInr: z.number().nonnegative(),\n});\nexport type CenterStats = z.infer<typeof centerStatsSchema>;\n\n// ─── Gym directory (member \"find a gym\") ─────────────────────────────────────\n\n/** A lightweight public directory result for the \"find a gym\" screen. */\nexport const nearbyGymSchema = z.object({\n id: objectIdSchema,\n code: gymCodeSchema,\n name: z.string(),\n /** The narrowest place name we hold (locality → district → state). */\n area: z.string(),\n district: z.string().nullable(),\n state: z.string().nullable(),\n /**\n * Kilometres from the coordinates the caller supplied.\n *\n * NULL, not 0, when it cannot be computed — the caller sent no position, or\n * the gym's address carries no coordinates. Zero is a real distance (\"you are\n * standing in it\") and using it as the unknown value put every un-geocoded\n * gym at the top of a nearest-first list.\n */\n distanceKm: z.number().nonnegative().nullable(),\n memberCount: z.number().int().nonnegative().optional(),\n});\nexport type NearbyGym = z.infer<typeof nearbyGymSchema>;\n\n/**\n * The gym directory has THREE ways in, and they differ in how far they reach.\n *\n * `code` exact public gym id — matches anywhere in the country, because\n * a member holding the id already knows which gym they mean and\n * may well be joining one in another state.\n * `q` name search — deliberately narrowed to ONE STATE. Gym names\n * repeat heavily (\"Fitness First\"), so a nationwide name search\n * returns a list nobody can pick from.\n * `districtId` browse — the gyms around them, nearest first when they share\n * their position.\n *\n * `stateId` / `districtId` are master-area ids (`area.ts`), matched against the\n * address `ancestry` array every center stores.\n */\nexport const GYM_SEARCH_MAX_RESULTS = 20;\n\nexport const gymSearchQuerySchema = z\n .object({\n /** Exact public gym id, e.g. `GYM-2025-001`. Unscoped by geography. */\n code: optionalTrimmedString(40),\n /** Name fragment. Requires `stateId`. */\n q: optionalTrimmedString(120),\n stateId: optionalTrimmedString(40),\n districtId: optionalTrimmedString(40),\n lat: z.coerce.number().min(-90).max(90).optional(),\n lng: z.coerce.number().min(-180).max(180).optional(),\n limit: z.coerce\n .number()\n .int()\n .min(1)\n .max(GYM_SEARCH_MAX_RESULTS)\n .default(GYM_SEARCH_MAX_RESULTS),\n })\n .refine((v) => !v.q || Boolean(v.stateId), {\n message: 'Pick a state to search gyms by name',\n path: ['stateId'],\n })\n .refine((v) => Boolean(v.code || v.q || v.stateId || v.districtId), {\n message: 'Enter a gym id, or pick where to look',\n path: ['stateId'],\n });\nexport type GymSearchQuery = z.infer<typeof gymSearchQuerySchema>;\n\n/**\n * The directory response.\n *\n * `truncated` says the district holds more gyms than were returned, which is\n * what the screen turns into \"showing the 20 nearest — enter a gym id if yours\n * is not here\" rather than letting the member assume their gym is absent.\n */\nexport const gymSearchResultSchema = z.object({\n items: z.array(nearbyGymSchema),\n total: z.number().int().nonnegative(),\n truncated: z.boolean(),\n});\nexport type GymSearchResult = z.infer<typeof gymSearchResultSchema>;\n\n// ─── Retained: nearby-gyms query ─────────────────────────────────────────────\n// Kept for the gym-search route the owner console's repo still serves. See the\n// note in `onboarding.ts`.\nexport const nearbyGymsQuerySchema = z.object({\n lat: z.coerce.number().min(-90).max(90).optional(),\n lng: z.coerce.number().min(-180).max(180).optional(),\n q: optionalTrimmedString(120),\n});\nexport type NearbyGymsQuery = z.infer<typeof nearbyGymsQuerySchema>;\n"]}
|
|
@@ -30,6 +30,7 @@ export declare const gymPlanSelectionSchema: z.ZodObject<{
|
|
|
30
30
|
export type GymPlanSelection = z.infer<typeof gymPlanSelectionSchema>;
|
|
31
31
|
export declare const gymRegistrationBodySchema: z.ZodObject<{
|
|
32
32
|
organisation: z.ZodObject<{
|
|
33
|
+
mapLinkUrl: z.ZodOptional<z.ZodString>;
|
|
33
34
|
name: z.ZodString;
|
|
34
35
|
description: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
35
36
|
address: z.ZodObject<{
|
|
@@ -82,6 +83,8 @@ export declare const gymRegistrationBodySchema: z.ZodObject<{
|
|
|
82
83
|
numberOfCenters: z.ZodNumber;
|
|
83
84
|
}, z.core.$strip>;
|
|
84
85
|
center: z.ZodObject<{
|
|
86
|
+
checkInRadiusM: z.ZodOptional<z.ZodNumber>;
|
|
87
|
+
mapLinkUrl: z.ZodOptional<z.ZodString>;
|
|
85
88
|
name: z.ZodString;
|
|
86
89
|
description: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
87
90
|
types: z.ZodArray<z.ZodEnum<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gym-registration.d.ts","sourceRoot":"","sources":["../src/gym-registration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,sEAAsE;AACtE,eAAO,MAAM,sBAAsB;;;iBAGjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,yBAAyB
|
|
1
|
+
{"version":3,"file":"gym-registration.d.ts","sourceRoot":"","sources":["../src/gym-registration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,sEAAsE;AACtE,eAAO,MAAM,sBAAsB;;;iBAGjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAIpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './file.js';
|
|
|
22
22
|
export * from './gym-registration.js';
|
|
23
23
|
export * from './home.js';
|
|
24
24
|
export * from './insights.js';
|
|
25
|
+
export * from './location.js';
|
|
25
26
|
export * from './member.js';
|
|
26
27
|
export * from './member-profile.js';
|
|
27
28
|
export * from './membership.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAElC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAE5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,WAAW,CAAC;AAE1B,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AAExC,cAAc,eAAe,CAAC;AAE9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAE7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAElC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAE5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,WAAW,CAAC;AAE1B,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AAExC,cAAc,eAAe,CAAC;AAE9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAE7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,8 @@ export * from './file.js';
|
|
|
29
29
|
export * from './gym-registration.js';
|
|
30
30
|
export * from './home.js';
|
|
31
31
|
export * from './insights.js';
|
|
32
|
+
// ─── Where a place physically is (Maps link, pin, check-in position) ─────────
|
|
33
|
+
export * from './location.js';
|
|
32
34
|
// ─── People ──────────────────────────────────────────────────────────────────
|
|
33
35
|
export * from './member.js';
|
|
34
36
|
export * from './member-profile.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,6EAA6E;AAC7E,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,gFAAgF;AAChF,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,gFAAgF;AAChF,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC","sourcesContent":["/**\n * gymmonk-schema — Public API\n * ===========================\n * Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS).\n * Single source of truth (SSOT) for data shapes, TypeScript types and\n * validation, consumed by both gymmonk-backend and gymmonk-web-client.\n *\n * Every domain module is re-exported through this barrel. Import names are\n * globally unique across modules, so `export *` composes without collisions.\n */\n\n// ─── Owner desk-entry FORM validation (client-side working shapes) ───────────\nexport * from './add-person-form.js';\nexport * from './announcement.js';\n// ─── Location master data (the address picker's lookup API) ───────────────\nexport * from './area.js';\nexport * from './center.js';\n// ─── Check-in QR (poster payload shared by owner, member and backend) ────────\nexport * from './chat.js';\nexport * from './check-in.js';\n// ─── Foundation ──────────────────────────────────────────────────────────────\nexport * from './common.js';\nexport * from './equipment.js';\n// ─── Training ────────────────────────────────────────────────────────────────\nexport * from './exercise.js';\nexport * from './feedback.js';\n// ─── Uploads ─────────────────────────────────────────────────────────────────\nexport * from './file.js';\n// ─── \"List your gym\" — a member registers the business they run ──────────────\nexport * from './gym-registration.js';\nexport * from './home.js';\nexport * from './insights.js';\n// ─── People ──────────────────────────────────────────────────────────────────\nexport * from './member.js';\nexport * from './member-profile.js';\nexport * from './membership.js';\n// ─── Membership ──────────────────────────────────────────────────────────────\nexport * from './membership-plan.js';\nexport * from './membership-request.js';\n// ─── API result messages + error codes ───────────────────────────────────────\nexport * from './messages.js';\n// ─── Engagement & analytics ──────────────────────────────────────────────────\nexport * from './notification.js';\nexport * from './onboarding.js';\n// ─── Onboarding wizard FORM validation (client-side working shapes) ──────────\nexport * from './onboarding-form.js';\n// ─── Gym structure & onboarding ──────────────────────────────────────────────\nexport * from './organisation.js';\nexport * from './owner-profile.js';\n// ─── Platform console (people + gyms directories, role keys) ─────────────────\nexport * from './platform.js';\nexport * from './pricing.js';\n// ─── Member \"edit my profile\" FORM validation ────────────────────────────────\nexport * from './profile-edit-form.js';\nexport * from './session.js';\nexport * from './shared.js';\nexport * from './social.js';\nexport * from './staff.js';\nexport * from './subscription.js';\nexport * from './user.js';\nexport * from './workout-plan.js';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,6EAA6E;AAC7E,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,gFAAgF;AAChF,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,gFAAgF;AAChF,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC","sourcesContent":["/**\n * gymmonk-schema — Public API\n * ===========================\n * Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS).\n * Single source of truth (SSOT) for data shapes, TypeScript types and\n * validation, consumed by both gymmonk-backend and gymmonk-web-client.\n *\n * Every domain module is re-exported through this barrel. Import names are\n * globally unique across modules, so `export *` composes without collisions.\n */\n\n// ─── Owner desk-entry FORM validation (client-side working shapes) ───────────\nexport * from './add-person-form.js';\nexport * from './announcement.js';\n// ─── Location master data (the address picker's lookup API) ───────────────\nexport * from './area.js';\nexport * from './center.js';\n// ─── Check-in QR (poster payload shared by owner, member and backend) ────────\nexport * from './chat.js';\nexport * from './check-in.js';\n// ─── Foundation ──────────────────────────────────────────────────────────────\nexport * from './common.js';\nexport * from './equipment.js';\n// ─── Training ────────────────────────────────────────────────────────────────\nexport * from './exercise.js';\nexport * from './feedback.js';\n// ─── Uploads ─────────────────────────────────────────────────────────────────\nexport * from './file.js';\n// ─── \"List your gym\" — a member registers the business they run ──────────────\nexport * from './gym-registration.js';\nexport * from './home.js';\nexport * from './insights.js';\n// ─── Where a place physically is (Maps link, pin, check-in position) ─────────\nexport * from './location.js';\n// ─── People ──────────────────────────────────────────────────────────────────\nexport * from './member.js';\nexport * from './member-profile.js';\nexport * from './membership.js';\n// ─── Membership ──────────────────────────────────────────────────────────────\nexport * from './membership-plan.js';\nexport * from './membership-request.js';\n// ─── API result messages + error codes ───────────────────────────────────────\nexport * from './messages.js';\n// ─── Engagement & analytics ──────────────────────────────────────────────────\nexport * from './notification.js';\nexport * from './onboarding.js';\n// ─── Onboarding wizard FORM validation (client-side working shapes) ──────────\nexport * from './onboarding-form.js';\n// ─── Gym structure & onboarding ──────────────────────────────────────────────\nexport * from './organisation.js';\nexport * from './owner-profile.js';\n// ─── Platform console (people + gyms directories, role keys) ─────────────────\nexport * from './platform.js';\nexport * from './pricing.js';\n// ─── Member \"edit my profile\" FORM validation ────────────────────────────────\nexport * from './profile-edit-form.js';\nexport * from './session.js';\nexport * from './shared.js';\nexport * from './social.js';\nexport * from './staff.js';\nexport * from './subscription.js';\nexport * from './user.js';\nexport * from './workout-plan.js';\n"]}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gymmonk-schema — Where a place physically is
|
|
3
|
+
* ============================================
|
|
4
|
+
* The pasted Google Maps link an owner gives for their headquarters and each
|
|
5
|
+
* centre, and the coordinates read out of it.
|
|
6
|
+
*
|
|
7
|
+
* VALIDATION LIVES HERE, RESOLUTION DOES NOT. Deciding whether a string is a
|
|
8
|
+
* Google Maps link is pure and belongs in one place for both repos. FOLLOWING
|
|
9
|
+
* that link — a server-side fetch of a short URL — stays in gymmonk-backend
|
|
10
|
+
* (`shared/lib/resolve-map-link`), because it is I/O and because it is the
|
|
11
|
+
* SSRF-shaped half that must never run in a browser.
|
|
12
|
+
*
|
|
13
|
+
* WHY THE CLIENT'S COORDINATES ARE NEVER TRUSTED
|
|
14
|
+
* ----------------------------------------------
|
|
15
|
+
* The registration form previews the pin as you paste, so the browser already
|
|
16
|
+
* holds `{lat, lng}` and could simply send them. It must not, and no body schema
|
|
17
|
+
* here accepts them. That pin decides whether a member's check-in counts, so
|
|
18
|
+
* taking it from the client would let anyone replaying the request place a gym
|
|
19
|
+
* on top of a member's sofa and have the geofence agree. The URL is evidence;
|
|
20
|
+
* the coordinates are a conclusion, and the server draws it.
|
|
21
|
+
*
|
|
22
|
+
* @module gymmonk-schema/location
|
|
23
|
+
*/
|
|
24
|
+
import { z } from 'zod';
|
|
25
|
+
/** True for a link we are willing to treat as a Google map. */
|
|
26
|
+
export declare function isGoogleMapsUrl(raw: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The pasted link, validated only as far as "plausibly a Google map".
|
|
29
|
+
*
|
|
30
|
+
* Deliberately permissive about FORM and strict about HOST. The backend's
|
|
31
|
+
* resolver performs an outbound request for short links, so the host allowlist
|
|
32
|
+
* is a security control; the rest of the URL can be any of the half-dozen shapes
|
|
33
|
+
* Maps emits, and rejecting one because it did not match a pattern would be
|
|
34
|
+
* telling an owner their own share link is wrong.
|
|
35
|
+
*
|
|
36
|
+
* An empty string is accepted and means "remove the link", which is why this
|
|
37
|
+
* cannot simply be `.url()`.
|
|
38
|
+
*/
|
|
39
|
+
export declare const mapLinkUrlSchema: z.ZodString;
|
|
40
|
+
/** Mix into any create/update body that describes a physical place. */
|
|
41
|
+
export declare const mapLinkInputSchema: z.ZodObject<{
|
|
42
|
+
mapLinkUrl: z.ZodOptional<z.ZodString>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
export type MapLinkInput = z.infer<typeof mapLinkInputSchema>;
|
|
45
|
+
/**
|
|
46
|
+
* What the server stored for a resolved link.
|
|
47
|
+
*
|
|
48
|
+
* `lat`/`lng` are absent when the link carried no readable pin. The link is
|
|
49
|
+
* still kept: "Open in Maps" works from the url alone, and an owner who pasted
|
|
50
|
+
* something should not find the field empty next time they open the form. It
|
|
51
|
+
* simply means proximity stays unenforceable for that gym, which is the truth.
|
|
52
|
+
*/
|
|
53
|
+
export declare const mapLinkSchema: z.ZodObject<{
|
|
54
|
+
url: z.ZodString;
|
|
55
|
+
lat: z.ZodOptional<z.ZodNumber>;
|
|
56
|
+
lng: z.ZodOptional<z.ZodNumber>;
|
|
57
|
+
}, z.core.$strip>;
|
|
58
|
+
export type MapLink = z.infer<typeof mapLinkSchema>;
|
|
59
|
+
/**
|
|
60
|
+
* A GeoJSON point, in MongoDB's order.
|
|
61
|
+
*
|
|
62
|
+
* `[lng, lat]`, NOT `[lat, lng]`. Reversing them is the classic 2dsphere bug and
|
|
63
|
+
* it does not throw — it silently puts every Indian gym in the Indian Ocean and
|
|
64
|
+
* the geofence then refuses every check-in.
|
|
65
|
+
*/
|
|
66
|
+
export declare const geoPointSchema: z.ZodObject<{
|
|
67
|
+
type: z.ZodLiteral<"Point">;
|
|
68
|
+
coordinates: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
69
|
+
}, z.core.$strip>;
|
|
70
|
+
export type GeoPoint = z.infer<typeof geoPointSchema>;
|
|
71
|
+
/**
|
|
72
|
+
* What a place exposes about its position on the wire.
|
|
73
|
+
*
|
|
74
|
+
* Both optional: no gym is required to have a pin, and one that does not simply
|
|
75
|
+
* cannot have attendance verified against it.
|
|
76
|
+
*/
|
|
77
|
+
export declare const placePinSchema: z.ZodObject<{
|
|
78
|
+
mapLink: z.ZodOptional<z.ZodObject<{
|
|
79
|
+
url: z.ZodString;
|
|
80
|
+
lat: z.ZodOptional<z.ZodNumber>;
|
|
81
|
+
lng: z.ZodOptional<z.ZodNumber>;
|
|
82
|
+
}, z.core.$strip>>;
|
|
83
|
+
location: z.ZodOptional<z.ZodObject<{
|
|
84
|
+
type: z.ZodLiteral<"Point">;
|
|
85
|
+
coordinates: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
86
|
+
}, z.core.$strip>>;
|
|
87
|
+
}, z.core.$strip>;
|
|
88
|
+
export type PlacePin = z.infer<typeof placePinSchema>;
|
|
89
|
+
/**
|
|
90
|
+
* The position a check-in request carries: where the scanning device thought it
|
|
91
|
+
* was, and how sure it was.
|
|
92
|
+
*
|
|
93
|
+
* WHY ON THE REQUEST, AND NOT READ FROM THE USER'S STORED FIX
|
|
94
|
+
* The app records a member's location trail separately, and reading the latest
|
|
95
|
+
* entry here would be wrong: that write happens BESIDE the scan, not before it,
|
|
96
|
+
* so at the moment check-in reaches the server the trail may hold a fix from an
|
|
97
|
+
* hour ago, or nothing. Attendance would then be verified against where the
|
|
98
|
+
* member happened to be earlier, which is evidence of nothing.
|
|
99
|
+
*
|
|
100
|
+
* ALL THREE ARE OPTIONAL and stay that way. A browser without geolocation, a
|
|
101
|
+
* refused permission, a fix that never arrived indoors — each is ordinary, and
|
|
102
|
+
* the server treats a missing fix as unverifiable rather than as a failure.
|
|
103
|
+
*/
|
|
104
|
+
export declare const checkInGeoInputSchema: z.ZodObject<{
|
|
105
|
+
lat: z.ZodOptional<z.ZodNumber>;
|
|
106
|
+
lng: z.ZodOptional<z.ZodNumber>;
|
|
107
|
+
accuracyM: z.ZodOptional<z.ZodNumber>;
|
|
108
|
+
}, z.core.$strip>;
|
|
109
|
+
export type CheckInGeoInput = z.infer<typeof checkInGeoInputSchema>;
|
|
110
|
+
//# sourceMappingURL=location.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"location.d.ts","sourceRoot":"","sources":["../src/location.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,+DAA+D;AAC/D,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAWpD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,aAMzB,CAAC;AAEL,uEAAuE;AACvE,eAAO,MAAM,kBAAkB;;iBAE7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;iBAIxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;;;iBAGzB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;iBAGzB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAItD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB;;;;iBAUhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
package/dist/location.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gymmonk-schema — Where a place physically is
|
|
3
|
+
* ============================================
|
|
4
|
+
* The pasted Google Maps link an owner gives for their headquarters and each
|
|
5
|
+
* centre, and the coordinates read out of it.
|
|
6
|
+
*
|
|
7
|
+
* VALIDATION LIVES HERE, RESOLUTION DOES NOT. Deciding whether a string is a
|
|
8
|
+
* Google Maps link is pure and belongs in one place for both repos. FOLLOWING
|
|
9
|
+
* that link — a server-side fetch of a short URL — stays in gymmonk-backend
|
|
10
|
+
* (`shared/lib/resolve-map-link`), because it is I/O and because it is the
|
|
11
|
+
* SSRF-shaped half that must never run in a browser.
|
|
12
|
+
*
|
|
13
|
+
* WHY THE CLIENT'S COORDINATES ARE NEVER TRUSTED
|
|
14
|
+
* ----------------------------------------------
|
|
15
|
+
* The registration form previews the pin as you paste, so the browser already
|
|
16
|
+
* holds `{lat, lng}` and could simply send them. It must not, and no body schema
|
|
17
|
+
* here accepts them. That pin decides whether a member's check-in counts, so
|
|
18
|
+
* taking it from the client would let anyone replaying the request place a gym
|
|
19
|
+
* on top of a member's sofa and have the geofence agree. The URL is evidence;
|
|
20
|
+
* the coordinates are a conclusion, and the server draws it.
|
|
21
|
+
*
|
|
22
|
+
* @module gymmonk-schema/location
|
|
23
|
+
*/
|
|
24
|
+
import { z } from 'zod';
|
|
25
|
+
/** Hosts whose links are treated as Google maps. */
|
|
26
|
+
const MAP_HOSTS = new Set([
|
|
27
|
+
'maps.google.com',
|
|
28
|
+
'www.google.com',
|
|
29
|
+
'google.com',
|
|
30
|
+
'maps.app.goo.gl',
|
|
31
|
+
'goo.gl',
|
|
32
|
+
'g.co',
|
|
33
|
+
]);
|
|
34
|
+
/** True for a link we are willing to treat as a Google map. */
|
|
35
|
+
export function isGoogleMapsUrl(raw) {
|
|
36
|
+
try {
|
|
37
|
+
const url = new URL(raw.trim());
|
|
38
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
39
|
+
return false;
|
|
40
|
+
const host = url.hostname.toLowerCase();
|
|
41
|
+
// `google.co.in`, `google.co.uk` and the rest of the country domains all
|
|
42
|
+
// serve the same /maps paths.
|
|
43
|
+
return MAP_HOSTS.has(host) || /(^|\.)google\.[a-z.]{2,6}$/.test(host);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The pasted link, validated only as far as "plausibly a Google map".
|
|
51
|
+
*
|
|
52
|
+
* Deliberately permissive about FORM and strict about HOST. The backend's
|
|
53
|
+
* resolver performs an outbound request for short links, so the host allowlist
|
|
54
|
+
* is a security control; the rest of the URL can be any of the half-dozen shapes
|
|
55
|
+
* Maps emits, and rejecting one because it did not match a pattern would be
|
|
56
|
+
* telling an owner their own share link is wrong.
|
|
57
|
+
*
|
|
58
|
+
* An empty string is accepted and means "remove the link", which is why this
|
|
59
|
+
* cannot simply be `.url()`.
|
|
60
|
+
*/
|
|
61
|
+
export const mapLinkUrlSchema = z
|
|
62
|
+
.string()
|
|
63
|
+
.trim()
|
|
64
|
+
.max(2000)
|
|
65
|
+
.refine((v) => v === '' || isGoogleMapsUrl(v), {
|
|
66
|
+
message: 'Paste a Google Maps link (from Share → Copy link)',
|
|
67
|
+
});
|
|
68
|
+
/** Mix into any create/update body that describes a physical place. */
|
|
69
|
+
export const mapLinkInputSchema = z.object({
|
|
70
|
+
mapLinkUrl: mapLinkUrlSchema.optional(),
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* What the server stored for a resolved link.
|
|
74
|
+
*
|
|
75
|
+
* `lat`/`lng` are absent when the link carried no readable pin. The link is
|
|
76
|
+
* still kept: "Open in Maps" works from the url alone, and an owner who pasted
|
|
77
|
+
* something should not find the field empty next time they open the form. It
|
|
78
|
+
* simply means proximity stays unenforceable for that gym, which is the truth.
|
|
79
|
+
*/
|
|
80
|
+
export const mapLinkSchema = z.object({
|
|
81
|
+
url: z.string(),
|
|
82
|
+
lat: z.number().optional(),
|
|
83
|
+
lng: z.number().optional(),
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* A GeoJSON point, in MongoDB's order.
|
|
87
|
+
*
|
|
88
|
+
* `[lng, lat]`, NOT `[lat, lng]`. Reversing them is the classic 2dsphere bug and
|
|
89
|
+
* it does not throw — it silently puts every Indian gym in the Indian Ocean and
|
|
90
|
+
* the geofence then refuses every check-in.
|
|
91
|
+
*/
|
|
92
|
+
export const geoPointSchema = z.object({
|
|
93
|
+
type: z.literal('Point'),
|
|
94
|
+
coordinates: z.tuple([z.number().min(-180).max(180), z.number().min(-90).max(90)]),
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* What a place exposes about its position on the wire.
|
|
98
|
+
*
|
|
99
|
+
* Both optional: no gym is required to have a pin, and one that does not simply
|
|
100
|
+
* cannot have attendance verified against it.
|
|
101
|
+
*/
|
|
102
|
+
export const placePinSchema = z.object({
|
|
103
|
+
mapLink: mapLinkSchema.optional(),
|
|
104
|
+
location: geoPointSchema.optional(),
|
|
105
|
+
});
|
|
106
|
+
// ─── Check-in position ───────────────────────────────────────────────────────
|
|
107
|
+
/**
|
|
108
|
+
* The position a check-in request carries: where the scanning device thought it
|
|
109
|
+
* was, and how sure it was.
|
|
110
|
+
*
|
|
111
|
+
* WHY ON THE REQUEST, AND NOT READ FROM THE USER'S STORED FIX
|
|
112
|
+
* The app records a member's location trail separately, and reading the latest
|
|
113
|
+
* entry here would be wrong: that write happens BESIDE the scan, not before it,
|
|
114
|
+
* so at the moment check-in reaches the server the trail may hold a fix from an
|
|
115
|
+
* hour ago, or nothing. Attendance would then be verified against where the
|
|
116
|
+
* member happened to be earlier, which is evidence of nothing.
|
|
117
|
+
*
|
|
118
|
+
* ALL THREE ARE OPTIONAL and stay that way. A browser without geolocation, a
|
|
119
|
+
* refused permission, a fix that never arrived indoors — each is ordinary, and
|
|
120
|
+
* the server treats a missing fix as unverifiable rather than as a failure.
|
|
121
|
+
*/
|
|
122
|
+
export const checkInGeoInputSchema = z.object({
|
|
123
|
+
lat: z.number().min(-90).max(90).optional(),
|
|
124
|
+
lng: z.number().min(-180).max(180).optional(),
|
|
125
|
+
/**
|
|
126
|
+
* The browser's own accuracy radius, metres. Bounded rather than trusted: it
|
|
127
|
+
* widens the allowed radius, so an unbounded value would be a way to switch
|
|
128
|
+
* the check off by claiming a very uncertain fix. The server caps the
|
|
129
|
+
* allowance again on its own side.
|
|
130
|
+
*/
|
|
131
|
+
accuracyM: z.number().min(0).max(100_000).optional(),
|
|
132
|
+
});
|
|
133
|
+
//# sourceMappingURL=location.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"location.js","sourceRoot":"","sources":["../src/location.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Where a place physically is\n * ============================================\n * The pasted Google Maps link an owner gives for their headquarters and each\n * centre, and the coordinates read out of it.\n *\n * VALIDATION LIVES HERE, RESOLUTION DOES NOT. Deciding whether a string is a\n * Google Maps link is pure and belongs in one place for both repos. FOLLOWING\n * that link — a server-side fetch of a short URL — stays in gymmonk-backend\n * (`shared/lib/resolve-map-link`), because it is I/O and because it is the\n * SSRF-shaped half that must never run in a browser.\n *\n * WHY THE CLIENT'S COORDINATES ARE NEVER TRUSTED\n * ----------------------------------------------\n * The registration form previews the pin as you paste, so the browser already\n * holds `{lat, lng}` and could simply send them. It must not, and no body schema\n * here accepts them. That pin decides whether a member's check-in counts, so\n * taking it from the client would let anyone replaying the request place a gym\n * on top of a member's sofa and have the geofence agree. The URL is evidence;\n * the coordinates are a conclusion, and the server draws it.\n *\n * @module gymmonk-schema/location\n */\n\nimport { z } from 'zod';\n\n/** Hosts whose links are treated as Google maps. */\nconst MAP_HOSTS = new Set([\n 'maps.google.com',\n 'www.google.com',\n 'google.com',\n 'maps.app.goo.gl',\n 'goo.gl',\n 'g.co',\n]);\n\n/** True for a link we are willing to treat as a Google map. */\nexport function isGoogleMapsUrl(raw: string): boolean {\n try {\n const url = new URL(raw.trim());\n if (url.protocol !== 'https:' && url.protocol !== 'http:') return false;\n const host = url.hostname.toLowerCase();\n // `google.co.in`, `google.co.uk` and the rest of the country domains all\n // serve the same /maps paths.\n return MAP_HOSTS.has(host) || /(^|\\.)google\\.[a-z.]{2,6}$/.test(host);\n } catch {\n return false;\n }\n}\n\n/**\n * The pasted link, validated only as far as \"plausibly a Google map\".\n *\n * Deliberately permissive about FORM and strict about HOST. The backend's\n * resolver performs an outbound request for short links, so the host allowlist\n * is a security control; the rest of the URL can be any of the half-dozen shapes\n * Maps emits, and rejecting one because it did not match a pattern would be\n * telling an owner their own share link is wrong.\n *\n * An empty string is accepted and means \"remove the link\", which is why this\n * cannot simply be `.url()`.\n */\nexport const mapLinkUrlSchema = z\n .string()\n .trim()\n .max(2000)\n .refine((v) => v === '' || isGoogleMapsUrl(v), {\n message: 'Paste a Google Maps link (from Share → Copy link)',\n });\n\n/** Mix into any create/update body that describes a physical place. */\nexport const mapLinkInputSchema = z.object({\n mapLinkUrl: mapLinkUrlSchema.optional(),\n});\nexport type MapLinkInput = z.infer<typeof mapLinkInputSchema>;\n\n/**\n * What the server stored for a resolved link.\n *\n * `lat`/`lng` are absent when the link carried no readable pin. The link is\n * still kept: \"Open in Maps\" works from the url alone, and an owner who pasted\n * something should not find the field empty next time they open the form. It\n * simply means proximity stays unenforceable for that gym, which is the truth.\n */\nexport const mapLinkSchema = z.object({\n url: z.string(),\n lat: z.number().optional(),\n lng: z.number().optional(),\n});\nexport type MapLink = z.infer<typeof mapLinkSchema>;\n\n/**\n * A GeoJSON point, in MongoDB's order.\n *\n * `[lng, lat]`, NOT `[lat, lng]`. Reversing them is the classic 2dsphere bug and\n * it does not throw — it silently puts every Indian gym in the Indian Ocean and\n * the geofence then refuses every check-in.\n */\nexport const geoPointSchema = z.object({\n type: z.literal('Point'),\n coordinates: z.tuple([z.number().min(-180).max(180), z.number().min(-90).max(90)]),\n});\nexport type GeoPoint = z.infer<typeof geoPointSchema>;\n\n/**\n * What a place exposes about its position on the wire.\n *\n * Both optional: no gym is required to have a pin, and one that does not simply\n * cannot have attendance verified against it.\n */\nexport const placePinSchema = z.object({\n mapLink: mapLinkSchema.optional(),\n location: geoPointSchema.optional(),\n});\nexport type PlacePin = z.infer<typeof placePinSchema>;\n\n// ─── Check-in position ───────────────────────────────────────────────────────\n\n/**\n * The position a check-in request carries: where the scanning device thought it\n * was, and how sure it was.\n *\n * WHY ON THE REQUEST, AND NOT READ FROM THE USER'S STORED FIX\n * The app records a member's location trail separately, and reading the latest\n * entry here would be wrong: that write happens BESIDE the scan, not before it,\n * so at the moment check-in reaches the server the trail may hold a fix from an\n * hour ago, or nothing. Attendance would then be verified against where the\n * member happened to be earlier, which is evidence of nothing.\n *\n * ALL THREE ARE OPTIONAL and stay that way. A browser without geolocation, a\n * refused permission, a fix that never arrived indoors — each is ordinary, and\n * the server treats a missing fix as unverifiable rather than as a failure.\n */\nexport const checkInGeoInputSchema = z.object({\n lat: z.number().min(-90).max(90).optional(),\n lng: z.number().min(-180).max(180).optional(),\n /**\n * The browser's own accuracy radius, metres. Bounded rather than trusted: it\n * widens the allowed radius, so an unbounded value would be a way to switch\n * the check off by claiming a very uncertain fix. The server caps the\n * allowance again on its own side.\n */\n accuracyM: z.number().min(0).max(100_000).optional(),\n});\nexport type CheckInGeoInput = z.infer<typeof checkInGeoInputSchema>;\n"]}
|
package/dist/onboarding.d.ts
CHANGED
|
@@ -250,6 +250,7 @@ export declare const ownerOnboardingBodySchema: z.ZodObject<{
|
|
|
250
250
|
avatarUrl: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
251
251
|
}, z.core.$strip>;
|
|
252
252
|
organisation: z.ZodObject<{
|
|
253
|
+
mapLinkUrl: z.ZodOptional<z.ZodString>;
|
|
253
254
|
name: z.ZodString;
|
|
254
255
|
description: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
255
256
|
address: z.ZodObject<{
|
|
@@ -302,6 +303,8 @@ export declare const ownerOnboardingBodySchema: z.ZodObject<{
|
|
|
302
303
|
numberOfCenters: z.ZodNumber;
|
|
303
304
|
}, z.core.$strip>;
|
|
304
305
|
center: z.ZodObject<{
|
|
306
|
+
checkInRadiusM: z.ZodOptional<z.ZodNumber>;
|
|
307
|
+
mapLinkUrl: z.ZodOptional<z.ZodString>;
|
|
305
308
|
name: z.ZodString;
|
|
306
309
|
description: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
307
310
|
types: z.ZodArray<z.ZodEnum<{
|
package/dist/onboarding.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../src/onboarding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgBxB,eAAO,MAAM,oBAAoB;;;EAA8B,CAAC;AAChE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCnC,CAAC;AACL,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,wCAAoC,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;iBAQnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,sFAAsF;AACtF,eAAO,MAAM,8BAA8B;;;;;;;;;;iBAS/B,CAAC;AACb,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAEtF;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;iBAUjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAatE,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAa5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,mEAAmE;AACnE,eAAO,MAAM,6BAA6B;;;iBAGxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,yBAAyB
|
|
1
|
+
{"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../src/onboarding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgBxB,eAAO,MAAM,oBAAoB;;;EAA8B,CAAC;AAChE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCnC,CAAC;AACL,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,wCAAoC,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;iBAQnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,sFAAsF;AACtF,eAAO,MAAM,8BAA8B;;;;;;;;;;iBAS/B,CAAC;AACb,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAEtF;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;iBAUjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAatE,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAa5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,mEAAmE;AACnE,eAAO,MAAM,6BAA6B;;;iBAGxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAKpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|
package/dist/organisation.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export type OrganisationApprovalStatus = z.infer<typeof organisationApprovalStat
|
|
|
34
34
|
/** GSTIN — 15 chars; optional (many small gyms are unregistered). */
|
|
35
35
|
export declare const gstNumberSchema: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
36
36
|
export declare const createOrganisationBodySchema: z.ZodObject<{
|
|
37
|
+
mapLinkUrl: z.ZodOptional<z.ZodString>;
|
|
37
38
|
name: z.ZodString;
|
|
38
39
|
description: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
39
40
|
address: z.ZodObject<{
|
|
@@ -87,6 +88,7 @@ export declare const createOrganisationBodySchema: z.ZodObject<{
|
|
|
87
88
|
}, z.core.$strip>;
|
|
88
89
|
export type CreateOrganisationBody = z.infer<typeof createOrganisationBodySchema>;
|
|
89
90
|
export declare const updateOrganisationBodySchema: z.ZodObject<{
|
|
91
|
+
mapLinkUrl: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
90
92
|
name: z.ZodOptional<z.ZodString>;
|
|
91
93
|
description: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
|
|
92
94
|
address: z.ZodOptional<z.ZodObject<{
|
|
@@ -140,6 +142,17 @@ export declare const updateOrganisationBodySchema: z.ZodObject<{
|
|
|
140
142
|
}, z.core.$strip>;
|
|
141
143
|
export type UpdateOrganisationBody = z.infer<typeof updateOrganisationBodySchema>;
|
|
142
144
|
export declare const organisationSchema: z.ZodObject<{
|
|
145
|
+
createdAt: z.ZodISODateTime;
|
|
146
|
+
updatedAt: z.ZodISODateTime;
|
|
147
|
+
mapLink: z.ZodOptional<z.ZodObject<{
|
|
148
|
+
url: z.ZodString;
|
|
149
|
+
lat: z.ZodOptional<z.ZodNumber>;
|
|
150
|
+
lng: z.ZodOptional<z.ZodNumber>;
|
|
151
|
+
}, z.core.$strip>>;
|
|
152
|
+
location: z.ZodOptional<z.ZodObject<{
|
|
153
|
+
type: z.ZodLiteral<"Point">;
|
|
154
|
+
coordinates: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
155
|
+
}, z.core.$strip>>;
|
|
143
156
|
id: z.ZodString;
|
|
144
157
|
ownerId: z.ZodString;
|
|
145
158
|
name: z.ZodString;
|
|
@@ -203,8 +216,6 @@ export declare const organisationSchema: z.ZodObject<{
|
|
|
203
216
|
}>;
|
|
204
217
|
approvedAt: z.ZodNullable<z.ZodISODateTime>;
|
|
205
218
|
reviewNote: z.ZodNullable<z.ZodString>;
|
|
206
|
-
createdAt: z.ZodISODateTime;
|
|
207
|
-
updatedAt: z.ZodISODateTime;
|
|
208
219
|
}, z.core.$strip>;
|
|
209
220
|
export type Organisation = z.infer<typeof organisationSchema>;
|
|
210
221
|
/**
|
|
@@ -212,6 +223,17 @@ export type Organisation = z.infer<typeof organisationSchema>;
|
|
|
212
223
|
* and estate detail a reviewer needs to decide without opening each one.
|
|
213
224
|
*/
|
|
214
225
|
export declare const platformOrganisationSchema: z.ZodObject<{
|
|
226
|
+
createdAt: z.ZodISODateTime;
|
|
227
|
+
updatedAt: z.ZodISODateTime;
|
|
228
|
+
mapLink: z.ZodOptional<z.ZodObject<{
|
|
229
|
+
url: z.ZodString;
|
|
230
|
+
lat: z.ZodOptional<z.ZodNumber>;
|
|
231
|
+
lng: z.ZodOptional<z.ZodNumber>;
|
|
232
|
+
}, z.core.$strip>>;
|
|
233
|
+
location: z.ZodOptional<z.ZodObject<{
|
|
234
|
+
type: z.ZodLiteral<"Point">;
|
|
235
|
+
coordinates: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
236
|
+
}, z.core.$strip>>;
|
|
215
237
|
id: z.ZodString;
|
|
216
238
|
ownerId: z.ZodString;
|
|
217
239
|
name: z.ZodString;
|
|
@@ -275,8 +297,6 @@ export declare const platformOrganisationSchema: z.ZodObject<{
|
|
|
275
297
|
}>;
|
|
276
298
|
approvedAt: z.ZodNullable<z.ZodISODateTime>;
|
|
277
299
|
reviewNote: z.ZodNullable<z.ZodString>;
|
|
278
|
-
createdAt: z.ZodISODateTime;
|
|
279
|
-
updatedAt: z.ZodISODateTime;
|
|
280
300
|
ownerName: z.ZodString;
|
|
281
301
|
ownerEmail: z.ZodString;
|
|
282
302
|
ownerPhone: z.ZodNullable<z.ZodString>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organisation.d.ts","sourceRoot":"","sources":["../src/organisation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"organisation.d.ts","sourceRoot":"","sources":["../src/organisation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB,eAAO,MAAM,wBAAwB;;;EAAiC,CAAC;AACvE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAI1E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gCAAgC;;;;EAA8C,CAAC;AAC5F,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAC;AAI1F,qEAAqE;AACrE,eAAO,MAAM,eAAe,6CAO3B,CAAC;AAIF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwBvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAyC,CAAC;AACnF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAIlF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwB7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAYrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;iBAU1C,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,2EAA2E;AAC3E,eAAO,MAAM,4BAA4B;;iBAEvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;kBASvB,CAAC;AACd,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
|
package/dist/organisation.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import { deletedFilterSchema, isoDateTimeSchema, objectIdSchema, optionalEmailSchema, optionalTrimmedString, paginationSchema, } from './common.js';
|
|
11
|
+
import { mapLinkInputSchema, placePinSchema } from './location.js';
|
|
11
12
|
import { addressSchema, socialLinksSchema } from './shared.js';
|
|
12
13
|
// ─── Status ──────────────────────────────────────────────────────────────────
|
|
13
14
|
export const organisationStatusSchema = z.enum(['active', 'inactive']);
|
|
@@ -53,6 +54,11 @@ export const createOrganisationBodySchema = z.object({
|
|
|
53
54
|
socials: socialLinksSchema,
|
|
54
55
|
/** Owner-declared number of centers (for planning; actual centers are docs). */
|
|
55
56
|
numberOfCenters: z.number().int().min(1).max(500),
|
|
57
|
+
/**
|
|
58
|
+
* A pasted Google Maps link for the HEADQUARTERS. The server reads the
|
|
59
|
+
* coordinates out of it; the client never sends them.
|
|
60
|
+
*/
|
|
61
|
+
...mapLinkInputSchema.shape,
|
|
56
62
|
});
|
|
57
63
|
export const updateOrganisationBodySchema = createOrganisationBodySchema.partial();
|
|
58
64
|
// ─── Entity (API response) ───────────────────────────────────────────────────
|
|
@@ -75,6 +81,9 @@ export const organisationSchema = z.object({
|
|
|
75
81
|
approvedAt: isoDateTimeSchema.nullable(),
|
|
76
82
|
/** The reviewer's note, shown to the owner on a rejection. */
|
|
77
83
|
reviewNote: z.string().nullable(),
|
|
84
|
+
// The headquarters pin. Informational: attendance is measured against a
|
|
85
|
+
// CENTRE's pin, never the organisation's.
|
|
86
|
+
...placePinSchema.shape,
|
|
78
87
|
createdAt: isoDateTimeSchema,
|
|
79
88
|
updatedAt: isoDateTimeSchema,
|
|
80
89
|
});
|
package/dist/organisation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organisation.js","sourceRoot":"","sources":["../src/organisation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Organisation\n * =============================\n * The gym business an owner runs. One owner → one organisation → many centers\n * (branches). Created at owner onboarding; edited from the owner console.\n *\n * @module gymmonk-schema/organisation\n */\n\nimport { z } from 'zod';\nimport {\n deletedFilterSchema,\n isoDateTimeSchema,\n objectIdSchema,\n optionalEmailSchema,\n optionalTrimmedString,\n paginationSchema,\n} from './common.js';\nimport { addressSchema, socialLinksSchema } from './shared.js';\n\n// ─── Status ──────────────────────────────────────────────────────────────────\n\nexport const organisationStatusSchema = z.enum(['active', 'inactive']);\nexport type OrganisationStatus = z.infer<typeof organisationStatusSchema>;\n\n// ─── Platform approval ───────────────────────────────────────────────────────\n\n/**\n * Whether the PLATFORM has vetted this gym business.\n *\n * Anyone signed in can register a gym — it is an ordinary action a member\n * takes, not a separate account type. What they cannot do is start operating\n * one: an unapproved organisation is invisible in the member-facing gym\n * directory and its owner holds the owner ROLE but none of its powers (see\n * `casl-ability#grantOwner`). A superadmin or platform manager decides.\n *\n * Distinct from `status`, which is the OWNER's own switch for a business that\n * has been approved and later paused. Approval is the platform's word; status\n * is the owner's.\n */\nexport const organisationApprovalStatusSchema = z.enum(['pending', 'approved', 'rejected']);\nexport type OrganisationApprovalStatus = z.infer<typeof organisationApprovalStatusSchema>;\n\n// ─── Registration identifiers ────────────────────────────────────────────────\n\n/** GSTIN — 15 chars; optional (many small gyms are unregistered). */\nexport const gstNumberSchema = z.preprocess(\n (v) => (typeof v === 'string' && v.trim() === '' ? undefined : v),\n z\n .string()\n .trim()\n .regex(/^[0-9A-Z]{15}$/, 'Enter a valid 15-character GSTIN')\n .optional(),\n);\n\n// ─── Create / update bodies ──────────────────────────────────────────────────\n\nexport const createOrganisationBodySchema = z.object({\n name: z.string().trim().min(1, 'Organisation name is required').max(160),\n description: optionalTrimmedString(2000),\n address: addressSchema,\n gstNumber: gstNumberSchema,\n /** Company Identification Number (CIN) or other registration id. */\n registrationNumber: optionalTrimmedString(40),\n /**\n * The BUSINESS's contact address, alongside GST/CIN/website.\n *\n * Not a centre's `email`: a branch's address is where a member writes about\n * that branch, while this is where the business is reached — and a\n * multi-centre gym has one of the first per branch and exactly one of these.\n */\n email: optionalEmailSchema,\n website: optionalTrimmedString(200),\n socials: socialLinksSchema,\n /** Owner-declared number of centers (for planning; actual centers are docs). */\n numberOfCenters: z.number().int().min(1).max(500),\n});\nexport type CreateOrganisationBody = z.infer<typeof createOrganisationBodySchema>;\n\nexport const updateOrganisationBodySchema = createOrganisationBodySchema.partial();\nexport type UpdateOrganisationBody = z.infer<typeof updateOrganisationBodySchema>;\n\n// ─── Entity (API response) ───────────────────────────────────────────────────\n\nexport const organisationSchema = z.object({\n id: objectIdSchema,\n ownerId: objectIdSchema,\n name: z.string(),\n description: z.string().nullable(),\n address: addressSchema,\n gstNumber: z.string().nullable(),\n registrationNumber: z.string().nullable(),\n email: z.string().nullable(),\n website: z.string().nullable(),\n socials: socialLinksSchema,\n numberOfCenters: z.number().int(),\n status: organisationStatusSchema,\n /** The platform's decision — see `organisationApprovalStatusSchema`. */\n approvalStatus: organisationApprovalStatusSchema,\n /** When the platform decided. Null while pending. */\n approvedAt: isoDateTimeSchema.nullable(),\n /** The reviewer's note, shown to the owner on a rejection. */\n reviewNote: z.string().nullable(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Organisation = z.infer<typeof organisationSchema>;\n\n// ─── Platform review (superadmin / platform manager) ─────────────────────────\n\n/**\n * A row in the platform's gym-approvals queue: the organisation plus the owner\n * and estate detail a reviewer needs to decide without opening each one.\n */\nexport const platformOrganisationSchema = organisationSchema.extend({\n ownerName: z.string(),\n ownerEmail: z.string(),\n ownerPhone: z.string().nullable(),\n /** Centers actually created under it (`numberOfCenters` is owner-declared). */\n centerCount: z.number().int().nonnegative(),\n /**\n * Soft-deleted by a superadmin. On the wire so the directory can mark the row\n * and offer Restore — a delete the console cannot show is a delete the console\n * cannot undo, which would make a reversible action irreversible in practice.\n */\n isDeleted: z.boolean(),\n});\nexport type PlatformOrganisation = z.infer<typeof platformOrganisationSchema>;\n\nexport const platformOrganisationQuerySchema = paginationSchema.extend({\n approvalStatus: organisationApprovalStatusSchema.optional(),\n /** Free-text over organisation name / owner email. */\n q: optionalTrimmedString(120),\n /**\n * Soft-delete visibility. `only` is the superadmin's restore queue. Mirrors\n * `platformUserQuerySchema.deleted` so both directories behave identically —\n * see `deletedFilterSchema` for why this is not a boolean.\n */\n deleted: deletedFilterSchema.default('exclude'),\n});\nexport type PlatformOrganisationQuery = z.infer<typeof platformOrganisationQuerySchema>;\n\n/** Approve or reject. The note is surfaced to the owner on a rejection. */\nexport const decideOrganisationBodySchema = z.object({\n note: optionalTrimmedString(300),\n});\nexport type DecideOrganisationBody = z.infer<typeof decideOrganisationBodySchema>;\n\n/**\n * What the OWNER's own surfaces need: where their gym registration stands, and\n * why if it was turned down. `null` when they have not registered a gym.\n */\nexport const myGymRegistrationSchema = z\n .object({\n organisationId: objectIdSchema,\n organisationName: z.string(),\n approvalStatus: organisationApprovalStatusSchema,\n approvedAt: isoDateTimeSchema.nullable(),\n reviewNote: z.string().nullable(),\n submittedAt: isoDateTimeSchema,\n })\n .nullable();\nexport type MyGymRegistration = z.infer<typeof myGymRegistrationSchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"organisation.js","sourceRoot":"","sources":["../src/organisation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Organisation\n * =============================\n * The gym business an owner runs. One owner → one organisation → many centers\n * (branches). Created at owner onboarding; edited from the owner console.\n *\n * @module gymmonk-schema/organisation\n */\n\nimport { z } from 'zod';\nimport {\n deletedFilterSchema,\n isoDateTimeSchema,\n objectIdSchema,\n optionalEmailSchema,\n optionalTrimmedString,\n paginationSchema,\n} from './common.js';\nimport { mapLinkInputSchema, placePinSchema } from './location.js';\nimport { addressSchema, socialLinksSchema } from './shared.js';\n\n// ─── Status ──────────────────────────────────────────────────────────────────\n\nexport const organisationStatusSchema = z.enum(['active', 'inactive']);\nexport type OrganisationStatus = z.infer<typeof organisationStatusSchema>;\n\n// ─── Platform approval ───────────────────────────────────────────────────────\n\n/**\n * Whether the PLATFORM has vetted this gym business.\n *\n * Anyone signed in can register a gym — it is an ordinary action a member\n * takes, not a separate account type. What they cannot do is start operating\n * one: an unapproved organisation is invisible in the member-facing gym\n * directory and its owner holds the owner ROLE but none of its powers (see\n * `casl-ability#grantOwner`). A superadmin or platform manager decides.\n *\n * Distinct from `status`, which is the OWNER's own switch for a business that\n * has been approved and later paused. Approval is the platform's word; status\n * is the owner's.\n */\nexport const organisationApprovalStatusSchema = z.enum(['pending', 'approved', 'rejected']);\nexport type OrganisationApprovalStatus = z.infer<typeof organisationApprovalStatusSchema>;\n\n// ─── Registration identifiers ────────────────────────────────────────────────\n\n/** GSTIN — 15 chars; optional (many small gyms are unregistered). */\nexport const gstNumberSchema = z.preprocess(\n (v) => (typeof v === 'string' && v.trim() === '' ? undefined : v),\n z\n .string()\n .trim()\n .regex(/^[0-9A-Z]{15}$/, 'Enter a valid 15-character GSTIN')\n .optional(),\n);\n\n// ─── Create / update bodies ──────────────────────────────────────────────────\n\nexport const createOrganisationBodySchema = z.object({\n name: z.string().trim().min(1, 'Organisation name is required').max(160),\n description: optionalTrimmedString(2000),\n address: addressSchema,\n gstNumber: gstNumberSchema,\n /** Company Identification Number (CIN) or other registration id. */\n registrationNumber: optionalTrimmedString(40),\n /**\n * The BUSINESS's contact address, alongside GST/CIN/website.\n *\n * Not a centre's `email`: a branch's address is where a member writes about\n * that branch, while this is where the business is reached — and a\n * multi-centre gym has one of the first per branch and exactly one of these.\n */\n email: optionalEmailSchema,\n website: optionalTrimmedString(200),\n socials: socialLinksSchema,\n /** Owner-declared number of centers (for planning; actual centers are docs). */\n numberOfCenters: z.number().int().min(1).max(500),\n /**\n * A pasted Google Maps link for the HEADQUARTERS. The server reads the\n * coordinates out of it; the client never sends them.\n */\n ...mapLinkInputSchema.shape,\n});\nexport type CreateOrganisationBody = z.infer<typeof createOrganisationBodySchema>;\n\nexport const updateOrganisationBodySchema = createOrganisationBodySchema.partial();\nexport type UpdateOrganisationBody = z.infer<typeof updateOrganisationBodySchema>;\n\n// ─── Entity (API response) ───────────────────────────────────────────────────\n\nexport const organisationSchema = z.object({\n id: objectIdSchema,\n ownerId: objectIdSchema,\n name: z.string(),\n description: z.string().nullable(),\n address: addressSchema,\n gstNumber: z.string().nullable(),\n registrationNumber: z.string().nullable(),\n email: z.string().nullable(),\n website: z.string().nullable(),\n socials: socialLinksSchema,\n numberOfCenters: z.number().int(),\n status: organisationStatusSchema,\n /** The platform's decision — see `organisationApprovalStatusSchema`. */\n approvalStatus: organisationApprovalStatusSchema,\n /** When the platform decided. Null while pending. */\n approvedAt: isoDateTimeSchema.nullable(),\n /** The reviewer's note, shown to the owner on a rejection. */\n reviewNote: z.string().nullable(),\n // The headquarters pin. Informational: attendance is measured against a\n // CENTRE's pin, never the organisation's.\n ...placePinSchema.shape,\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Organisation = z.infer<typeof organisationSchema>;\n\n// ─── Platform review (superadmin / platform manager) ─────────────────────────\n\n/**\n * A row in the platform's gym-approvals queue: the organisation plus the owner\n * and estate detail a reviewer needs to decide without opening each one.\n */\nexport const platformOrganisationSchema = organisationSchema.extend({\n ownerName: z.string(),\n ownerEmail: z.string(),\n ownerPhone: z.string().nullable(),\n /** Centers actually created under it (`numberOfCenters` is owner-declared). */\n centerCount: z.number().int().nonnegative(),\n /**\n * Soft-deleted by a superadmin. On the wire so the directory can mark the row\n * and offer Restore — a delete the console cannot show is a delete the console\n * cannot undo, which would make a reversible action irreversible in practice.\n */\n isDeleted: z.boolean(),\n});\nexport type PlatformOrganisation = z.infer<typeof platformOrganisationSchema>;\n\nexport const platformOrganisationQuerySchema = paginationSchema.extend({\n approvalStatus: organisationApprovalStatusSchema.optional(),\n /** Free-text over organisation name / owner email. */\n q: optionalTrimmedString(120),\n /**\n * Soft-delete visibility. `only` is the superadmin's restore queue. Mirrors\n * `platformUserQuerySchema.deleted` so both directories behave identically —\n * see `deletedFilterSchema` for why this is not a boolean.\n */\n deleted: deletedFilterSchema.default('exclude'),\n});\nexport type PlatformOrganisationQuery = z.infer<typeof platformOrganisationQuerySchema>;\n\n/** Approve or reject. The note is surfaced to the owner on a rejection. */\nexport const decideOrganisationBodySchema = z.object({\n note: optionalTrimmedString(300),\n});\nexport type DecideOrganisationBody = z.infer<typeof decideOrganisationBodySchema>;\n\n/**\n * What the OWNER's own surfaces need: where their gym registration stands, and\n * why if it was turned down. `null` when they have not registered a gym.\n */\nexport const myGymRegistrationSchema = z\n .object({\n organisationId: objectIdSchema,\n organisationName: z.string(),\n approvalStatus: organisationApprovalStatusSchema,\n approvedAt: isoDateTimeSchema.nullable(),\n reviewNote: z.string().nullable(),\n submittedAt: isoDateTimeSchema,\n })\n .nullable();\nexport type MyGymRegistration = z.infer<typeof myGymRegistrationSchema>;\n"]}
|
package/dist/session.d.ts
CHANGED
|
@@ -90,6 +90,9 @@ export type WorkoutSession = z.infer<typeof workoutSessionSchema>;
|
|
|
90
90
|
*/
|
|
91
91
|
export declare const checkInBodySchema: z.ZodObject<{
|
|
92
92
|
code: z.ZodString;
|
|
93
|
+
lat: z.ZodOptional<z.ZodNumber>;
|
|
94
|
+
lng: z.ZodOptional<z.ZodNumber>;
|
|
95
|
+
accuracyM: z.ZodOptional<z.ZodNumber>;
|
|
93
96
|
}, z.core.$strip>;
|
|
94
97
|
export type CheckInBody = z.infer<typeof checkInBodySchema>;
|
|
95
98
|
/**
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB,eAAO,MAAM,mBAAmB;;;;EAA0C,CAAC;AAC3E,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAIhE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,sBAAsB;;;;iBAMjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,+CAA+C;AAC/C,eAAO,MAAM,cAAc,EAAE,gBAI5B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAO/F;AAID,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAY/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB;;;;;iBAOQ,CAAC;AACvC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;iBAE7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,0BAA0B;;;iBAGrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB;;;;;;iBAEpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E,eAAO,MAAM,sBAAsB;;;;EAAwC,CAAC;AAC5E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,qBAAqB;;;;;;;;;iBAKhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,qBAAqB;;;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,sEAAsE;AACtE,eAAO,MAAM,wBAAwB;;;;;;;;;;;iBAOnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
package/dist/session.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
import { checkInCodeSchema } from './check-in.js';
|
|
12
12
|
import { isoDateSchema, isoDateTimeSchema, objectIdSchema, optionalTrimmedString, paginationSchema, } from './common.js';
|
|
13
|
+
import { checkInGeoInputSchema } from './location.js';
|
|
13
14
|
import { weekdaySchema } from './shared.js';
|
|
14
15
|
// ─── Live session widget status ──────────────────────────────────────────────
|
|
15
16
|
export const sessionStatusSchema = z.enum(['idle', 'active', 'completed']);
|
|
@@ -88,9 +89,14 @@ export const workoutSessionSchema = z.object({
|
|
|
88
89
|
* Staff marking someone present by hand is a different, authorised path —
|
|
89
90
|
* `markAttendanceBodySchema` below.
|
|
90
91
|
*/
|
|
91
|
-
export const checkInBodySchema = z
|
|
92
|
+
export const checkInBodySchema = z
|
|
93
|
+
.object({
|
|
92
94
|
code: checkInCodeSchema,
|
|
93
|
-
})
|
|
95
|
+
})
|
|
96
|
+
// Where the scanning device thought it was. All three optional — see
|
|
97
|
+
// `checkInGeoInputSchema`. The server measures this against the gym's pin and
|
|
98
|
+
// treats a missing fix as unverifiable rather than as a failure.
|
|
99
|
+
.extend(checkInGeoInputSchema.shape);
|
|
94
100
|
/**
|
|
95
101
|
* Check out — the same poster, scanned again. Verified against the session's
|
|
96
102
|
* OWN center, so a member cannot close a session by scanning a different gym.
|
package/dist/session.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Workout sessions, check-in & attendance\n * ========================================================\n * A member checks in to start a live session, marks exercises done, and checks\n * out. Sessions roll up into attendance records the owner can review, and can\n * also be recorded directly by an owner (manual attendance marking).\n *\n * @module gymmonk-schema/session\n */\n\nimport { z } from 'zod';\nimport { checkInCodeSchema } from './check-in.js';\nimport {\n isoDateSchema,\n isoDateTimeSchema,\n objectIdSchema,\n optionalTrimmedString,\n paginationSchema,\n} from './common.js';\nimport { weekdaySchema } from './shared.js';\n\n// ─── Live session widget status ──────────────────────────────────────────────\n\nexport const sessionStatusSchema = z.enum(['idle', 'active', 'completed']);\nexport type SessionStatus = z.infer<typeof sessionStatusSchema>;\n\n// ─── The set stopwatch ───────────────────────────────────────────────────────\n\n/**\n * The member's own stopwatch, which is NOT the session clock.\n *\n * Home shows two running times and they measure different things. The session\n * clock is \"how long have you been in the gym\" — it runs from `checkInAt`,\n * cannot be paused, and is derived rather than stored. This one is \"how long\n * has this set taken\", and the member starts, pauses and resets it freely.\n *\n * Stored as ACCUMULATED + STARTED-AT rather than as a running total, because a\n * total would need the server to tick. Elapsed is\n * `accumulatedSec + (running ? now - startedAt : 0)`, so a paused stopwatch is\n * a plain number, a running one survives a refresh or a dead battery, and no\n * job has to write to the database once a second.\n *\n * `startedAt` is null exactly when `running` is false; the pair is a small\n * state machine, and the backend is the only writer.\n */\nexport const sessionStopwatchSchema = z.object({\n running: z.boolean(),\n /** Seconds banked by previous runs, excluding any run in progress. */\n accumulatedSec: z.number().int().nonnegative(),\n /** When the current run began. Null while paused. */\n startedAt: isoDateTimeSchema.nullable(),\n});\nexport type SessionStopwatch = z.infer<typeof sessionStopwatchSchema>;\n\n/** A stopwatch that has never been started. */\nexport const IDLE_STOPWATCH: SessionStopwatch = {\n running: false,\n accumulatedSec: 0,\n startedAt: null,\n};\n\n/**\n * Resolve a stopwatch to whole seconds elapsed.\n *\n * Shared rather than reimplemented on each side: the backend needs it to bank\n * time on pause, and the client needs it to render every tick. Two copies of\n * this arithmetic would drift the moment one of them forgot the running run.\n */\nexport function stopwatchElapsedSec(stopwatch: SessionStopwatch, now: Date = new Date()): number {\n if (!stopwatch.running || !stopwatch.startedAt) return stopwatch.accumulatedSec;\n const started = Date.parse(stopwatch.startedAt);\n if (Number.isNaN(started)) return stopwatch.accumulatedSec;\n // Floored at the banked value: a client clock behind the server's would\n // otherwise make the number run backwards.\n return stopwatch.accumulatedSec + Math.max(0, Math.floor((now.getTime() - started) / 1000));\n}\n\n// ─── Session entity ──────────────────────────────────────────────────────────\n\nexport const workoutSessionSchema = z.object({\n id: objectIdSchema,\n userId: objectIdSchema,\n centerId: objectIdSchema,\n status: sessionStatusSchema,\n checkInAt: isoDateTimeSchema,\n checkOutAt: isoDateTimeSchema.nullable(),\n /** The plan weekday being trained, if the member is on a plan. */\n planWeekday: weekdaySchema.nullable(),\n completedExerciseIds: z.array(objectIdSchema),\n totalExercises: z.number().int().nonnegative(),\n stopwatch: sessionStopwatchSchema,\n});\nexport type WorkoutSession = z.infer<typeof workoutSessionSchema>;\n\n// ─── Session mutations ───────────────────────────────────────────────────────\n\n/**\n * Check in by scanning the gym's QR poster.\n *\n * `code` is REQUIRED and is the whole point: the center is resolved FROM the\n * scanned code, never from a client-supplied `centerId`. An earlier version of\n * this body took an optional `centerId`, which meant any authenticated member\n * could mark themselves present from their sofa. Presence has to be proven by\n * being close enough to the poster to photograph it.\n *\n * Staff marking someone present by hand is a different, authorised path —\n * `markAttendanceBodySchema` below.\n */\nexport const checkInBodySchema = z.object({\n code: checkInCodeSchema,\n});\nexport type CheckInBody = z.infer<typeof checkInBodySchema>;\n\n/**\n * Check out — the same poster, scanned again. Verified against the session's\n * OWN center, so a member cannot close a session by scanning a different gym.\n */\nexport const checkOutBodySchema = z.object({\n code: checkInCodeSchema,\n});\nexport type CheckOutBody = z.infer<typeof checkOutBodySchema>;\n\nexport const markExerciseDoneBodySchema = z.object({\n exerciseId: objectIdSchema,\n done: z.boolean(),\n});\nexport type MarkExerciseDoneBody = z.infer<typeof markExerciseDoneBodySchema>;\n\n/**\n * Drive the set stopwatch.\n *\n * An ACTION, not a state patch. The client says what the member pressed and the\n * server computes the resulting `{ running, accumulatedSec, startedAt }` — which\n * keeps the banking arithmetic in one place and means a client cannot post an\n * arbitrary elapsed time to inflate a workout.\n */\nexport const stopwatchActionBodySchema = z.object({\n action: z.enum(['start', 'pause', 'reset']),\n});\nexport type StopwatchActionBody = z.infer<typeof stopwatchActionBodySchema>;\n\n// ─── Attendance ──────────────────────────────────────────────────────────────\n\nexport const attendanceStatusSchema = z.enum(['present', 'absent', 'rest']);\nexport type AttendanceStatus = z.infer<typeof attendanceStatusSchema>;\n\nexport const attendanceEntrySchema = z.object({\n date: isoDateSchema,\n checkInAt: isoDateTimeSchema.nullable(),\n checkOutAt: isoDateTimeSchema.nullable(),\n status: attendanceStatusSchema,\n});\nexport type AttendanceEntry = z.infer<typeof attendanceEntrySchema>;\n\nexport const attendanceQuerySchema = paginationSchema.extend({\n userId: objectIdSchema.optional(),\n from: isoDateSchema.optional(),\n to: isoDateSchema.optional(),\n});\nexport type AttendanceQuery = z.infer<typeof attendanceQuerySchema>;\n\n/** Owner marking attendance for a member or staff member manually. */\nexport const markAttendanceBodySchema = z.object({\n userId: objectIdSchema,\n date: isoDateSchema.optional(),\n status: attendanceStatusSchema.default('present'),\n checkInAt: isoDateTimeSchema.optional(),\n checkOutAt: isoDateTimeSchema.optional(),\n note: optionalTrimmedString(160),\n});\nexport type MarkAttendanceBody = z.infer<typeof markAttendanceBodySchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Workout sessions, check-in & attendance\n * ========================================================\n * A member checks in to start a live session, marks exercises done, and checks\n * out. Sessions roll up into attendance records the owner can review, and can\n * also be recorded directly by an owner (manual attendance marking).\n *\n * @module gymmonk-schema/session\n */\n\nimport { z } from 'zod';\nimport { checkInCodeSchema } from './check-in.js';\nimport {\n isoDateSchema,\n isoDateTimeSchema,\n objectIdSchema,\n optionalTrimmedString,\n paginationSchema,\n} from './common.js';\nimport { checkInGeoInputSchema } from './location.js';\nimport { weekdaySchema } from './shared.js';\n\n// ─── Live session widget status ──────────────────────────────────────────────\n\nexport const sessionStatusSchema = z.enum(['idle', 'active', 'completed']);\nexport type SessionStatus = z.infer<typeof sessionStatusSchema>;\n\n// ─── The set stopwatch ───────────────────────────────────────────────────────\n\n/**\n * The member's own stopwatch, which is NOT the session clock.\n *\n * Home shows two running times and they measure different things. The session\n * clock is \"how long have you been in the gym\" — it runs from `checkInAt`,\n * cannot be paused, and is derived rather than stored. This one is \"how long\n * has this set taken\", and the member starts, pauses and resets it freely.\n *\n * Stored as ACCUMULATED + STARTED-AT rather than as a running total, because a\n * total would need the server to tick. Elapsed is\n * `accumulatedSec + (running ? now - startedAt : 0)`, so a paused stopwatch is\n * a plain number, a running one survives a refresh or a dead battery, and no\n * job has to write to the database once a second.\n *\n * `startedAt` is null exactly when `running` is false; the pair is a small\n * state machine, and the backend is the only writer.\n */\nexport const sessionStopwatchSchema = z.object({\n running: z.boolean(),\n /** Seconds banked by previous runs, excluding any run in progress. */\n accumulatedSec: z.number().int().nonnegative(),\n /** When the current run began. Null while paused. */\n startedAt: isoDateTimeSchema.nullable(),\n});\nexport type SessionStopwatch = z.infer<typeof sessionStopwatchSchema>;\n\n/** A stopwatch that has never been started. */\nexport const IDLE_STOPWATCH: SessionStopwatch = {\n running: false,\n accumulatedSec: 0,\n startedAt: null,\n};\n\n/**\n * Resolve a stopwatch to whole seconds elapsed.\n *\n * Shared rather than reimplemented on each side: the backend needs it to bank\n * time on pause, and the client needs it to render every tick. Two copies of\n * this arithmetic would drift the moment one of them forgot the running run.\n */\nexport function stopwatchElapsedSec(stopwatch: SessionStopwatch, now: Date = new Date()): number {\n if (!stopwatch.running || !stopwatch.startedAt) return stopwatch.accumulatedSec;\n const started = Date.parse(stopwatch.startedAt);\n if (Number.isNaN(started)) return stopwatch.accumulatedSec;\n // Floored at the banked value: a client clock behind the server's would\n // otherwise make the number run backwards.\n return stopwatch.accumulatedSec + Math.max(0, Math.floor((now.getTime() - started) / 1000));\n}\n\n// ─── Session entity ──────────────────────────────────────────────────────────\n\nexport const workoutSessionSchema = z.object({\n id: objectIdSchema,\n userId: objectIdSchema,\n centerId: objectIdSchema,\n status: sessionStatusSchema,\n checkInAt: isoDateTimeSchema,\n checkOutAt: isoDateTimeSchema.nullable(),\n /** The plan weekday being trained, if the member is on a plan. */\n planWeekday: weekdaySchema.nullable(),\n completedExerciseIds: z.array(objectIdSchema),\n totalExercises: z.number().int().nonnegative(),\n stopwatch: sessionStopwatchSchema,\n});\nexport type WorkoutSession = z.infer<typeof workoutSessionSchema>;\n\n// ─── Session mutations ───────────────────────────────────────────────────────\n\n/**\n * Check in by scanning the gym's QR poster.\n *\n * `code` is REQUIRED and is the whole point: the center is resolved FROM the\n * scanned code, never from a client-supplied `centerId`. An earlier version of\n * this body took an optional `centerId`, which meant any authenticated member\n * could mark themselves present from their sofa. Presence has to be proven by\n * being close enough to the poster to photograph it.\n *\n * Staff marking someone present by hand is a different, authorised path —\n * `markAttendanceBodySchema` below.\n */\nexport const checkInBodySchema = z\n .object({\n code: checkInCodeSchema,\n })\n // Where the scanning device thought it was. All three optional — see\n // `checkInGeoInputSchema`. The server measures this against the gym's pin and\n // treats a missing fix as unverifiable rather than as a failure.\n .extend(checkInGeoInputSchema.shape);\nexport type CheckInBody = z.infer<typeof checkInBodySchema>;\n\n/**\n * Check out — the same poster, scanned again. Verified against the session's\n * OWN center, so a member cannot close a session by scanning a different gym.\n */\nexport const checkOutBodySchema = z.object({\n code: checkInCodeSchema,\n});\nexport type CheckOutBody = z.infer<typeof checkOutBodySchema>;\n\nexport const markExerciseDoneBodySchema = z.object({\n exerciseId: objectIdSchema,\n done: z.boolean(),\n});\nexport type MarkExerciseDoneBody = z.infer<typeof markExerciseDoneBodySchema>;\n\n/**\n * Drive the set stopwatch.\n *\n * An ACTION, not a state patch. The client says what the member pressed and the\n * server computes the resulting `{ running, accumulatedSec, startedAt }` — which\n * keeps the banking arithmetic in one place and means a client cannot post an\n * arbitrary elapsed time to inflate a workout.\n */\nexport const stopwatchActionBodySchema = z.object({\n action: z.enum(['start', 'pause', 'reset']),\n});\nexport type StopwatchActionBody = z.infer<typeof stopwatchActionBodySchema>;\n\n// ─── Attendance ──────────────────────────────────────────────────────────────\n\nexport const attendanceStatusSchema = z.enum(['present', 'absent', 'rest']);\nexport type AttendanceStatus = z.infer<typeof attendanceStatusSchema>;\n\nexport const attendanceEntrySchema = z.object({\n date: isoDateSchema,\n checkInAt: isoDateTimeSchema.nullable(),\n checkOutAt: isoDateTimeSchema.nullable(),\n status: attendanceStatusSchema,\n});\nexport type AttendanceEntry = z.infer<typeof attendanceEntrySchema>;\n\nexport const attendanceQuerySchema = paginationSchema.extend({\n userId: objectIdSchema.optional(),\n from: isoDateSchema.optional(),\n to: isoDateSchema.optional(),\n});\nexport type AttendanceQuery = z.infer<typeof attendanceQuerySchema>;\n\n/** Owner marking attendance for a member or staff member manually. */\nexport const markAttendanceBodySchema = z.object({\n userId: objectIdSchema,\n date: isoDateSchema.optional(),\n status: attendanceStatusSchema.default('present'),\n checkInAt: isoDateTimeSchema.optional(),\n checkOutAt: isoDateTimeSchema.optional(),\n note: optionalTrimmedString(160),\n});\nexport type MarkAttendanceBody = z.infer<typeof markAttendanceBodySchema>;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gymmonk-schema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS) — single source of truth (SSOT) consumed by gymmonk-backend and gymmonk-web-client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|