realtimex-crm 0.5.7 → 0.6.1
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/assets/{DealList-hFgyoSCd.js → DealList-B16NLW5g.js} +2 -2
- package/dist/assets/{DealList-hFgyoSCd.js.map → DealList-B16NLW5g.js.map} +1 -1
- package/dist/assets/index-C6XAWVQG.css +1 -0
- package/dist/assets/index-Cx_2Y6Ur.js +153 -0
- package/dist/assets/{index-BzM6--R_.js.map → index-Cx_2Y6Ur.js.map} +1 -1
- package/dist/index.html +1 -1
- package/dist/stats.html +1 -1
- package/package.json +1 -1
- package/src/components/admin/login-page.tsx +14 -6
- package/src/components/atomic-crm/providers/supabase/authProvider.ts +14 -0
- package/src/components/atomic-crm/providers/supabase/dataProvider.ts +46 -12
- package/src/components/atomic-crm/root/CRM.tsx +7 -0
- package/src/components/atomic-crm/sales/SalesList.tsx +200 -1
- package/src/components/atomic-crm/settings/SettingsPage.tsx +6 -18
- package/src/components/supabase/change-password-page.tsx +119 -0
- package/src/components/supabase/forgot-password-page.tsx +181 -36
- package/src/components/supabase/otp-input.tsx +116 -0
- package/src/components/supabase/otp-login-page.tsx +254 -0
- package/supabase/config.toml +248 -23
- package/supabase/functions/_shared/utils.ts +1 -1
- package/supabase/functions/setup/index.ts +58 -0
- package/supabase/functions/users/index.ts +72 -14
- package/supabase/migrations/20251218200545_add_email_confirmed_to_sales.sql +52 -0
- package/dist/assets/index-BzM6--R_.js +0 -153
- package/dist/assets/index-C5TuuS9H.css +0 -1
package/supabase/config.toml
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
|
+
# For detailed configuration reference documentation, visit:
|
|
2
|
+
# https://supabase.com/docs/guides/local-development/cli/config
|
|
1
3
|
# A string used to distinguish different Supabase projects on the same host. Defaults to the
|
|
2
4
|
# working directory name when running `supabase init`.
|
|
3
|
-
project_id = "atomic-crm
|
|
5
|
+
project_id = "atomic-crm"
|
|
4
6
|
|
|
5
7
|
[api]
|
|
6
8
|
enabled = true
|
|
7
9
|
# Port to use for the API URL.
|
|
8
10
|
port = 54321
|
|
9
11
|
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
|
|
10
|
-
# endpoints. public and
|
|
11
|
-
schemas = ["public", "
|
|
12
|
-
# Extra schemas to add to the search_path of every request.
|
|
12
|
+
# endpoints. `public` and `graphql_public` schemas are included by default.
|
|
13
|
+
schemas = ["public", "graphql_public"]
|
|
14
|
+
# Extra schemas to add to the search_path of every request.
|
|
13
15
|
extra_search_path = ["public", "extensions"]
|
|
14
16
|
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
|
|
15
17
|
# for accidental or malicious requests.
|
|
16
18
|
max_rows = 1000
|
|
17
19
|
|
|
20
|
+
[api.tls]
|
|
21
|
+
# Enable HTTPS endpoints locally using a self-signed certificate.
|
|
22
|
+
enabled = false
|
|
23
|
+
# Paths to self-signed certificate pair.
|
|
24
|
+
# cert_path = "../certs/my-cert.pem"
|
|
25
|
+
# key_path = "../certs/my-key.pem"
|
|
26
|
+
|
|
18
27
|
[db]
|
|
19
28
|
# Port to use for the local database URL.
|
|
20
29
|
port = 54322
|
|
@@ -22,7 +31,7 @@ port = 54322
|
|
|
22
31
|
shadow_port = 54320
|
|
23
32
|
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
|
|
24
33
|
# server_version;` on the remote database to check.
|
|
25
|
-
major_version =
|
|
34
|
+
major_version = 17
|
|
26
35
|
|
|
27
36
|
[db.pooler]
|
|
28
37
|
enabled = false
|
|
@@ -36,9 +45,36 @@ default_pool_size = 20
|
|
|
36
45
|
# Maximum number of client connections allowed.
|
|
37
46
|
max_client_conn = 100
|
|
38
47
|
|
|
48
|
+
# [db.vault]
|
|
49
|
+
# secret_key = "env(SECRET_VALUE)"
|
|
50
|
+
|
|
51
|
+
[db.migrations]
|
|
52
|
+
# If disabled, migrations will be skipped during a db push or reset.
|
|
53
|
+
enabled = true
|
|
54
|
+
# Specifies an ordered list of schema files that describe your database.
|
|
55
|
+
# Supports glob patterns relative to supabase directory: "./schemas/*.sql"
|
|
56
|
+
schema_paths = []
|
|
57
|
+
|
|
58
|
+
[db.seed]
|
|
59
|
+
# If enabled, seeds the database after migrations during a db reset.
|
|
60
|
+
enabled = true
|
|
61
|
+
# Specifies an ordered list of seed files to load during db reset.
|
|
62
|
+
# Supports glob patterns relative to supabase directory: "./seeds/*.sql"
|
|
63
|
+
sql_paths = ["./seed.sql"]
|
|
64
|
+
|
|
65
|
+
[db.network_restrictions]
|
|
66
|
+
# Enable management of network restrictions.
|
|
67
|
+
enabled = false
|
|
68
|
+
# List of IPv4 CIDR blocks allowed to connect to the database.
|
|
69
|
+
# Defaults to allow all IPv4 connections. Set empty array to block all IPs.
|
|
70
|
+
allowed_cidrs = ["0.0.0.0/0"]
|
|
71
|
+
# List of IPv6 CIDR blocks allowed to connect to the database.
|
|
72
|
+
# Defaults to allow all IPv6 connections. Set empty array to block all IPs.
|
|
73
|
+
allowed_cidrs_v6 = ["::/0"]
|
|
74
|
+
|
|
39
75
|
[realtime]
|
|
40
76
|
enabled = true
|
|
41
|
-
# Bind realtime via either IPv4 or IPv6. (default:
|
|
77
|
+
# Bind realtime via either IPv4 or IPv6. (default: IPv4)
|
|
42
78
|
# ip_version = "IPv6"
|
|
43
79
|
# The maximum length in bytes of HTTP request headers. (default: 4096)
|
|
44
80
|
# max_header_length = 4096
|
|
@@ -49,6 +85,8 @@ enabled = true
|
|
|
49
85
|
port = 54323
|
|
50
86
|
# External URL of the API server that frontend connects to.
|
|
51
87
|
api_url = "http://127.0.0.1"
|
|
88
|
+
# OpenAI API Key to use for Supabase AI in the Supabase Studio.
|
|
89
|
+
openai_api_key = "env(OPENAI_API_KEY)"
|
|
52
90
|
|
|
53
91
|
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
|
|
54
92
|
# are monitored, and you can view the emails that would have been sent from the web interface.
|
|
@@ -59,21 +97,63 @@ port = 54324
|
|
|
59
97
|
# Uncomment to expose additional ports for testing user applications that send emails.
|
|
60
98
|
# smtp_port = 54325
|
|
61
99
|
# pop3_port = 54326
|
|
100
|
+
# admin_email = "admin@email.com"
|
|
101
|
+
# sender_name = "Admin"
|
|
62
102
|
|
|
63
103
|
[storage]
|
|
64
104
|
enabled = true
|
|
65
105
|
# The maximum file size allowed (e.g. "5MB", "500KB").
|
|
66
106
|
file_size_limit = "50MiB"
|
|
67
107
|
|
|
108
|
+
# Uncomment to configure local storage buckets
|
|
109
|
+
# [storage.buckets.images]
|
|
110
|
+
# public = false
|
|
111
|
+
# file_size_limit = "50MiB"
|
|
112
|
+
# allowed_mime_types = ["image/png", "image/jpeg"]
|
|
113
|
+
# objects_path = "./images"
|
|
114
|
+
|
|
115
|
+
# Allow connections via S3 compatible clients
|
|
116
|
+
[storage.s3_protocol]
|
|
117
|
+
enabled = true
|
|
118
|
+
|
|
119
|
+
# Image transformation API is available to Supabase Pro plan.
|
|
120
|
+
# [storage.image_transformation]
|
|
121
|
+
# enabled = true
|
|
122
|
+
|
|
123
|
+
# Store analytical data in S3 for running ETL jobs over Iceberg Catalog
|
|
124
|
+
# This feature is only available on the hosted platform.
|
|
125
|
+
[storage.analytics]
|
|
126
|
+
enabled = false
|
|
127
|
+
max_namespaces = 5
|
|
128
|
+
max_tables = 10
|
|
129
|
+
max_catalogs = 2
|
|
130
|
+
|
|
131
|
+
# Analytics Buckets is available to Supabase Pro plan.
|
|
132
|
+
# [storage.analytics.buckets.my-warehouse]
|
|
133
|
+
|
|
134
|
+
# Store vector embeddings in S3 for large and durable datasets
|
|
135
|
+
# This feature is only available on the hosted platform.
|
|
136
|
+
[storage.vector]
|
|
137
|
+
enabled = false
|
|
138
|
+
max_buckets = 10
|
|
139
|
+
max_indexes = 5
|
|
140
|
+
|
|
141
|
+
# Vector Buckets is available to Supabase Pro plan.
|
|
142
|
+
# [storage.vector.buckets.documents-openai]
|
|
143
|
+
|
|
68
144
|
[auth]
|
|
69
145
|
enabled = true
|
|
70
146
|
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
|
|
71
147
|
# in emails.
|
|
72
|
-
site_url = "http://
|
|
148
|
+
site_url = "http://127.0.0.1:3000"
|
|
73
149
|
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
|
74
|
-
additional_redirect_urls = ["https://
|
|
150
|
+
additional_redirect_urls = ["https://127.0.0.1:3000"]
|
|
75
151
|
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
|
|
76
152
|
jwt_expiry = 3600
|
|
153
|
+
# JWT issuer URL. If not set, defaults to the local API URL (http://127.0.0.1:<port>/auth/v1).
|
|
154
|
+
# jwt_issuer = ""
|
|
155
|
+
# Path to JWT signing key. DO NOT commit your signing keys file to git.
|
|
156
|
+
# signing_keys_path = "./signing_keys.json"
|
|
77
157
|
# If disabled, the refresh token will never expire.
|
|
78
158
|
enable_refresh_token_rotation = true
|
|
79
159
|
# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
|
|
@@ -81,6 +161,37 @@ enable_refresh_token_rotation = true
|
|
|
81
161
|
refresh_token_reuse_interval = 10
|
|
82
162
|
# Allow/disallow new user signups to your project.
|
|
83
163
|
enable_signup = true
|
|
164
|
+
# Allow/disallow anonymous sign-ins to your project.
|
|
165
|
+
enable_anonymous_sign_ins = false
|
|
166
|
+
# Allow/disallow testing manual linking of accounts
|
|
167
|
+
enable_manual_linking = false
|
|
168
|
+
# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.
|
|
169
|
+
minimum_password_length = 6
|
|
170
|
+
# Passwords that do not meet the following requirements will be rejected as weak. Supported values
|
|
171
|
+
# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols`
|
|
172
|
+
password_requirements = ""
|
|
173
|
+
|
|
174
|
+
[auth.rate_limit]
|
|
175
|
+
# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
|
|
176
|
+
email_sent = 2
|
|
177
|
+
# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
|
|
178
|
+
sms_sent = 30
|
|
179
|
+
# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
|
|
180
|
+
anonymous_users = 30
|
|
181
|
+
# Number of sessions that can be refreshed in a 5 minute interval per IP address.
|
|
182
|
+
token_refresh = 150
|
|
183
|
+
# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users).
|
|
184
|
+
sign_in_sign_ups = 30
|
|
185
|
+
# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address.
|
|
186
|
+
token_verifications = 30
|
|
187
|
+
# Number of Web3 logins that can be made in a 5 minute interval per IP address.
|
|
188
|
+
web3 = 30
|
|
189
|
+
|
|
190
|
+
# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`.
|
|
191
|
+
# [auth.captcha]
|
|
192
|
+
# enabled = true
|
|
193
|
+
# provider = "hcaptcha"
|
|
194
|
+
# secret = ""
|
|
84
195
|
|
|
85
196
|
[auth.email]
|
|
86
197
|
# Allow/disallow new user signups via email to your project.
|
|
@@ -90,27 +201,67 @@ enable_signup = true
|
|
|
90
201
|
double_confirm_changes = true
|
|
91
202
|
# If enabled, users need to confirm their email address before signing in.
|
|
92
203
|
enable_confirmations = false
|
|
204
|
+
# If enabled, users will need to reauthenticate or have logged in recently to change their password.
|
|
205
|
+
secure_password_change = false
|
|
206
|
+
# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
|
|
207
|
+
max_frequency = "1s"
|
|
208
|
+
# Number of characters used in the email OTP.
|
|
209
|
+
otp_length = 6
|
|
210
|
+
# Number of seconds before the email OTP expires (defaults to 1 hour).
|
|
211
|
+
otp_expiry = 3600
|
|
93
212
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
213
|
+
# Use a production-ready SMTP server
|
|
214
|
+
# [auth.email.smtp]
|
|
215
|
+
# enabled = true
|
|
216
|
+
# host = "smtp.sendgrid.net"
|
|
217
|
+
# port = 587
|
|
218
|
+
# user = "apikey"
|
|
219
|
+
# pass = "env(SENDGRID_API_KEY)"
|
|
220
|
+
# admin_email = "admin@email.com"
|
|
221
|
+
# sender_name = "Admin"
|
|
97
222
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
223
|
+
# Uncomment to customize email template
|
|
224
|
+
# [auth.email.template.invite]
|
|
225
|
+
# subject = "You have been invited"
|
|
226
|
+
# content_path = "./supabase/templates/invite.html"
|
|
227
|
+
|
|
228
|
+
# Uncomment to customize notification email template
|
|
229
|
+
# [auth.email.notification.password_changed]
|
|
230
|
+
# enabled = true
|
|
231
|
+
# subject = "Your password has been changed"
|
|
232
|
+
# content_path = "./templates/password_changed_notification.html"
|
|
101
233
|
|
|
102
234
|
[auth.sms]
|
|
103
235
|
# Allow/disallow new user signups via SMS to your project.
|
|
104
|
-
enable_signup =
|
|
236
|
+
enable_signup = false
|
|
105
237
|
# If enabled, users need to confirm their phone number before signing in.
|
|
106
238
|
enable_confirmations = false
|
|
107
239
|
# Template for sending OTP to users
|
|
108
|
-
template = "Your code is {{ .Code }}
|
|
240
|
+
template = "Your code is {{ .Code }}"
|
|
241
|
+
# Controls the minimum amount of time that must pass before sending another sms otp.
|
|
242
|
+
max_frequency = "5s"
|
|
109
243
|
|
|
110
244
|
# Use pre-defined map of phone number to OTP for testing.
|
|
111
|
-
[auth.sms.test_otp]
|
|
245
|
+
# [auth.sms.test_otp]
|
|
112
246
|
# 4152127777 = "123456"
|
|
113
247
|
|
|
248
|
+
# Configure logged in session timeouts.
|
|
249
|
+
# [auth.sessions]
|
|
250
|
+
# Force log out after the specified duration.
|
|
251
|
+
# timebox = "24h"
|
|
252
|
+
# Force log out if the user has been inactive longer than the specified duration.
|
|
253
|
+
# inactivity_timeout = "8h"
|
|
254
|
+
|
|
255
|
+
# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object.
|
|
256
|
+
# [auth.hook.before_user_created]
|
|
257
|
+
# enabled = true
|
|
258
|
+
# uri = "pg-functions://postgres/auth/before-user-created-hook"
|
|
259
|
+
|
|
260
|
+
# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used.
|
|
261
|
+
# [auth.hook.custom_access_token]
|
|
262
|
+
# enabled = true
|
|
263
|
+
# uri = "pg-functions://<database>/<schema>/<hook_name>"
|
|
264
|
+
|
|
114
265
|
# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
|
|
115
266
|
[auth.sms.twilio]
|
|
116
267
|
enabled = false
|
|
@@ -119,8 +270,31 @@ message_service_sid = ""
|
|
|
119
270
|
# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
|
|
120
271
|
auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
|
|
121
272
|
|
|
273
|
+
# Multi-factor-authentication is available to Supabase Pro plan.
|
|
274
|
+
[auth.mfa]
|
|
275
|
+
# Control how many MFA factors can be enrolled at once per user.
|
|
276
|
+
max_enrolled_factors = 10
|
|
277
|
+
|
|
278
|
+
# Control MFA via App Authenticator (TOTP)
|
|
279
|
+
[auth.mfa.totp]
|
|
280
|
+
enroll_enabled = false
|
|
281
|
+
verify_enabled = false
|
|
282
|
+
|
|
283
|
+
# Configure MFA via Phone Messaging
|
|
284
|
+
[auth.mfa.phone]
|
|
285
|
+
enroll_enabled = false
|
|
286
|
+
verify_enabled = false
|
|
287
|
+
otp_length = 6
|
|
288
|
+
template = "Your code is {{ .Code }}"
|
|
289
|
+
max_frequency = "5s"
|
|
290
|
+
|
|
291
|
+
# Configure MFA via WebAuthn
|
|
292
|
+
# [auth.mfa.web_authn]
|
|
293
|
+
# enroll_enabled = true
|
|
294
|
+
# verify_enabled = true
|
|
295
|
+
|
|
122
296
|
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
|
|
123
|
-
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `
|
|
297
|
+
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`,
|
|
124
298
|
# `twitter`, `slack`, `spotify`, `workos`, `zoom`.
|
|
125
299
|
[auth.external.apple]
|
|
126
300
|
enabled = false
|
|
@@ -132,11 +306,65 @@ redirect_uri = ""
|
|
|
132
306
|
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
|
|
133
307
|
# or any other third-party OIDC providers.
|
|
134
308
|
url = ""
|
|
309
|
+
# If enabled, the nonce check will be skipped. Required for local sign in with Google auth.
|
|
310
|
+
skip_nonce_check = false
|
|
311
|
+
# If enabled, it will allow the user to successfully authenticate when the provider does not return an email address.
|
|
312
|
+
email_optional = false
|
|
135
313
|
|
|
136
|
-
|
|
314
|
+
# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard.
|
|
315
|
+
# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting.
|
|
316
|
+
[auth.web3.solana]
|
|
317
|
+
enabled = false
|
|
318
|
+
|
|
319
|
+
# Use Firebase Auth as a third-party provider alongside Supabase Auth.
|
|
320
|
+
[auth.third_party.firebase]
|
|
321
|
+
enabled = false
|
|
322
|
+
# project_id = "my-firebase-project"
|
|
323
|
+
|
|
324
|
+
# Use Auth0 as a third-party provider alongside Supabase Auth.
|
|
325
|
+
[auth.third_party.auth0]
|
|
326
|
+
enabled = false
|
|
327
|
+
# tenant = "my-auth0-tenant"
|
|
328
|
+
# tenant_region = "us"
|
|
329
|
+
|
|
330
|
+
# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth.
|
|
331
|
+
[auth.third_party.aws_cognito]
|
|
332
|
+
enabled = false
|
|
333
|
+
# user_pool_id = "my-user-pool-id"
|
|
334
|
+
# user_pool_region = "us-east-1"
|
|
335
|
+
|
|
336
|
+
# Use Clerk as a third-party provider alongside Supabase Auth.
|
|
337
|
+
[auth.third_party.clerk]
|
|
137
338
|
enabled = false
|
|
339
|
+
# Obtain from https://clerk.com/setup/supabase
|
|
340
|
+
# domain = "example.clerk.accounts.dev"
|
|
341
|
+
|
|
342
|
+
# OAuth server configuration
|
|
343
|
+
[auth.oauth_server]
|
|
344
|
+
# Enable OAuth server functionality
|
|
345
|
+
enabled = false
|
|
346
|
+
# Path for OAuth consent flow UI
|
|
347
|
+
authorization_url_path = "/oauth/consent"
|
|
348
|
+
# Allow dynamic client registration
|
|
349
|
+
allow_dynamic_registration = false
|
|
350
|
+
|
|
351
|
+
[edge_runtime]
|
|
352
|
+
enabled = true
|
|
353
|
+
# Supported request policies: `oneshot`, `per_worker`.
|
|
354
|
+
# `per_worker` (default) — enables hot reload during local development.
|
|
355
|
+
# `oneshot` — fallback mode if hot reload causes issues (e.g. in large repos or with symlinks).
|
|
356
|
+
policy = "per_worker"
|
|
357
|
+
# Port to attach the Chrome inspector for debugging edge functions.
|
|
358
|
+
inspector_port = 8083
|
|
359
|
+
# The Deno major version to use.
|
|
360
|
+
deno_version = 2
|
|
361
|
+
|
|
362
|
+
# [edge_runtime.secrets]
|
|
363
|
+
# secret_key = "env(SECRET_VALUE)"
|
|
364
|
+
|
|
365
|
+
[analytics]
|
|
366
|
+
enabled = true
|
|
138
367
|
port = 54327
|
|
139
|
-
vector_port = 54328
|
|
140
368
|
# Configure one of the supported backends: `postgres`, `bigquery`.
|
|
141
369
|
backend = "postgres"
|
|
142
370
|
|
|
@@ -152,6 +380,3 @@ s3_region = "env(S3_REGION)"
|
|
|
152
380
|
s3_access_key = "env(S3_ACCESS_KEY)"
|
|
153
381
|
# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
|
|
154
382
|
s3_secret_key = "env(S3_SECRET_KEY)"
|
|
155
|
-
|
|
156
|
-
[functions.postmark]
|
|
157
|
-
verify_jwt = false
|
|
@@ -2,7 +2,7 @@ export const corsHeaders = {
|
|
|
2
2
|
"Access-Control-Allow-Origin": "*",
|
|
3
3
|
"Access-Control-Allow-Headers":
|
|
4
4
|
"authorization, x-client-info, apikey, content-type",
|
|
5
|
-
"Access-Control-Allow-Methods": "POST, PATCH, DELETE",
|
|
5
|
+
"Access-Control-Allow-Methods": "POST, PATCH, PUT, DELETE",
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
export function createErrorResponse(status: number, message: string) {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
2
|
+
import { supabaseAdmin } from "../_shared/supabaseAdmin.ts";
|
|
3
|
+
import { corsHeaders, createErrorResponse } from "../_shared/utils.ts";
|
|
4
|
+
|
|
5
|
+
async function createFirstUser(req: Request) {
|
|
6
|
+
const { email, password, first_name, last_name } = await req.json();
|
|
7
|
+
|
|
8
|
+
// Check if any users exist
|
|
9
|
+
const { count } = await supabaseAdmin
|
|
10
|
+
.from("sales")
|
|
11
|
+
.select("*", { count: "exact", head: true });
|
|
12
|
+
|
|
13
|
+
if (count && count > 0) {
|
|
14
|
+
return createErrorResponse(403, "First user already exists");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Create user with admin API (bypasses signup restrictions)
|
|
18
|
+
const { data, error: userError } = await supabaseAdmin.auth.admin.createUser({
|
|
19
|
+
email,
|
|
20
|
+
password,
|
|
21
|
+
email_confirm: true, // Auto-confirm first user
|
|
22
|
+
user_metadata: { first_name, last_name },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (!data?.user || userError) {
|
|
26
|
+
console.error("Error creating first user:", userError);
|
|
27
|
+
return createErrorResponse(500, "Failed to create first user");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// The database trigger will create the sales record and set administrator = true for first user
|
|
31
|
+
|
|
32
|
+
return new Response(
|
|
33
|
+
JSON.stringify({
|
|
34
|
+
data: {
|
|
35
|
+
id: data.user.id,
|
|
36
|
+
email: data.user.email,
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
{
|
|
40
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Deno.serve(async (req: Request) => {
|
|
46
|
+
if (req.method === "OPTIONS") {
|
|
47
|
+
return new Response(null, {
|
|
48
|
+
status: 204,
|
|
49
|
+
headers: corsHeaders,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (req.method === "POST") {
|
|
54
|
+
return createFirstUser(req);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return createErrorResponse(405, "Method Not Allowed");
|
|
58
|
+
});
|
|
@@ -42,32 +42,35 @@ async function updateSaleAvatar(user_id: string, avatar: string) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
async function inviteUser(req: Request, currentUserSale: any) {
|
|
45
|
-
const { email,
|
|
45
|
+
const { email, first_name, last_name, disabled, administrator } =
|
|
46
46
|
await req.json();
|
|
47
47
|
|
|
48
48
|
if (!currentUserSale.administrator) {
|
|
49
49
|
return createErrorResponse(401, "Not Authorized");
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
});
|
|
52
|
+
// Create user using inviteUserByEmail which:
|
|
53
|
+
// 1. Creates the user account
|
|
54
|
+
// 2. Sends the "Invite user" email template (not "Confirm signup")
|
|
55
|
+
// 3. User will use OTP to log in (not the invite link)
|
|
56
|
+
console.log(`[inviteUser] Creating user and sending welcome email to ${email}`);
|
|
57
57
|
|
|
58
|
-
const { error:
|
|
59
|
-
|
|
58
|
+
const { data, error: inviteError } = await supabaseAdmin.auth.admin.inviteUserByEmail(email, {
|
|
59
|
+
data: { first_name, last_name }, // User metadata
|
|
60
|
+
});
|
|
60
61
|
|
|
61
|
-
if (
|
|
62
|
-
console.error(`Error
|
|
63
|
-
return createErrorResponse(500,
|
|
62
|
+
if (inviteError) {
|
|
63
|
+
console.error(`[inviteUser] Error: ${inviteError.message}`);
|
|
64
|
+
return createErrorResponse(500, `Failed to invite user: ${inviteError.message}`);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
if (!data?.user
|
|
67
|
-
console.error(`
|
|
68
|
-
return createErrorResponse(500, "Failed to
|
|
67
|
+
if (!data?.user) {
|
|
68
|
+
console.error(`[inviteUser] No user returned from invite`);
|
|
69
|
+
return createErrorResponse(500, "Failed to create user");
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
console.log(`[inviteUser] User created and welcome email sent to ${email}`);
|
|
73
|
+
|
|
71
74
|
try {
|
|
72
75
|
await updateSaleDisabled(data.user.id, disabled);
|
|
73
76
|
const sale = await updateSaleAdministrator(data.user.id, administrator);
|
|
@@ -86,6 +89,57 @@ async function inviteUser(req: Request, currentUserSale: any) {
|
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
async function resendInvite(req: Request, currentUserSale: any) {
|
|
93
|
+
try {
|
|
94
|
+
const { sales_id, action } = await req.json();
|
|
95
|
+
console.log("[resendInvite] Request:", { sales_id, action });
|
|
96
|
+
|
|
97
|
+
if (!currentUserSale.administrator) {
|
|
98
|
+
return createErrorResponse(401, "Not Authorized");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const { data: sale } = await supabaseAdmin
|
|
102
|
+
.from("sales")
|
|
103
|
+
.select("*")
|
|
104
|
+
.eq("id", sales_id)
|
|
105
|
+
.single();
|
|
106
|
+
|
|
107
|
+
if (!sale) {
|
|
108
|
+
return createErrorResponse(404, "User not found");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Get user from auth
|
|
112
|
+
const { data: authUser, error: getUserError } =
|
|
113
|
+
await supabaseAdmin.auth.admin.getUserById(sale.user_id);
|
|
114
|
+
|
|
115
|
+
if (!authUser?.user || getUserError) {
|
|
116
|
+
console.error("Error getting user:", getUserError);
|
|
117
|
+
return createErrorResponse(404, "User not found");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log("[resendInvite] User email:", authUser.user.email, "Action:", action);
|
|
121
|
+
|
|
122
|
+
// NOTE: With OTP authentication, users don't need invite/reset emails
|
|
123
|
+
// Instead, instruct them to:
|
|
124
|
+
// - For new users: Use "Login with email code (OTP)" to set up their account
|
|
125
|
+
// - For password reset: Use "Forgot Password?" which sends OTP code
|
|
126
|
+
|
|
127
|
+
// Return success - admin should manually notify user to use OTP login
|
|
128
|
+
console.log("[resendInvite] OTP-based flow - no email sent");
|
|
129
|
+
return new Response(
|
|
130
|
+
JSON.stringify({
|
|
131
|
+
data: sale,
|
|
132
|
+
}),
|
|
133
|
+
{
|
|
134
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error("[resendInvite] Unexpected error:", err);
|
|
139
|
+
return createErrorResponse(500, `Internal error: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
89
143
|
async function patchUser(req: Request, currentUserSale: any) {
|
|
90
144
|
const {
|
|
91
145
|
sales_id,
|
|
@@ -202,5 +256,9 @@ Deno.serve(async (req: Request) => {
|
|
|
202
256
|
return patchUser(req, currentUserSale.data);
|
|
203
257
|
}
|
|
204
258
|
|
|
259
|
+
if (req.method === "PUT") {
|
|
260
|
+
return resendInvite(req, currentUserSale.data);
|
|
261
|
+
}
|
|
262
|
+
|
|
205
263
|
return createErrorResponse(405, "Method Not Allowed");
|
|
206
264
|
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
-- Add email_confirmed_at column to sales table
|
|
2
|
+
ALTER TABLE sales ADD COLUMN email_confirmed_at timestamp with time zone;
|
|
3
|
+
|
|
4
|
+
-- Update existing records with current confirmation status
|
|
5
|
+
UPDATE sales
|
|
6
|
+
SET email_confirmed_at = auth.users.email_confirmed_at
|
|
7
|
+
FROM auth.users
|
|
8
|
+
WHERE sales.user_id = auth.users.id;
|
|
9
|
+
|
|
10
|
+
-- Update the handle_new_user trigger to include email_confirmed_at
|
|
11
|
+
CREATE OR REPLACE FUNCTION handle_new_user()
|
|
12
|
+
RETURNS trigger
|
|
13
|
+
LANGUAGE plpgsql
|
|
14
|
+
SECURITY DEFINER SET search_path = ''
|
|
15
|
+
AS $$
|
|
16
|
+
DECLARE
|
|
17
|
+
sales_count integer;
|
|
18
|
+
BEGIN
|
|
19
|
+
-- Count existing sales records
|
|
20
|
+
SELECT COUNT(*) INTO sales_count FROM public.sales;
|
|
21
|
+
|
|
22
|
+
INSERT INTO public.sales (user_id, email, first_name, last_name, administrator, email_confirmed_at)
|
|
23
|
+
VALUES (
|
|
24
|
+
new.id,
|
|
25
|
+
new.email,
|
|
26
|
+
COALESCE(new.raw_user_meta_data->>'first_name', ''),
|
|
27
|
+
COALESCE(new.raw_user_meta_data->>'last_name', ''),
|
|
28
|
+
-- First user is administrator, others are not
|
|
29
|
+
CASE WHEN sales_count = 0 THEN true ELSE false END,
|
|
30
|
+
new.email_confirmed_at
|
|
31
|
+
);
|
|
32
|
+
RETURN new;
|
|
33
|
+
END;
|
|
34
|
+
$$;
|
|
35
|
+
|
|
36
|
+
-- Update the handle_update_user trigger to include email_confirmed_at
|
|
37
|
+
CREATE OR REPLACE FUNCTION handle_update_user()
|
|
38
|
+
RETURNS trigger
|
|
39
|
+
LANGUAGE plpgsql
|
|
40
|
+
SECURITY DEFINER SET search_path = ''
|
|
41
|
+
AS $$
|
|
42
|
+
BEGIN
|
|
43
|
+
UPDATE public.sales
|
|
44
|
+
SET
|
|
45
|
+
email = new.email,
|
|
46
|
+
first_name = COALESCE(new.raw_user_meta_data->>'first_name', first_name),
|
|
47
|
+
last_name = COALESCE(new.raw_user_meta_data->>'last_name', last_name),
|
|
48
|
+
email_confirmed_at = new.email_confirmed_at
|
|
49
|
+
WHERE user_id = new.id;
|
|
50
|
+
RETURN new;
|
|
51
|
+
END;
|
|
52
|
+
$$;
|