diff --git a/app/models.py b/app/models.py index 9167319..d9aac3b 100644 --- a/app/models.py +++ b/app/models.py @@ -221,6 +221,9 @@ class CreateContactRequest(BaseModel): public_key: str = Field(min_length=64, max_length=64, description="Public key (64-char hex)") name: str | None = Field(default=None, description="Display name for the contact") + type: int = Field( + default=0, ge=0, le=3, description="Contact type (0=unknown, 1=client, 2=repeater, 3=room)" + ) try_historical: bool = Field( default=False, description="Attempt to decrypt historical DM packets for this contact", diff --git a/app/routers/contacts.py b/app/routers/contacts.py index e9609fd..00d332e 100644 --- a/app/routers/contacts.py +++ b/app/routers/contacts.py @@ -315,6 +315,7 @@ async def create_contact( contact_upsert = ContactUpsert( public_key=lower_key, name=request.name, + type=request.type, on_radio=False, ) await ContactRepository.upsert(contact_upsert) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 30dfbd1..25b7143 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -158,10 +158,10 @@ export const api = { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ public_keys: publicKeys }), }), - createContact: (publicKey: string, name?: string, tryHistorical?: boolean) => + createContact: (publicKey: string, name?: string, tryHistorical?: boolean, type?: number) => fetchJson('/contacts', { method: 'POST', - body: JSON.stringify({ public_key: publicKey, name, try_historical: tryHistorical }), + body: JSON.stringify({ public_key: publicKey, name, type, try_historical: tryHistorical }), }), markContactRead: (publicKey: string) => fetchJson<{ status: string; public_key: string }>(`/contacts/${publicKey}/mark-read`, { diff --git a/frontend/src/components/NewMessageModal.tsx b/frontend/src/components/NewMessageModal.tsx index 2914abe..2049fba 100644 --- a/frontend/src/components/NewMessageModal.tsx +++ b/frontend/src/components/NewMessageModal.tsx @@ -32,7 +32,12 @@ interface NewMessageModalProps { nonce: number; } | null; onClose: () => void; - onCreateContact: (name: string, publicKey: string, tryHistorical: boolean) => Promise; + onCreateContact: ( + name: string, + publicKey: string, + tryHistorical: boolean, + type?: number + ) => Promise; onCreateChannel: (name: string, key: string, tryHistorical: boolean) => Promise; onCreateHashtagChannel: (name: string, tryHistorical: boolean) => Promise; onBulkAddHashtagChannels: (channelNames: string[], tryHistorical: boolean) => Promise; @@ -91,6 +96,7 @@ export function NewMessageModal({ }: NewMessageModalProps) { const [tab, setTab] = useState('new-contact'); const [name, setName] = useState(''); + const [contactType, setContactType] = useState(1); const [contactKey, setContactKey] = useState(''); const [channelKey, setChannelKey] = useState(''); const [bulkChannelText, setBulkChannelText] = useState(''); @@ -103,6 +109,7 @@ export function NewMessageModal({ const resetForm = () => { setName(''); + setContactType(1); setContactKey(''); setChannelKey(''); setBulkChannelText(''); @@ -161,7 +168,7 @@ export function NewMessageModal({ setError('Name and public key are required'); return; } - await onCreateContact(name.trim(), contactKey.trim(), tryHistorical); + await onCreateContact(name.trim(), contactKey.trim(), tryHistorical, contactType); } else if (tab === 'new-channel') { if (!name.trim() || !channelKey.trim()) { setError('Channel name and key are required'); @@ -293,6 +300,19 @@ export function NewMessageModal({ placeholder="64-character hex public key" /> +
+ + +
diff --git a/frontend/src/hooks/useContactsAndChannels.ts b/frontend/src/hooks/useContactsAndChannels.ts index 3e82cf3..bc14ffc 100644 --- a/frontend/src/hooks/useContactsAndChannels.ts +++ b/frontend/src/hooks/useContactsAndChannels.ts @@ -50,8 +50,8 @@ export function useContactsAndChannels({ }, []); const handleCreateContact = useCallback( - async (name: string, publicKey: string, tryHistorical: boolean) => { - const created = await api.createContact(publicKey, name || undefined, tryHistorical); + async (name: string, publicKey: string, tryHistorical: boolean, type?: number) => { + const created = await api.createContact(publicKey, name || undefined, tryHistorical, type); const data = await fetchAllContacts(); setContacts(data); diff --git a/frontend/src/test/newMessageModal.test.tsx b/frontend/src/test/newMessageModal.test.tsx index b6ef18a..d68eeb4 100644 --- a/frontend/src/test/newMessageModal.test.tsx +++ b/frontend/src/test/newMessageModal.test.tsx @@ -172,7 +172,7 @@ describe('NewMessageModal form reset', () => { await user.click(screen.getByRole('button', { name: 'Create' })); await waitFor(() => { - expect(onCreateContact).toHaveBeenCalledWith('Bob', 'bb'.repeat(32), false); + expect(onCreateContact).toHaveBeenCalledWith('Bob', 'bb'.repeat(32), false, 1); }); expect(onClose).toHaveBeenCalled(); });