Add name field for places; show place ID in API key section

This commit is contained in:
copilot-swe-agent[bot] 2026-07-02 16:05:28 +00:00 committed by GitHub
parent 689f2b5a6e
commit 8287b7ed6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 50 additions and 6 deletions

View file

@ -0,0 +1 @@
ALTER TABLE places ADD COLUMN name TEXT;

View file

@ -19,6 +19,8 @@
<div class="card" style="margin-top: 16px;"> <div class="card" style="margin-top: 16px;">
<h3 style="margin-top:0;font-size:14px;">Add place</h3> <h3 style="margin-top:0;font-size:14px;">Add place</h3>
<form id="add-place-form"> <form id="add-place-form">
<label for="place-name">Name <span style="color:var(--muted);font-weight:normal;">(optional)</span></label>
<input type="text" id="place-name">
<div id="add-place-custom-fields" class="hidden"> <div id="add-place-custom-fields" class="hidden">
<label for="place-id">Place ID <span style="color:var(--muted);font-weight:normal;">(leave blank to auto-generate)</span></label> <label for="place-id">Place ID <span style="color:var(--muted);font-weight:normal;">(leave blank to auto-generate)</span></label>
<input type="text" id="place-id"> <input type="text" id="place-id">
@ -41,9 +43,24 @@
<button class="danger" id="delete-place-btn">Delete place</button> <button class="danger" id="delete-place-btn">Delete place</button>
</div> </div>
<div class="card">
<h3>Name</h3>
<form id="set-place-name-form" class="inline-form">
<div class="field-group">
<label for="place-name-custom">Place name <span style="color:var(--muted);font-weight:normal;">(leave blank to show the place ID instead)</span></label>
<input type="text" id="place-name-custom">
</div>
<button type="submit" class="primary">Save</button>
</form>
</div>
<div class="card"> <div class="card">
<h3>API Key</h3> <h3>API Key</h3>
<div class="inline-form"> <div class="inline-form">
<div class="field-group">
<label for="place-id-display">Place ID</label>
<input type="text" id="place-id-display" readonly>
</div>
<div class="field-group"> <div class="field-group">
<label for="place-api-key-display">Current key</label> <label for="place-api-key-display">Current key</label>
<input type="password" id="place-api-key-display" readonly> <input type="password" id="place-api-key-display" readonly>

View file

