diff --git a/messages/en.json b/messages/en.json index cc9ea3b..6b0e882 100644 --- a/messages/en.json +++ b/messages/en.json @@ -21,5 +21,11 @@ "sms_label_phone": "Phone Number", "sms_label_message": "Message", "sms_button_submit": "Send Message", + "settings_title": "Settings", + "settings_category_twilio": "Twilio Config", + "settings_twilio_account_sid": "Account SID", + "settings_twilio_auth_token": "Auth Token", + "settings_twilio_phone_number": "Phone Number", + "settings_save": "Save Settings", "error_page_go_home": "Go Home" -} +} \ No newline at end of file diff --git a/src/routes/app/settings/+page.server.ts b/src/routes/app/settings/+page.server.ts new file mode 100644 index 0000000..2c60456 --- /dev/null +++ b/src/routes/app/settings/+page.server.ts @@ -0,0 +1,77 @@ +import { PhoneRegex } from '$lib/regex'; +import { encrypt } from '$lib/server/crypto/encryption.js'; +import { logger } from '$lib/server/logger'; +import { prisma } from '$lib/server/prisma'; +import { fail, type Actions } from '@sveltejs/kit'; +import zod from 'zod'; + +export const load = async (event) => { + const tenantId = event.locals.auth!.orgId!; + + const configs = await prisma.tenantConfig.findUnique({ + where: { tenantId: tenantId }, + select: { accountSID: true, authToken: true, phoneNumber: true }, + }); + + return { + configs: configs, + }; +}; + +export const actions = { + update: async (event) => { + const form = await event.request.formData(); + const tenantId = event.locals.auth!.orgId!; + + if (!form.has('accountSID')) { + return fail(400, { error: 'account_sid_missing' }); + } + if (!form.has('authToken')) { + return fail(400, { error: 'auth_token_missing' }); + } + if (!form.has('phoneNumber')) { + return fail(400, { error: 'phone_number_missing' }); + } + + const accountSID = form.get('accountSID'); + if (typeof accountSID !== 'string') { + return fail(400, { error: 'invalid_account_sid' }); + } + const authToken = form.get('authToken'); + if (typeof authToken !== 'string') { + return fail(400, { error: 'invalid_auth_token' }); + } + const { + success: phoneSuccess, + data: phoneNumber, + error: phoneError, + } = zod.string().regex(PhoneRegex).safeParse(form.get('phone')); + if (!phoneSuccess) { + logger.error(phoneError); + return fail(400, { error: 'invalid_phone_number' }); + } + + const configs = await prisma.tenantConfig.upsert({ + where: { + tenantId: tenantId, + }, + create: { + tenantId: tenantId, + accountSID: encrypt(accountSID), + authToken: encrypt(authToken), + phoneNumber: encrypt(phoneNumber), + }, + update: { + tenantId: tenantId, + accountSID: accountSID, + authToken: authToken, + phoneNumber: phoneNumber, + }, + select: { accountSID: true, authToken: true, phoneNumber: true }, + }); + + return { + configs: configs, + }; + }, +} satisfies Actions; diff --git a/src/routes/app/settings/+page.svelte b/src/routes/app/settings/+page.svelte index b637efa..e8759d6 100644 --- a/src/routes/app/settings/+page.svelte +++ b/src/routes/app/settings/+page.svelte @@ -1,4 +1,90 @@ -
+
+
+
+

{messages.settings_title()}

+ {#if form?.error} + + {/if} +
+
+
+ +

{messages.settings_category_twilio()}

+ + {#snippet label()} +
+ + {messages.settings_twilio_account_sid()} +
+ {/snippet} +
+ + {#snippet label()} +
+ + {messages.settings_twilio_auth_token()} +
+ {/snippet} +
+ + {#snippet label()} +
+ + {messages.settings_twilio_phone_number()} +
+ {/snippet} +
+
+
+ +
+
+
+
+ + diff --git a/src/routes/app/sms/+page.server.ts b/src/routes/app/sms/+page.server.ts index b447710..3dc5f38 100644 --- a/src/routes/app/sms/+page.server.ts +++ b/src/routes/app/sms/+page.server.ts @@ -1,10 +1,32 @@ import { TWILIO_PHONE_NUMBER } from '$env/static/private'; import { PhoneRegex } from '$lib/regex'; import { logger } from '$lib/server/logger'; +import { prisma } from '$lib/server/prisma/index.js'; import { TwilioClient } from '$lib/server/twilio'; -import { fail, type Actions } from '@sveltejs/kit'; +import { error, fail, type Actions } from '@sveltejs/kit'; import zod from 'zod'; +export const load = async (event) => { + const tenantId = event.locals.auth!.orgId!; + + const configs = await prisma.tenantConfig.findUnique({ + where: { tenantId: tenantId }, + select: { + accountSID: true, + authToken: true, + phoneNumber: true, + }, + }); + + if (!configs || Object.keys(configs).length === 0) { + return error(500, new Error('twilio_configs_not_set')); + } + + return { + configs: configs, + }; +}; + export const actions = { push: async (event) => { const form = await event.request.formData();