-
-
Notifications
You must be signed in to change notification settings - Fork 39
feat: create and update recipients in firebase auth store #1321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
a30ac8d
50f8694
cc6841f
527073a
f89a80e
51a38cf
1b7e9cf
b300f10
145273a
ff962a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /* | ||
| Warnings: | ||
| - Made the column `phone_id` on table `payment_information` required. This step will fail if there are existing NULL values in that column. | ||
| */ | ||
| -- DropForeignKey | ||
| ALTER TABLE "payment_information" DROP CONSTRAINT "payment_information_phone_id_fkey"; | ||
|
|
||
| -- AlterTable | ||
| ALTER TABLE "payment_information" ALTER COLUMN "phone_id" SET NOT NULL; | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "payment_information" ADD CONSTRAINT "payment_information_phone_id_fkey" FOREIGN KEY ("phone_id") REFERENCES "phone"("id") ON DELETE RESTRICT ON UPDATE CASCADE; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,5 @@ | ||||||||||||||||||||||||||||||||||||||||||
| import { ProgramPermission, Recipient, RecipientStatus } from '@prisma/client'; | ||||||||||||||||||||||||||||||||||||||||||
| import { FirebaseService } from '@socialincome/shared/src/firebase/services/auth.service'; | ||||||||||||||||||||||||||||||||||||||||||
| import { BaseService } from '../core/base.service'; | ||||||||||||||||||||||||||||||||||||||||||
| import { ServiceResult } from '../core/base.types'; | ||||||||||||||||||||||||||||||||||||||||||
| import { ProgramAccessService } from '../program-access/program-access.service'; | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -12,6 +13,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export class RecipientService extends BaseService { | ||||||||||||||||||||||||||||||||||||||||||
| private programAccessService = new ProgramAccessService(); | ||||||||||||||||||||||||||||||||||||||||||
| private firebaseAuthService = new FirebaseService(); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async create(userId: string, recipient: RecipientCreateInput): Promise<ServiceResult<Recipient>> { | ||||||||||||||||||||||||||||||||||||||||||
| const accessResult = await this.programAccessService.getAccessiblePrograms(userId); | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -27,6 +29,12 @@ export class RecipientService extends BaseService { | |||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| const phoneNumber = recipient.paymentInformation?.create?.phone?.create?.number; | ||||||||||||||||||||||||||||||||||||||||||
| if (!phoneNumber) { | ||||||||||||||||||||||||||||||||||||||||||
| return this.resultFail('No phone number provided for recipient creation'); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| await this.firebaseAuthService.createByPhoneNumber(phoneNumber); | ||||||||||||||||||||||||||||||||||||||||||
| const newRecipient = await this.db.recipient.create({ data: recipient }); | ||||||||||||||||||||||||||||||||||||||||||
| return this.resultOk(newRecipient); | ||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -44,7 +52,7 @@ export class RecipientService extends BaseService { | |||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const existing = await this.db.recipient.findUnique({ | ||||||||||||||||||||||||||||||||||||||||||
| where: { id: recipient.id?.toString() }, | ||||||||||||||||||||||||||||||||||||||||||
| select: { programId: true }, | ||||||||||||||||||||||||||||||||||||||||||
| select: { programId: true, paymentInformation: { select: { phone: { select: { number: true } } } } }, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!existing) { | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -58,6 +66,15 @@ export class RecipientService extends BaseService { | |||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| const phoneNumber = | ||||||||||||||||||||||||||||||||||||||||||
| recipient.paymentInformation?.upsert?.update?.phone?.upsert?.update.number?.toString() || | ||||||||||||||||||||||||||||||||||||||||||
| recipient.paymentInformation?.upsert?.create?.phone?.create?.number?.toString(); | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+70
to
+72
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unnecessary Phone numbers are already strings. Calling const phoneNumber =
- recipient.paymentInformation?.upsert?.update?.phone?.upsert?.update.number?.toString() ||
- recipient.paymentInformation?.upsert?.create?.phone?.create?.number?.toString();
+ recipient.paymentInformation?.upsert?.update?.phone?.upsert?.update.number ||
+ recipient.paymentInformation?.upsert?.create?.phone?.create?.number;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!phoneNumber || !existing.paymentInformation?.phone?.number) { | ||||||||||||||||||||||||||||||||||||||||||
| return this.resultFail('No phone number available for recipient update'); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| await this.firebaseAuthService.updateByPhoneNumber(existing.paymentInformation?.phone.number, phoneNumber); | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+70
to
+78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocker: allow recipient updates when phone stays untouched.
- const phoneNumber =
- recipient.paymentInformation?.upsert?.update?.phone?.upsert?.update.number?.toString() ||
- recipient.paymentInformation?.upsert?.create?.phone?.create?.number?.toString();
-
- if (!phoneNumber || !existing.paymentInformation?.phone?.number) {
- return this.resultFail('No phone number available for recipient update');
- }
-
- await this.firebaseAuthService.updateByPhoneNumber(existing.paymentInformation?.phone.number, phoneNumber);
+ const newPhoneNumber =
+ recipient.paymentInformation?.upsert?.update?.phone?.upsert?.update.number ||
+ recipient.paymentInformation?.upsert?.create?.phone?.create?.number;
+ const existingPhoneNumber = existing.paymentInformation?.phone?.number;
+
+ if (newPhoneNumber) {
+ if (!existingPhoneNumber) {
+ return this.resultFail('No phone number available for recipient update');
+ }
+ await this.firebaseAuthService.updateByPhoneNumber(existingPhoneNumber, newPhoneNumber);
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| const updatedRecipient = await this.db.recipient.update({ | ||||||||||||||||||||||||||||||||||||||||||
| where: { id: recipient.id?.toString() }, | ||||||||||||||||||||||||||||||||||||||||||
| data: recipient, | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { getOrInitializeFirebaseAdmin } from '@socialincome/shared/src/firebase/admin/app'; | ||
| import { AuthAdmin } from '@socialincome/shared/src/firebase/admin/AuthAdmin'; | ||
| import { credential } from 'firebase-admin'; | ||
|
|
||
| const { FIREBASE_SERVICE_ACCOUNT_JSON, FIREBASE_DATABASE_URL } = process.env; | ||
|
|
||
| // Firebase service account JSON is Base64-encoded to avoid multiline GitHub Secrets issues in Docker builds | ||
| const credentials = | ||
| FIREBASE_SERVICE_ACCOUNT_JSON && FIREBASE_DATABASE_URL | ||
| ? { | ||
| credential: credential.cert(JSON.parse(Buffer.from(FIREBASE_SERVICE_ACCOUNT_JSON, 'base64').toString('utf-8'))), | ||
| databaseURL: FIREBASE_DATABASE_URL, | ||
| } | ||
| : undefined; | ||
|
|
||
| export class FirebaseService { | ||
| private authAdmin = new AuthAdmin(getOrInitializeFirebaseAdmin(credentials)); | ||
|
|
||
| async createByPhoneNumber(phoneNumber: string) { | ||
| try { | ||
| const existingUser = await this.getByPhoneNumber(phoneNumber); | ||
| if (existingUser) { | ||
| console.log('User already exists for phone number:', phoneNumber); | ||
| return existingUser; | ||
| } | ||
| return await this.authAdmin.auth.createUser({ | ||
| phoneNumber, | ||
| }); | ||
| } catch (error) { | ||
| console.error('Error creating user by phone number:', error); | ||
| throw new Error('Could not create auth user by phone number'); | ||
| } | ||
| } | ||
|
|
||
| async updateByPhoneNumber(oldPhoneNumber: string, newPhoneNumber: string) { | ||
| try { | ||
| const existingUser = await this.getByPhoneNumber(oldPhoneNumber); | ||
| if (!existingUser) throw new Error('Auth user not found'); | ||
| return await this.authAdmin.auth.updateUser(existingUser.uid, { | ||
| phoneNumber: newPhoneNumber, | ||
| }); | ||
| } catch (error) { | ||
| console.error('Error fetching user by phone number:', error); | ||
| throw new Error('Could not update auth user by phone number'); | ||
| } | ||
| } | ||
|
|
||
| async getByPhoneNumber(phoneNumber: string) { | ||
| try { | ||
| return await this.authAdmin.auth.getUserByPhoneNumber(phoneNumber); | ||
| } catch (error: any) { | ||
| if (error?.code === 'auth/user-not-found') { | ||
| return null; | ||
| } | ||
| console.error('Error getting user by phone number:', error); | ||
| throw new Error('Auth user not found by phone number'); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -50,7 +50,7 @@ export function buildUpdateRecipientInput( | |||||||||
| upsert: { | ||||||||||
| create: { | ||||||||||
| ...basePaymentInfo, | ||||||||||
| phone: paymentInfoFields.phone.value ? { create: { number: paymentInfoFields.phone.value } } : undefined, | ||||||||||
| phone: { create: { number: paymentInfoFields.phone.value } }, | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unconditional create may pass undefined phone number. If - phone: { create: { number: paymentInfoFields.phone.value } },
+ phone: paymentInfoFields.phone.value
+ ? { create: { number: paymentInfoFields.phone.value } }
+ : undefined,📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| }, | ||||||||||
| update: { ...basePaymentInfo, phone: paymentPhoneUpdate }, | ||||||||||
| where: { id: recipient.paymentInformation?.id }, | ||||||||||
|
|
@@ -91,7 +91,7 @@ export function buildCreateRecipientInput( | |||||||||
| create: { | ||||||||||
| provider: paymentInfoFields.provider.value, | ||||||||||
| code: paymentInfoFields.code.value, | ||||||||||
| phone: paymentInfoFields.phone.value ? { create: { number: paymentInfoFields.phone.value } } : undefined, | ||||||||||
| phone: { create: { number: paymentInfoFields.phone.value } }, | ||||||||||
| }, | ||||||||||
| }, | ||||||||||
| contact: { | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Backfill
payment_information.phone_idbefore enforcing NOT NULLIf any existing
payment_informationrow still hasphone_id = NULL, thisALTER COLUMN ... SET NOT NULLwill abort the migration. Please add a data backfill (or a guard that blocks the deployment earlier) so the constraint can be applied safely.🤖 Prompt for AI Agents