@ -56,7 +56,7 @@ function renderPlaceList() {
const idSpan = document.createElement('span'); const idSpan = document.createElement('span');
idSpan.className = 'place-list-id'; idSpan.className = 'place-list-id';
idSpan.textContent = place.id; idSpan.textContent = place.name || place.id;
li.appendChild(idSpan); li.appendChild(idSpan);
if (place.ownerUsername) { if (place.ownerUsername) {
@ -82,7 +82,10 @@ async function selectPlace(placeId) {
document.getElementById('no-place').classList.add('hidden'); document.getElementById('no-place').classList.add('hidden');
document.getElementById('place-view').classList.remove('hidden'); document.getElementById('place-view').classList.remove('hidden');
document.getElementById('place-title').textContent = placeId; document.getElementById('place-title').textContent = data.place.name || placeId;
document.getElementById('place-id-display').value = placeId;
document.getElementById('place-name-custom').value = data.place.name || '';
const apiKeyInput = document.getElementById('place-api-key-display'); const apiKeyInput = document.getElementById('place-api-key-display');
apiKeyInput.value = data.place.apiKey || ''; apiKeyInput.value = data.place.apiKey || '';
@ -177,10 +180,12 @@ document.getElementById('add-place-form').addEventListener('submit', async (e) =
e.preventDefault(); e.preventDefault();
const id = document.getElementById('place-id').value.trim(); const id = document.getElementById('place-id').value.trim();
const apiKey = document.getElementById('place-api-key').value.trim(); const apiKey = document.getElementById('place-api-key').value.trim();
const name = document.getElementById('place-name').value.trim();
const body = {}; const body = {};
if (id) body.id = id; if (id) body.id = id;
if (apiKey) body.apiKey = apiKey; if (apiKey) body.apiKey = apiKey;
if (name) body.name = name;
const data = await api('/dashboard/places', { const data = await api('/dashboard/places', {
method: 'POST', method: 'POST',
@ -190,6 +195,7 @@ document.getElementById('add-place-form').addEventListener('submit', async (e) =
if (data.success) { if (data.success) {
document.getElementById('place-id').value = ''; document.getElementById('place-id').value = '';
document.getElementById('place-api-key').value = ''; document.getElementById('place-api-key').value = '';
document.getElementById('place-name').value = '';
await loadPlaces(); await loadPlaces();
selectPlace(data.place.id); selectPlace(data.place.id);
} else { } else {
@ -265,6 +271,24 @@ document.getElementById('regenerate-api-key-btn').addEventListener('click', asyn
} }
}); });
document.getElementById('set-place-name-form').addEventListener('submit', async (e) => {
e.preventDefault();
if (!currentPlaceId) return;
const name = document.getElementById('place-name-custom').value.trim();
const data = await api(`/dashboard/places/${encodeURIComponent(currentPlaceId)}`, {
method: 'PUT',
body: JSON.stringify({ name })
});
if (data.success) {
await loadPlaces();
selectPlace(currentPlaceId);
} else {
alert(data.message || 'Failed to set place name');
}
});
document.getElementById('set-api-key-form').addEventListener('submit', async (e) => { document.getElementById('set-api-key-form').addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
if (!currentPlaceId) return; if (!currentPlaceId) return;

View file

@ -97,7 +97,7 @@ router.get('/places', (req, res) => {
router.post('/places', (req, res) => { router.post('/places', (req, res) => {
const canSetCustomValues = req.user.role >= ROLES.ADMIN; const canSetCustomValues = req.user.role >= ROLES.ADMIN;
const { settings } = req.body || {}; const { settings } = req.body || {};
let { id, apiKey } = req.body || {}; let { id, apiKey, name } = req.body || {};
if (!canSetCustomValues && (id || apiKey)) { if (!canSetCustomValues && (id || apiKey)) {
return res.status(403).json({ success: false, message: "Only admins can set a custom place id or API key" }); return res.status(403).json({ success: false, message: "Only admins can set a custom place id or API key" });
@ -106,6 +106,7 @@ router.post('/places', (req, res) => {
// If we reach here, either the caller is an admin, or both id and apiKey were already falsy. // If we reach here, either the caller is an admin, or both id and apiKey were already falsy.
id = id ? String(id).trim() : ''; id = id ? String(id).trim() : '';
apiKey = apiKey ? String(apiKey).trim() : ''; apiKey = apiKey ? String(apiKey).trim() : '';
name = name ? String(name).trim() : '';
if (!id) id = generatePlaceId(); if (!id) id = generatePlaceId();
if (!apiKey) apiKey = generateApiKey(); if (!apiKey) apiKey = generateApiKey();
@ -119,12 +120,12 @@ router.post('/places', (req, res) => {
return res.status(409).json({ success: false, message: "A place with that id already exists" }); return res.status(409).json({ success: false, message: "A place with that id already exists" });
} }
db.run(`INSERT INTO places (id, apiKey, settings, ownerId) VALUES (?, ?, ?, ?)`, [id, apiKey, settings || '{}', req.user.id], (err) => { db.run(`INSERT INTO places (id, apiKey, settings, ownerId, name) VALUES (?, ?, ?, ?, ?)`, [id, apiKey, settings || '{}', req.user.id, name || null], (err) => {
if (err) { if (err) {
console.error('Failed to create place:', err.message); console.error('Failed to create place:', err.message);
return res.status(500).json({ success: false, message: "Internal server error" }); return res.status(500).json({ success: false, message: "Internal server error" });
} }
res.json({ success: true, place: { id, apiKey, settings: settings || '{}', ownerId: req.user.id } }); res.json({ success: true, place: { id, apiKey, settings: settings || '{}', ownerId: req.user.id, name: name || null } });
}); });
}); });
}); });
@ -155,8 +156,9 @@ router.put('/places/:placeId', loadPlace, (req, res) => {
apiKey = String(req.body.apiKey).trim() || generateApiKey(); apiKey = String(req.body.apiKey).trim() || generateApiKey();
} }
const settings = req.body.settings !== undefined ? req.body.settings : req.place.settings; const settings = req.body.settings !== undefined ? req.body.settings : req.place.settings;
const name = req.body.name !== undefined ? (String(req.body.name).trim() || null) : req.place.name;
db.run(`UPDATE places SET apiKey = ?, settings = ? WHERE id = ?`, [apiKey, settings, req.place.id], (err) => { db.run(`UPDATE places SET apiKey = ?, settings = ?, name = ? WHERE id = ?`, [apiKey, settings, name, req.place.id], (err) => {
if (err) { if (err) {
console.error('Failed to update place:', err.message); console.error('Failed to update place:', err.message);
return res.status(500).json({ success: false, message: "Internal server error" }); return res.status(500).json({ success: false, message: "Internal server error" });