Skip to content

Commit b23a4f6

Browse files
authored
Merge pull request #905 from chinecherem58/issue-822-stealth-meta-address
feat: add stealth_meta_address to user profile and profile endpoints …
2 parents 85edee8 + 7d8afcd commit b23a4f6

5 files changed

Lines changed: 86 additions & 6 deletions

File tree

backend/src/routes/user.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import express, { Response } from 'express';
88
import { supabase } from '../config/database';
99
import { authenticate, AuthenticatedRequest } from '../middleware/auth';
10+
import { validateRequest } from '../utils/validation';
11+
import { userProfileUpdateSchema } from '../schemas/user-profile';
1012
import logger from '../config/logger';
1113
import { roleService } from '../services/role-service';
1214

@@ -148,4 +150,60 @@ router.delete('/account', async (req: AuthenticatedRequest, res: Response) => {
148150
}
149151
});
150152

153+
/**
154+
* GET /api/user/profile
155+
* Returns the current user's profile data.
156+
*/
157+
router.get('/profile', async (req: AuthenticatedRequest, res: Response) => {
158+
try {
159+
const userId = req.user!.id;
160+
const { data: profile, error } = await supabase
161+
.from('profiles')
162+
.select('id, display_name, company_name, plan_type, stealth_meta_address, created_at, updated_at')
163+
.eq('id', userId)
164+
.single();
165+
166+
if (error) {
167+
logger.error('Error fetching user profile:', error);
168+
return res.status(500).json({ success: false, error: 'Failed to fetch profile' });
169+
}
170+
171+
return res.status(200).json({ success: true, data: profile });
172+
} catch (error) {
173+
logger.error('Error fetching user profile:', error);
174+
return res.status(500).json({ success: false, error: 'Failed to fetch profile' });
175+
}
176+
});
177+
178+
/**
179+
* PUT /api/user/profile
180+
* Update profile fields such as display name, company name, or stealth meta-address.
181+
*/
182+
router.put('/profile', async (req: AuthenticatedRequest, res: Response) => {
183+
try {
184+
const userId = req.user!.id;
185+
const validatedData = validateRequest(userProfileUpdateSchema, req.body);
186+
187+
const { data: profile, error } = await supabase
188+
.from('profiles')
189+
.update({
190+
...validatedData,
191+
updated_at: new Date().toISOString(),
192+
})
193+
.eq('id', userId)
194+
.select()
195+
.single();
196+
197+
if (error) {
198+
logger.error('Error updating user profile:', error);
199+
return res.status(500).json({ success: false, error: 'Failed to update profile' });
200+
}
201+
202+
return res.status(200).json({ success: true, data: profile });
203+
} catch (error) {
204+
logger.error('Error updating user profile:', error);
205+
return res.status(500).json({ success: false, error: 'Failed to update profile' });
206+
}
207+
});
208+
151209
export default router;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { z } from 'zod';
2+
3+
export const userProfileUpdateSchema = z.object({
4+
display_name: z.string().min(1, 'Display name must not be empty').max(200).optional(),
5+
company_name: z.string().max(200).optional(),
6+
stealth_meta_address: z.string().max(1024).optional(),
7+
});

backend/src/services/subscription-service.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,20 @@ export class SubscriptionService {
5050

5151
const stealthIndex = indexRow ? (indexRow.stealth_index as number) + 1 : 0;
5252

53-
// Derive stealth address when a meta-address is available
54-
const metaAddress = process.env.STEALTH_META_ADDRESS;
55-
let stealthAddress: string | null = null;
56-
if (metaAddress) {
57-
// subscriptionId is not yet known; we will update after insert
58-
// Store null initially and patch below once we have the row id
53+
// Derive stealth address when the user has a stored stealth meta-address.
54+
const { data: profile, error: profileError } = await client
55+
.from('profiles')
56+
.select('stealth_meta_address')
57+
.eq('id', userId)
58+
.single();
59+
60+
if (profileError) {
61+
throw new Error(`Failed to load user profile: ${profileError.message}`);
5962
}
6063

64+
const metaAddress = profile?.stealth_meta_address ?? null;
65+
let stealthAddress: string | null = null;
66+
6167
const { data: subscription, error: dbError } = await client
6268
.from("subscriptions")
6369
.insert({

client/scripts/001_create_users_and_profiles.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ create table if not exists public.profiles (
44
display_name text,
55
company_name text,
66
plan_type text default 'free' check (plan_type in ('free', 'pro', 'enterprise')),
7+
stealth_meta_address text,
78
created_at timestamp with time zone default now(),
89
updated_at timestamp with time zone default now()
910
);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Migration: Add stealth meta-address storage to user profiles.
2+
-- Issue: #822 - Persist per-user stealth meta-address for subscription stealth address derivation.
3+
4+
ALTER TABLE public.profiles
5+
ADD COLUMN IF NOT EXISTS stealth_meta_address TEXT;
6+
7+
COMMENT ON COLUMN public.profiles.stealth_meta_address IS
8+
'User-level stealth meta-address used to derive unique subscription stealth addresses.';

0 commit comments

Comments
 (0)