@fabriktor/fx 0.0.47 → 0.0.49

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.
@@ -2,12 +2,14 @@ export type AuthEmailOptions = {
2
2
  email: string;
3
3
  password: string;
4
4
  };
5
+ export type UpdateAuthEmailOptions = Pick<AuthEmailOptions, "email">;
5
6
  export type AuthSession = {
6
7
  token: string;
7
8
  };
8
9
  export interface AuthOperator {
9
10
  signUp(opts: AuthEmailOptions): Promise<AuthSession>;
10
11
  signIn(opts: AuthEmailOptions): Promise<AuthSession>;
12
+ updateEmail(opts: UpdateAuthEmailOptions): Promise<void>;
11
13
  getToken(): Promise<string>;
12
14
  refreshToken(): Promise<string>;
13
15
  emailVerified(): boolean;
@@ -4,6 +4,7 @@ import type { UsersFXOperator } from "../users/users.js";
4
4
  type ConnectionInsert = Parameters<ConnectionsOperator["insertOneConnection"]>[0]["in"];
5
5
  type BootstrapInput = Parameters<UsersOperator["bootstrap"]>[0]["in"];
6
6
  type BootstrapResult = Awaited<ReturnType<UsersOperator["bootstrap"]>>;
7
+ type UpdateEmailOptions = Parameters<AuthOperator["updateEmail"]>[0];
7
8
  export declare const SignInMode: {
8
9
  readonly Email: "email";
9
10
  readonly Username: "username";
@@ -31,6 +32,7 @@ export interface SessionFXOperator {
31
32
  signUpAndBootstrap(opts: SignUpAndBootstrapOptions): Promise<BootstrapResult>;
32
33
  signIn(opts: SignInOptions): Promise<void>;
33
34
  signInWithEmail(opts: SignInWithEmailOptions): Promise<void>;
35
+ updateEmail(opts: UpdateEmailOptions): Promise<void>;
34
36
  checkEmailVerification(): Promise<boolean>;
35
37
  }
36
38
  export declare class SessionFX implements SessionFXOperator {
@@ -42,6 +44,7 @@ export declare class SessionFX implements SessionFXOperator {
42
44
  signUpAndBootstrap(opts: SignUpAndBootstrapOptions): Promise<BootstrapResult>;
43
45
  signIn(opts: SignInOptions): Promise<void>;
44
46
  signInWithEmail(opts: SignInWithEmailOptions): Promise<void>;
47
+ updateEmail(opts: UpdateEmailOptions): Promise<void>;
45
48
  checkEmailVerification(): Promise<boolean>;
46
49
  }
47
50
  export declare function newSessionFX(opts: SessionFXOptions): SessionFX;
@@ -49,6 +49,12 @@ export class SessionFX {
49
49
  });
50
50
  await this.users.sync();
51
51
  }
52
+ async updateEmail(opts) {
53
+ await this.auth.updateEmail(opts);
54
+ await this.auth.reloadUser();
55
+ await this.auth.refreshToken();
56
+ await this.users.sync();
57
+ }
52
58
  async checkEmailVerification() {
53
59
  await this.auth.reloadUser();
54
60
  if (!this.auth.emailVerified()) {
@@ -1,11 +1,21 @@
1
1
  import type { GetByUIDOptions, OperationResultOf, TmpPhonesOperator, TmpUsernamesOperator, UsernameVerification, UsersOperator } from "@fabriktor/client";
2
- import type { User } from "@fabriktor/schema";
2
+ import type { InAddress, InUser, User } from "@fabriktor/schema";
3
3
  import { type RunSearchOptions, type SearchPage } from "../core/search.js";
4
4
  export type ResolveUsernameOptions = Parameters<UsersOperator["resolveUsername"]>[0];
5
5
  export type VerifyUsernameOptions = Parameters<TmpUsernamesOperator["verify"]>[0];
6
6
  export type GenerateUsernamesOptions = VerifyUsernameOptions;
7
7
  export type SearchUsersOptions = GetByUIDOptions & Omit<RunSearchOptions<User>, "search">;
8
8
  export type SearchUsersPage = SearchPage<User>;
9
+ export type UserProfileInput = Pick<InUser, "first_name" | "last_name" | "phone" | "company_name" | "privacy"> & InAddress;
10
+ export type UpdateUserProfileOptions = GetByUIDOptions & {
11
+ id: string;
12
+ in: UserProfileInput;
13
+ };
14
+ export type UpdateUserPhoneOptions = GetByUIDOptions & {
15
+ id: string;
16
+ phone: string;
17
+ code: string;
18
+ };
9
19
  export type SendPhoneCodeOptions = Parameters<TmpPhonesOperator["send"]>[0];
10
20
  export type VerifyPhoneCodeOptions = Parameters<TmpPhonesOperator["verify"]>[0];
11
21
  export type UsersFXOptions = {
@@ -15,6 +25,8 @@ export type UsersFXOptions = {
15
25
  };
16
26
  export interface UsersFXOperator {
17
27
  searchUsers(opts: SearchUsersOptions): Promise<SearchUsersPage>;
28
+ updateUserProfile(opts: UpdateUserProfileOptions): Promise<User>;
29
+ updateUserPhone(opts: UpdateUserPhoneOptions): Promise<User>;
18
30
  resolveUsername(opts: ResolveUsernameOptions): Promise<string>;
19
31
  verifyUsername(opts: VerifyUsernameOptions): Promise<OperationResultOf<UsernameVerification>>;
20
32
  generateUsernames(opts: GenerateUsernamesOptions): Promise<OperationResultOf<UsernameVerification>>;
@@ -27,10 +39,13 @@ export declare class UsersFX implements UsersFXOperator {
27
39
  private tmp_phones;
28
40
  constructor(opts: UsersFXOptions);
29
41
  searchUsers(opts: SearchUsersOptions): Promise<SearchUsersPage>;
42
+ updateUserProfile(opts: UpdateUserProfileOptions): Promise<User>;
43
+ updateUserPhone(opts: UpdateUserPhoneOptions): Promise<User>;
30
44
  resolveUsername(opts: ResolveUsernameOptions): Promise<string>;
31
45
  verifyUsername(opts: VerifyUsernameOptions): Promise<OperationResultOf<UsernameVerification>>;
32
46
  generateUsernames(opts: GenerateUsernamesOptions): Promise<OperationResultOf<UsernameVerification>>;
33
47
  sendPhoneCode(opts: SendPhoneCodeOptions): Promise<void>;
34
48
  verifyPhoneCode(opts: VerifyPhoneCodeOptions): Promise<void>;
49
+ private updatedUser;
35
50
  }
36
51
  export declare function newUsersFX(opts: UsersFXOptions): UsersFX;
@@ -10,7 +10,12 @@ import { normalizeUsername, validateUsernameFormat } from "./username.js";
10
10
  const msgUnableToResolveUsername = "Unable to resolve username.";
11
11
  const msgPhoneRequired = "Phone number is required.";
12
12
  const msgPhoneCodeRequired = "Verification code is required.";
13
+ const msgFirstNameRequired = "First name is required.";
14
+ const msgLastNameRequired = "Last name is required.";
15
+ const msgAddressRequired = "A complete address is required.";
16
+ const msgUserMissingAfterUpdate = "User could not be resolved after the profile update.";
13
17
  const resourceUsername = "username";
18
+ const resourceUser = "user";
14
19
  export class UsersFX {
15
20
  users;
16
21
  tmp_usernames;
@@ -30,6 +35,31 @@ export class UsersFX {
30
35
  }),
31
36
  });
32
37
  }
38
+ async updateUserProfile(opts) {
39
+ const input = normalizeUserProfileInput(opts.in);
40
+ validateUserProfileInput(input);
41
+ await this.users.replaceOneUser({
42
+ id: opts.id,
43
+ in: input,
44
+ });
45
+ return await this.updatedUser(opts.uid);
46
+ }
47
+ async updateUserPhone(opts) {
48
+ const phone = normalizePhone(opts.phone);
49
+ const code = normalizePhoneCode(opts.code);
50
+ if (!validatePhone(phone)) {
51
+ throw errPhoneRequired("phone");
52
+ }
53
+ if (!validatePhoneCode(code)) {
54
+ throw errPhoneCodeRequired("code");
55
+ }
56
+ await this.tmp_phones.verify({ phone, code });
57
+ await this.users.replaceOneUser({
58
+ id: opts.id,
59
+ in: { phone },
60
+ });
61
+ return await this.updatedUser(opts.uid);
62
+ }
33
63
  async resolveUsername(opts) {
34
64
  const username = normalizeUsername(opts.username);
35
65
  const out = await this.users.resolveUsername({
@@ -81,10 +111,81 @@ export class UsersFX {
81
111
  code,
82
112
  });
83
113
  }
114
+ async updatedUser(uid) {
115
+ const out = await this.users.getByUID({ uid });
116
+ const user = out.value?.[0];
117
+ if (!user) {
118
+ throw errResolveFailed({
119
+ resource: resourceUser,
120
+ msg: msgUserMissingAfterUpdate,
121
+ });
122
+ }
123
+ return user;
124
+ }
84
125
  }
85
126
  export function newUsersFX(opts) {
86
127
  return new UsersFX(opts);
87
128
  }
129
+ function normalizeUserProfileInput(input) {
130
+ return {
131
+ first_name: input.first_name.trim(),
132
+ last_name: input.last_name.trim(),
133
+ phone: input.phone.trim(),
134
+ company_name: input.company_name.trim(),
135
+ privacy: {
136
+ is_searchable: input.privacy.is_searchable,
137
+ is_messageable: input.privacy.is_messageable,
138
+ },
139
+ lat: input.lat,
140
+ lon: input.lon,
141
+ street: input.street.trim(),
142
+ city: input.city.trim(),
143
+ state: input.state.trim(),
144
+ postal_code: input.postal_code.trim(),
145
+ country: input.country.trim(),
146
+ formatted_address: input.formatted_address.trim(),
147
+ place_name: input.place_name.trim(),
148
+ place_id: input.place_id.trim(),
149
+ };
150
+ }
151
+ function validateUserProfileInput(input) {
152
+ if (!input.first_name) {
153
+ throw errRequired({
154
+ field: "first_name",
155
+ msg: msgFirstNameRequired,
156
+ });
157
+ }
158
+ if (!input.last_name) {
159
+ throw errRequired({
160
+ field: "last_name",
161
+ msg: msgLastNameRequired,
162
+ });
163
+ }
164
+ if (input.phone && !validatePhone(input.phone)) {
165
+ throw errValidation({
166
+ field: "phone",
167
+ msg: msgPhoneRequired,
168
+ });
169
+ }
170
+ if (!hasCompleteAddress(input)) {
171
+ throw errValidation({
172
+ field: "formatted_address",
173
+ msg: msgAddressRequired,
174
+ });
175
+ }
176
+ }
177
+ function hasCompleteAddress(input) {
178
+ return (input.lat !== 0 &&
179
+ input.lon !== 0 &&
180
+ input.street.length > 0 &&
181
+ input.city.length > 0 &&
182
+ input.state.length > 0 &&
183
+ input.postal_code.length > 0 &&
184
+ input.country.length > 0 &&
185
+ input.formatted_address.length > 0 &&
186
+ input.place_name.length > 0 &&
187
+ input.place_id.length > 0);
188
+ }
88
189
  function errUsernameResolveFailed(field) {
89
190
  return errResolveFailed({
90
191
  resource: resourceUsername,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabriktor/fx",
3
- "version": "0.0.47",
3
+ "version": "0.0.49",
4
4
  "description": "![Fabriktor Logo](./img/fabriktor-character.gif)",
5
5
  "type": "module",
6
6
  "exports": {