"Import repeater contacts" imports nothing whenever a contact-type filter is supplied, and reports success. On a live repeater with 285 adverts, every one of the four permitted filter values selects 0 rows.
A second, related defect imports chat contacts with the wrong adv_type even when the import does work.
Cause
adverts.contact_type stores the display name, written via handler_helpers.discovery.NODE_TYPE_NAMES:
'Repeater' 'Chat Node' 'Room Server' 'Sensor'
companion_endpoints.import_repeater_contacts validates contact_types against MeshCore's names:
and companion_import_repeater_contacts compares the two directly:
query+=f" AND contact_type IN ({placeholders})"
SQLite string comparison is case-sensitive, so "repeater" never matches "Repeater", "room_server" never matches "Room Server", and "companion" never matches "Chat Node" under any casing.
Measured on that repeater's own database, running the endpoint's exact query:
contact_types
rows selected
["companion"]
0
["repeater"]
0
["room_server"]
0
["sensor"]
0
all four
0
(no filter)
285
Second defect, same cause. The adv_type assignment normalises the stored name but looks it up in the API-name table:
normalized_type=raw_type.lower().replace(" ","_").strip()adv_type=type_map.get(normalized_type,0)# type_map has "companion", not "chat_node"
"Chat Node" → "chat_node" → absent → adv_type = 0 instead of 1. Repeater/Room Server/Sensor happen to normalise onto their API names, so only chat contacts are affected. This one is silently wrong rather than empty, and needs a manual UPDATE companion_contacts SET adv_type=1 WHERE adv_type=0 to repair — I had already done exactly that by hand before finding the cause.
Reproduce
Let the repeater collect adverts (select distinct contact_type from adverts → the display names above).
POST /api/companion/import_repeater_contacts {"companion_name": "...", "contact_types": ["repeater"]}
Response: {"imported": 0, "removed": 0}. Nothing is written; companion_contacts is unchanged.
Repeat with no contact_types → imports fine, but chat contacts land with adv_type = 0.
Fix
PR to follow. The two sites derived the mapping independently, which is how they drifted; they now share one table keyed on the normalised stored form. The filter reverse-maps the requested API names onto stored keys and compares LOWER(REPLACE(TRIM(contact_type), ' ', '_')), so filtering and limiting stay in SQL. An unrecognised type still selects nothing rather than silently widening the query.
Includes 8 tests seeded with the exact stored display names; 6 fail on current dev.
Environment: Raspberry Pi 4, Python 3.13.5, dev at efc5616.
### Summary
"Import repeater contacts" imports **nothing** whenever a contact-type filter is supplied, and reports success. On a live repeater with 285 adverts, every one of the four permitted filter values selects 0 rows.
A second, related defect imports chat contacts with the wrong `adv_type` even when the import does work.
### Cause
`adverts.contact_type` stores the **display** name, written via `handler_helpers.discovery.NODE_TYPE_NAMES`:
```
'Repeater' 'Chat Node' 'Room Server' 'Sensor'
```
`companion_endpoints.import_repeater_contacts` validates `contact_types` against **MeshCore's** names:
```python
allowed = {"companion", "repeater", "room_server", "sensor"}
```
and `companion_import_repeater_contacts` compares the two directly:
```python
query += f" AND contact_type IN ({placeholders})"
```
SQLite string comparison is case-sensitive, so `"repeater"` never matches `"Repeater"`, `"room_server"` never matches `"Room Server"`, and `"companion"` never matches `"Chat Node"` under any casing.
Measured on that repeater's own database, running the endpoint's exact query:
| `contact_types` | rows selected |
|---|---|
| `["companion"]` | 0 |
| `["repeater"]` | 0 |
| `["room_server"]` | 0 |
| `["sensor"]` | 0 |
| all four | 0 |
| *(no filter)* | **285** |
**Second defect, same cause.** The `adv_type` assignment normalises the stored name but looks it up in the API-name table:
```python
normalized_type = raw_type.lower().replace(" ", "_").strip()
adv_type = type_map.get(normalized_type, 0) # type_map has "companion", not "chat_node"
```
`"Chat Node"` → `"chat_node"` → absent → **`adv_type = 0`** instead of 1. Repeater/Room Server/Sensor happen to normalise onto their API names, so only chat contacts are affected. This one is silently wrong rather than empty, and needs a manual `UPDATE companion_contacts SET adv_type=1 WHERE adv_type=0` to repair — I had already done exactly that by hand before finding the cause.
### Reproduce
1. Let the repeater collect adverts (`select distinct contact_type from adverts` → the display names above).
2. `POST /api/companion/import_repeater_contacts {"companion_name": "...", "contact_types": ["repeater"]}`
3. Response: `{"imported": 0, "removed": 0}`. Nothing is written; `companion_contacts` is unchanged.
4. Repeat with no `contact_types` → imports fine, but chat contacts land with `adv_type = 0`.
### Fix
PR to follow. The two sites derived the mapping independently, which is how they drifted; they now share one table keyed on the normalised **stored** form. The filter reverse-maps the requested API names onto stored keys and compares `LOWER(REPLACE(TRIM(contact_type), ' ', '_'))`, so filtering and limiting stay in SQL. An unrecognised type still selects nothing rather than silently widening the query.
Includes 8 tests seeded with the exact stored display names; 6 fail on current `dev`.
Environment: Raspberry Pi 4, Python 3.13.5, `dev` at `efc5616`.
<!-- github-sync-id:5338205469 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
"Import repeater contacts" imports nothing whenever a contact-type filter is supplied, and reports success. On a live repeater with 285 adverts, every one of the four permitted filter values selects 0 rows.
A second, related defect imports chat contacts with the wrong
adv_typeeven when the import does work.Cause
adverts.contact_typestores the display name, written viahandler_helpers.discovery.NODE_TYPE_NAMES:companion_endpoints.import_repeater_contactsvalidatescontact_typesagainst MeshCore's names:and
companion_import_repeater_contactscompares the two directly:SQLite string comparison is case-sensitive, so
"repeater"never matches"Repeater","room_server"never matches"Room Server", and"companion"never matches"Chat Node"under any casing.Measured on that repeater's own database, running the endpoint's exact query:
contact_types["companion"]["repeater"]["room_server"]["sensor"]Second defect, same cause. The
adv_typeassignment normalises the stored name but looks it up in the API-name table:"Chat Node"→"chat_node"→ absent →adv_type = 0instead of 1. Repeater/Room Server/Sensor happen to normalise onto their API names, so only chat contacts are affected. This one is silently wrong rather than empty, and needs a manualUPDATE companion_contacts SET adv_type=1 WHERE adv_type=0to repair — I had already done exactly that by hand before finding the cause.Reproduce
select distinct contact_type from adverts→ the display names above).POST /api/companion/import_repeater_contacts {"companion_name": "...", "contact_types": ["repeater"]}{"imported": 0, "removed": 0}. Nothing is written;companion_contactsis unchanged.contact_types→ imports fine, but chat contacts land withadv_type = 0.Fix
PR to follow. The two sites derived the mapping independently, which is how they drifted; they now share one table keyed on the normalised stored form. The filter reverse-maps the requested API names onto stored keys and compares
LOWER(REPLACE(TRIM(contact_type), ' ', '_')), so filtering and limiting stay in SQL. An unrecognised type still selects nothing rather than silently widening the query.Includes 8 tests seeded with the exact stored display names; 6 fail on current
dev.Environment: Raspberry Pi 4, Python 3.13.5,
devatefc5616.Merged fix in dev