@company-semantics/contracts 6.6.0 → 7.1.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/package.json +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +125 -4
- package/src/generated/openapi-routes.ts +1 -0
- package/src/identity/__tests__/avatar.test.ts +12 -12
- package/src/identity/avatar.ts +17 -17
- package/src/identity/people-org-chart.ts +1 -1
- package/src/identity/schemas.ts +1 -1
- package/src/identity/types.ts +29 -9
- package/src/org/index.ts +4 -0
- package/src/org/schemas.ts +27 -1
package/package.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
|
|
2
|
-
export const SPEC_HASH = '
|
|
3
|
-
export const SPEC_HASH_FULL = '
|
|
2
|
+
export const SPEC_HASH = '937aecbfe6e6' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = '937aecbfe6e6533edabb1b83d66be83f63100e519ecf9e4531af3e8a627e122a' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -1972,6 +1972,30 @@ export interface paths {
|
|
|
1972
1972
|
patch?: never;
|
|
1973
1973
|
trace?: never;
|
|
1974
1974
|
};
|
|
1975
|
+
"/api/users/{userId}/avatar": {
|
|
1976
|
+
parameters: {
|
|
1977
|
+
query?: never;
|
|
1978
|
+
header?: never;
|
|
1979
|
+
path?: never;
|
|
1980
|
+
cookie?: never;
|
|
1981
|
+
};
|
|
1982
|
+
get?: never;
|
|
1983
|
+
/**
|
|
1984
|
+
* Upload (replace) a user's avatar photo from an image file
|
|
1985
|
+
* @description Accepts a multipart image upload (PNG/JPEG/WebP/HEIC) up to 5 MB. The caller must be the target user or hold org.manage_users. HEIC/HEIF is converted to JPEG and the bytes are re-hosted via the blob store. Returns the resolved avatar.
|
|
1986
|
+
*/
|
|
1987
|
+
put: operations["uploadUserAvatar"];
|
|
1988
|
+
post?: never;
|
|
1989
|
+
/**
|
|
1990
|
+
* Clear a user's uploaded avatar (reverts to Slack photo or initials)
|
|
1991
|
+
* @description Clears an in-app uploaded avatar only (source=upload); Slack-sourced avatars are left intact. The caller must be the target user or hold org.manage_users. Returns the resolved avatar after reverting.
|
|
1992
|
+
*/
|
|
1993
|
+
delete: operations["deleteUserAvatar"];
|
|
1994
|
+
options?: never;
|
|
1995
|
+
head?: never;
|
|
1996
|
+
patch?: never;
|
|
1997
|
+
trace?: never;
|
|
1998
|
+
};
|
|
1975
1999
|
"/api/drive/files": {
|
|
1976
2000
|
parameters: {
|
|
1977
2001
|
query?: never;
|
|
@@ -2782,7 +2806,7 @@ export interface components {
|
|
|
2782
2806
|
slackUserId: string | null;
|
|
2783
2807
|
avatar: {
|
|
2784
2808
|
/** @enum {string} */
|
|
2785
|
-
source: "
|
|
2809
|
+
source: "photo" | "initials";
|
|
2786
2810
|
url?: string;
|
|
2787
2811
|
initials: string;
|
|
2788
2812
|
};
|
|
@@ -4067,7 +4091,7 @@ export interface components {
|
|
|
4067
4091
|
userId: string;
|
|
4068
4092
|
fullName: string;
|
|
4069
4093
|
jobTitle: string | null;
|
|
4070
|
-
|
|
4094
|
+
avatarUrl: string | null;
|
|
4071
4095
|
authorities: {
|
|
4072
4096
|
/** @enum {string} */
|
|
4073
4097
|
mechanism: "structural" | "delegated" | "rbac";
|
|
@@ -4078,6 +4102,7 @@ export interface components {
|
|
|
4078
4102
|
/** Format: uuid */
|
|
4079
4103
|
derivedFromUnitId: string;
|
|
4080
4104
|
derivedFromUnitName?: string;
|
|
4105
|
+
derivedFromRoot?: boolean;
|
|
4081
4106
|
derivedFromUserId?: string | null;
|
|
4082
4107
|
explanation: string;
|
|
4083
4108
|
derivationMetadata?: {
|
|
@@ -4093,7 +4118,7 @@ export interface components {
|
|
|
4093
4118
|
userId: string;
|
|
4094
4119
|
fullName: string;
|
|
4095
4120
|
jobTitle: string | null;
|
|
4096
|
-
|
|
4121
|
+
avatarUrl: string | null;
|
|
4097
4122
|
authorities: {
|
|
4098
4123
|
/** @enum {string} */
|
|
4099
4124
|
mechanism: "structural" | "delegated" | "rbac";
|
|
@@ -4104,6 +4129,7 @@ export interface components {
|
|
|
4104
4129
|
/** Format: uuid */
|
|
4105
4130
|
derivedFromUnitId: string;
|
|
4106
4131
|
derivedFromUnitName?: string;
|
|
4132
|
+
derivedFromRoot?: boolean;
|
|
4107
4133
|
derivedFromUserId?: string | null;
|
|
4108
4134
|
explanation: string;
|
|
4109
4135
|
derivationMetadata?: {
|
|
@@ -4226,7 +4252,7 @@ export interface components {
|
|
|
4226
4252
|
id: string;
|
|
4227
4253
|
fullName: string;
|
|
4228
4254
|
jobTitle: string | null;
|
|
4229
|
-
|
|
4255
|
+
avatarUrl: string | null;
|
|
4230
4256
|
primaryUnitId: string | null;
|
|
4231
4257
|
}[];
|
|
4232
4258
|
edges: {
|
|
@@ -7921,6 +7947,101 @@ export interface operations {
|
|
|
7921
7947
|
};
|
|
7922
7948
|
};
|
|
7923
7949
|
};
|
|
7950
|
+
uploadUserAvatar: {
|
|
7951
|
+
parameters: {
|
|
7952
|
+
query?: never;
|
|
7953
|
+
header?: never;
|
|
7954
|
+
path: {
|
|
7955
|
+
userId: string;
|
|
7956
|
+
};
|
|
7957
|
+
cookie?: never;
|
|
7958
|
+
};
|
|
7959
|
+
requestBody: {
|
|
7960
|
+
content: {
|
|
7961
|
+
"multipart/form-data": {
|
|
7962
|
+
[key: string]: unknown;
|
|
7963
|
+
};
|
|
7964
|
+
};
|
|
7965
|
+
};
|
|
7966
|
+
responses: {
|
|
7967
|
+
/** @description Avatar uploaded; resolved avatar returned */
|
|
7968
|
+
200: {
|
|
7969
|
+
headers: {
|
|
7970
|
+
[name: string]: unknown;
|
|
7971
|
+
};
|
|
7972
|
+
content?: never;
|
|
7973
|
+
};
|
|
7974
|
+
/** @description Missing or malformed multipart upload */
|
|
7975
|
+
400: {
|
|
7976
|
+
headers: {
|
|
7977
|
+
[name: string]: unknown;
|
|
7978
|
+
};
|
|
7979
|
+
content?: never;
|
|
7980
|
+
};
|
|
7981
|
+
/** @description Caller may not change this avatar */
|
|
7982
|
+
403: {
|
|
7983
|
+
headers: {
|
|
7984
|
+
[name: string]: unknown;
|
|
7985
|
+
};
|
|
7986
|
+
content?: never;
|
|
7987
|
+
};
|
|
7988
|
+
/** @description Target user is not a member of the caller org */
|
|
7989
|
+
404: {
|
|
7990
|
+
headers: {
|
|
7991
|
+
[name: string]: unknown;
|
|
7992
|
+
};
|
|
7993
|
+
content?: never;
|
|
7994
|
+
};
|
|
7995
|
+
/** @description Upload exceeds the 5 MB size limit */
|
|
7996
|
+
413: {
|
|
7997
|
+
headers: {
|
|
7998
|
+
[name: string]: unknown;
|
|
7999
|
+
};
|
|
8000
|
+
content?: never;
|
|
8001
|
+
};
|
|
8002
|
+
/** @description Unsupported image type or bytes are not a valid image */
|
|
8003
|
+
415: {
|
|
8004
|
+
headers: {
|
|
8005
|
+
[name: string]: unknown;
|
|
8006
|
+
};
|
|
8007
|
+
content?: never;
|
|
8008
|
+
};
|
|
8009
|
+
};
|
|
8010
|
+
};
|
|
8011
|
+
deleteUserAvatar: {
|
|
8012
|
+
parameters: {
|
|
8013
|
+
query?: never;
|
|
8014
|
+
header?: never;
|
|
8015
|
+
path: {
|
|
8016
|
+
userId: string;
|
|
8017
|
+
};
|
|
8018
|
+
cookie?: never;
|
|
8019
|
+
};
|
|
8020
|
+
requestBody?: never;
|
|
8021
|
+
responses: {
|
|
8022
|
+
/** @description Uploaded avatar cleared; resolved avatar returned */
|
|
8023
|
+
200: {
|
|
8024
|
+
headers: {
|
|
8025
|
+
[name: string]: unknown;
|
|
8026
|
+
};
|
|
8027
|
+
content?: never;
|
|
8028
|
+
};
|
|
8029
|
+
/** @description Caller may not change this avatar */
|
|
8030
|
+
403: {
|
|
8031
|
+
headers: {
|
|
8032
|
+
[name: string]: unknown;
|
|
8033
|
+
};
|
|
8034
|
+
content?: never;
|
|
8035
|
+
};
|
|
8036
|
+
/** @description Target user is not a member of the caller org */
|
|
8037
|
+
404: {
|
|
8038
|
+
headers: {
|
|
8039
|
+
[name: string]: unknown;
|
|
8040
|
+
};
|
|
8041
|
+
content?: never;
|
|
8042
|
+
};
|
|
8043
|
+
};
|
|
8044
|
+
};
|
|
7924
8045
|
listDriveFiles: {
|
|
7925
8046
|
parameters: {
|
|
7926
8047
|
query?: never;
|
|
@@ -117,6 +117,7 @@ export const openApiRoutes = {
|
|
|
117
117
|
'/api/users/org-chart': ['GET'],
|
|
118
118
|
'/api/users/org-chart/import': ['POST'],
|
|
119
119
|
'/api/users/org-chart/import/{operationId}/retry': ['POST'],
|
|
120
|
+
'/api/users/{userId}/avatar': ['DELETE', 'PUT'],
|
|
120
121
|
'/api/work-items/{id}': ['GET'],
|
|
121
122
|
'/api/work-items/{id}/content': ['PUT'],
|
|
122
123
|
'/api/work-items/{id}/title': ['PUT'],
|
|
@@ -36,39 +36,39 @@ describe('generateInitials', () => {
|
|
|
36
36
|
})
|
|
37
37
|
|
|
38
38
|
describe('resolveAvatar', () => {
|
|
39
|
-
it('initials is always populated when source is
|
|
40
|
-
const result = resolveAvatar({
|
|
41
|
-
expect(result.source).toBe('
|
|
39
|
+
it('initials is always populated when source is photo (critical invariant)', () => {
|
|
40
|
+
const result = resolveAvatar({ avatarUrl: 'https://example.com/img.jpg', fullName: 'Ian Heidt' })
|
|
41
|
+
expect(result.source).toBe('photo')
|
|
42
42
|
expect(result.url).toBe('https://example.com/img.jpg')
|
|
43
43
|
// INVARIANT: initials is ALWAYS populated, regardless of source.
|
|
44
|
-
// This prevents UI regressions when
|
|
44
|
+
// This prevents UI regressions when the photo fails to load.
|
|
45
45
|
expect(typeof result.initials).toBe('string')
|
|
46
46
|
expect(result.initials).toBe('IH')
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
it('returns initials source when no
|
|
49
|
+
it('returns initials source when no avatarUrl', () => {
|
|
50
50
|
const result = resolveAvatar({ fullName: 'Ian Heidt' } as any)
|
|
51
51
|
expect(result.source).toBe('initials')
|
|
52
52
|
expect(result.initials).toBe('IH')
|
|
53
53
|
expect(result.url).toBeUndefined()
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
it('returns
|
|
57
|
-
const result = resolveAvatar({
|
|
58
|
-
expect(result.source).toBe('
|
|
56
|
+
it('returns photo source with empty initials when fullName is empty', () => {
|
|
57
|
+
const result = resolveAvatar({ avatarUrl: 'https://example.com/img.jpg', fullName: '' })
|
|
58
|
+
expect(result.source).toBe('photo')
|
|
59
59
|
expect(result.url).toBe('https://example.com/img.jpg')
|
|
60
60
|
// Empty is valid — just not undefined
|
|
61
61
|
expect(result.initials).toBe('')
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
-
it('returns initials source when
|
|
65
|
-
const result = resolveAvatar({
|
|
64
|
+
it('returns initials source when avatarUrl is explicitly undefined', () => {
|
|
65
|
+
const result = resolveAvatar({ avatarUrl: undefined, fullName: 'Ian Heidt' })
|
|
66
66
|
expect(result.source).toBe('initials')
|
|
67
67
|
expect(result.initials).toBe('IH')
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
it('returns initials source when
|
|
71
|
-
const result = resolveAvatar({
|
|
70
|
+
it('returns initials source when avatarUrl is empty string (falsy)', () => {
|
|
71
|
+
const result = resolveAvatar({ avatarUrl: '', fullName: 'Ian Heidt' })
|
|
72
72
|
expect(result.source).toBe('initials')
|
|
73
73
|
expect(result.initials).toBe('IH')
|
|
74
74
|
})
|
package/src/identity/avatar.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Avatar Resolution Functions
|
|
3
3
|
*
|
|
4
4
|
* Canonical functions for resolving user avatars.
|
|
5
|
-
* Determines whether to use
|
|
5
|
+
* Determines whether to use a resolved profile photo or initials fallback.
|
|
6
6
|
*
|
|
7
7
|
* @see ADR-BE-063 for design rationale
|
|
8
8
|
*/
|
|
@@ -15,22 +15,22 @@ import type { UserIdentity } from './types';
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Source of the resolved avatar.
|
|
18
|
-
* - '
|
|
18
|
+
* - 'photo': Using a resolved profile photo
|
|
19
19
|
* - 'initials': Using generated initials (fallback)
|
|
20
20
|
*/
|
|
21
|
-
export type AvatarSource = '
|
|
21
|
+
export type AvatarSource = 'photo' | 'initials';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Resolved avatar for UI rendering.
|
|
25
25
|
*
|
|
26
26
|
* INVARIANT: initials is ALWAYS populated, regardless of source.
|
|
27
|
-
* This prevents UI regressions if
|
|
27
|
+
* This prevents UI regressions if the photo fails to load.
|
|
28
28
|
*/
|
|
29
29
|
export interface ResolvedAvatar {
|
|
30
|
-
/** Source of the avatar (
|
|
30
|
+
/** Source of the avatar (photo or initials) */
|
|
31
31
|
source: AvatarSource;
|
|
32
32
|
|
|
33
|
-
/**
|
|
33
|
+
/** resolved photo URL (present only if source === photo) */
|
|
34
34
|
url?: string;
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -85,29 +85,29 @@ export function generateInitials(fullName: string): string {
|
|
|
85
85
|
* Returns a ResolvedAvatar with source, optional URL, and required initials.
|
|
86
86
|
*
|
|
87
87
|
* INVARIANT: initials is ALWAYS populated, regardless of source.
|
|
88
|
-
* This ensures the UI always has a fallback if the
|
|
88
|
+
* This ensures the UI always has a fallback if the photo fails to load.
|
|
89
89
|
*
|
|
90
|
-
* @param identity - Object with
|
|
91
|
-
* @returns ResolvedAvatar with source, url (if
|
|
90
|
+
* @param identity - Object with avatarUrl and fullName fields
|
|
91
|
+
* @returns ResolvedAvatar with source, url (if photo), and initials
|
|
92
92
|
*
|
|
93
93
|
* @example
|
|
94
|
-
* //
|
|
95
|
-
* resolveAvatar({
|
|
96
|
-
* // { source: '
|
|
94
|
+
* // Photo avatar available
|
|
95
|
+
* resolveAvatar({ avatarUrl: "https://...", fullName: "Ian Heidt" })
|
|
96
|
+
* // { source: 'photo', url: 'https://...', initials: 'IH' }
|
|
97
97
|
*
|
|
98
|
-
* // No
|
|
98
|
+
* // No photo avatar (initials fallback)
|
|
99
99
|
* resolveAvatar({ fullName: "Ian Heidt" })
|
|
100
100
|
* // { source: 'initials', initials: 'IH' }
|
|
101
101
|
*/
|
|
102
102
|
export function resolveAvatar(
|
|
103
|
-
identity: Pick<UserIdentity, '
|
|
103
|
+
identity: Pick<UserIdentity, 'avatarUrl' | 'fullName'>
|
|
104
104
|
): ResolvedAvatar {
|
|
105
105
|
const initials = generateInitials(identity.fullName);
|
|
106
106
|
|
|
107
|
-
if (identity.
|
|
107
|
+
if (identity.avatarUrl) {
|
|
108
108
|
return {
|
|
109
|
-
source: '
|
|
110
|
-
url: identity.
|
|
109
|
+
source: 'photo',
|
|
110
|
+
url: identity.avatarUrl,
|
|
111
111
|
initials,
|
|
112
112
|
};
|
|
113
113
|
}
|
|
@@ -28,7 +28,7 @@ export const PeopleOrgChartNodeSchema = z.object({
|
|
|
28
28
|
id: z.string().uuid(),
|
|
29
29
|
fullName: z.string(),
|
|
30
30
|
jobTitle: z.string().nullable(),
|
|
31
|
-
|
|
31
|
+
avatarUrl: z.string().nullable(),
|
|
32
32
|
primaryUnitId: z.string().uuid().nullable(),
|
|
33
33
|
});
|
|
34
34
|
|
package/src/identity/schemas.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { z } from 'zod';
|
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
|
|
17
17
|
const ResolvedAvatarSchema = z.object({
|
|
18
|
-
source: z.enum(['
|
|
18
|
+
source: z.enum(['photo', 'initials']),
|
|
19
19
|
url: z.string().optional(),
|
|
20
20
|
initials: z.string(),
|
|
21
21
|
});
|
package/src/identity/types.ts
CHANGED
|
@@ -79,26 +79,46 @@ export type UserIdentity = {
|
|
|
79
79
|
updatedAt: ISODateString;
|
|
80
80
|
|
|
81
81
|
// ==========================================================================
|
|
82
|
-
//
|
|
82
|
+
// Avatar Fields (source-agnostic; see ADR-BE-063 for original Slack origin)
|
|
83
83
|
// ==========================================================================
|
|
84
|
+
//
|
|
85
|
+
// The avatar model is source-agnostic: avatarUrl is the resolved profile
|
|
86
|
+
// image regardless of where it came from, avatarSource records the stored
|
|
87
|
+
// origin (if any), and avatarUpdatedAt records when it last changed. Avatar
|
|
88
|
+
// rendering depends only on avatarUrl (falling back to generated initials);
|
|
89
|
+
// avatarSource/avatarUpdatedAt are metadata for sync and audit.
|
|
84
90
|
|
|
85
91
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
92
|
+
* Source-agnostic resolved profile image URL.
|
|
93
|
+
* When present, avatar resolution uses this photo (source === 'photo');
|
|
94
|
+
* otherwise it falls back to generated initials.
|
|
88
95
|
*/
|
|
89
|
-
|
|
96
|
+
avatarUrl?: string;
|
|
90
97
|
|
|
91
98
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
99
|
+
* Stored origin of the avatar image.
|
|
100
|
+
* Absent when there is no stored avatar (resolution falls back to initials).
|
|
101
|
+
* - 'slack': synced from a connected Slack workspace
|
|
102
|
+
* - 'upload': uploaded directly by the user
|
|
94
103
|
*/
|
|
95
|
-
|
|
104
|
+
avatarSource?: 'slack' | 'upload';
|
|
96
105
|
|
|
97
106
|
/**
|
|
98
|
-
* When the
|
|
107
|
+
* When the avatar was last updated (synced or uploaded).
|
|
99
108
|
* Used for debugging and audit purposes.
|
|
100
109
|
*/
|
|
101
|
-
|
|
110
|
+
avatarUpdatedAt?: ISODateString;
|
|
111
|
+
|
|
112
|
+
// ==========================================================================
|
|
113
|
+
// Slack Identity Fields (ADR-BE-063)
|
|
114
|
+
// ==========================================================================
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Slack user ID (set when Slack is connected and email matches).
|
|
118
|
+
* Used for efficient lookups in user_change event handling.
|
|
119
|
+
* This is Slack identity, not avatar data — retained independently of avatar source.
|
|
120
|
+
*/
|
|
121
|
+
slackUserId?: string;
|
|
102
122
|
};
|
|
103
123
|
|
|
104
124
|
// =============================================================================
|
package/src/org/index.ts
CHANGED
|
@@ -125,6 +125,8 @@ export {
|
|
|
125
125
|
WorkspaceOverviewSchema,
|
|
126
126
|
WorkspaceMembersResponseSchema,
|
|
127
127
|
WorkspaceMemberDetailSchema,
|
|
128
|
+
WorkspaceMemberManagesEntrySchema,
|
|
129
|
+
OrgUnitDesignationSchema,
|
|
128
130
|
RoleCatalogEntrySchema,
|
|
129
131
|
RoleCatalogResponseSchema,
|
|
130
132
|
WorkspaceAuthConfigSchema,
|
|
@@ -148,6 +150,8 @@ export type {
|
|
|
148
150
|
WorkspaceAccessResponse,
|
|
149
151
|
WorkspaceOverview as WorkspaceOverviewDto,
|
|
150
152
|
WorkspaceMembersResponse,
|
|
153
|
+
WorkspaceMemberManagesEntry,
|
|
154
|
+
OrgUnitDesignation,
|
|
151
155
|
WorkspaceAuthConfig as WorkspaceAuthConfigDto,
|
|
152
156
|
WorkspaceAuditEvent as WorkspaceAuditEventDto,
|
|
153
157
|
WorkspaceResolvePathResponse,
|
package/src/org/schemas.ts
CHANGED
|
@@ -25,6 +25,31 @@ const WorkspaceMemberUnitSummarySchema = z.object({
|
|
|
25
25
|
role: z.enum(['owner', 'manager', 'member']),
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Designation a member holds over a unit they manage (ADR-CTRL-112).
|
|
30
|
+
*
|
|
31
|
+
* Precedence, highest-first: whole_org > admin > leader > delegate > member.
|
|
32
|
+
* The org-wide designations (`whole_org`, `admin`) anchor to the org root
|
|
33
|
+
* unit. This vocabulary is DISPLAY-ONLY — it summarizes management reach for
|
|
34
|
+
* UI surfaces and is NEVER an authorization input (authority resolution uses
|
|
35
|
+
* the orthogonal membership/delegation axes, not this enum).
|
|
36
|
+
*/
|
|
37
|
+
export const OrgUnitDesignationSchema = z.enum([
|
|
38
|
+
'whole_org',
|
|
39
|
+
'admin',
|
|
40
|
+
'leader',
|
|
41
|
+
'delegate',
|
|
42
|
+
'member',
|
|
43
|
+
]);
|
|
44
|
+
export type OrgUnitDesignation = z.infer<typeof OrgUnitDesignationSchema>;
|
|
45
|
+
|
|
46
|
+
export const WorkspaceMemberManagesEntrySchema = z.object({
|
|
47
|
+
unitId: z.string(),
|
|
48
|
+
unitName: z.string(),
|
|
49
|
+
designation: OrgUnitDesignationSchema,
|
|
50
|
+
});
|
|
51
|
+
export type WorkspaceMemberManagesEntry = z.infer<typeof WorkspaceMemberManagesEntrySchema>;
|
|
52
|
+
|
|
28
53
|
const WorkspaceMemberSchema = z.object({
|
|
29
54
|
id: z.string(),
|
|
30
55
|
name: z.string(),
|
|
@@ -36,6 +61,7 @@ const WorkspaceMemberSchema = z.object({
|
|
|
36
61
|
lastActiveAt: z.string().nullable(),
|
|
37
62
|
primaryUnitId: z.string().uuid().nullable(),
|
|
38
63
|
unitMemberships: z.array(WorkspaceMemberUnitSummarySchema),
|
|
64
|
+
manages: z.array(WorkspaceMemberManagesEntrySchema),
|
|
39
65
|
unitMembershipsTruncated: z.boolean(),
|
|
40
66
|
inviteStatus: z.enum(['active', 'pending', 'expired']).nullable(),
|
|
41
67
|
});
|
|
@@ -867,7 +893,7 @@ export const OrgUnitOwnerSchema = z.object({
|
|
|
867
893
|
userId: z.string().uuid(),
|
|
868
894
|
fullName: z.string().min(1),
|
|
869
895
|
jobTitle: z.string().nullable(),
|
|
870
|
-
|
|
896
|
+
avatarUrl: z.string().nullable(),
|
|
871
897
|
/** All authority grants this user holds for the unit. At least one entry. */
|
|
872
898
|
authorities: z.array(OwnerAuthoritySchema).min(1),
|
|
873
899
|
});
|