add endpoint and UI for users to update their own username

This commit is contained in:
CPTN Cosmo 2026-04-18 17:26:12 +02:00
parent 21d7acd7e5
commit e153a76b77
4 changed files with 77 additions and 5 deletions

17
main.py
View file

@ -226,6 +226,23 @@ async def change_own_password(request: Request):
conn.close() conn.close()
return {"status": "ok"} return {"status": "ok"}
@app.put("/api/users/me/username", dependencies=[Depends(is_authenticated)])
async def change_own_username(request: Request):
data = await request.json()
new_username = data.get("username")
if not new_username:
raise HTTPException(status_code=400, detail="New username required")
user_id = request.session.get("user_id")
try:
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute('UPDATE users SET username = ? WHERE id = ?', (new_username, user_id))
conn.commit()
conn.close()
except sqlite3.IntegrityError:
raise HTTPException(status_code=400, detail="Username already exists")
return {"status": "ok"}
class InstanceCreate(BaseModel): class InstanceCreate(BaseModel):
name: str name: str
ip: str ip: str

View file

@ -14,6 +14,7 @@ document.addEventListener('DOMContentLoaded', () => {
const usersList = document.getElementById('users-list'); const usersList = document.getElementById('users-list');
const createUserForm = document.getElementById('create-user-form'); const createUserForm = document.getElementById('create-user-form');
const changePasswordForm = document.getElementById('change-password-form'); const changePasswordForm = document.getElementById('change-password-form');
const changeUsernameForm = document.getElementById('change-username-form');
const profileSuccess = document.getElementById('profile-success'); const profileSuccess = document.getElementById('profile-success');
const helpBtn = document.getElementById('help-btn'); const helpBtn = document.getElementById('help-btn');
const helpModal = document.getElementById('help-modal'); const helpModal = document.getElementById('help-modal');
@ -265,6 +266,7 @@ document.addEventListener('DOMContentLoaded', () => {
} else { } else {
navAdmin.style.display = 'none'; navAdmin.style.display = 'none';
} }
document.getElementById('profile-username').value = currentUser.username;
}; };
logoutBtn.addEventListener('click', async () => { logoutBtn.addEventListener('click', async () => {
@ -277,6 +279,28 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
changeUsernameForm.addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('profile-username').value;
try {
const res = await fetch('/api/users/me/username', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username })
});
if (res.ok) {
currentUser.username = username;
profileSuccess.style.display = 'block';
setTimeout(() => profileSuccess.style.display = 'none', 3000);
} else {
const data = await res.json();
alert(data.detail || 'Failed to update username');
}
} catch (e) {
console.error('Error changing username', e);
}
});
createUserForm.addEventListener('submit', async (e) => { createUserForm.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
const username = document.getElementById('new-username').value; const username = document.getElementById('new-username').value;

View file

@ -84,15 +84,26 @@
<div id="profile-section" class="tab-content"> <div id="profile-section" class="tab-content">
<div class="card profile-card"> <div class="card profile-card">
<h2>My Profile</h2> <h2>My Profile</h2>
<p class="helper-text">Change your account password below.</p> <p class="helper-text">Update your account details below.</p>
<form id="change-password-form">
<form id="change-username-form" class="profile-form">
<div class="form-group"> <div class="form-group">
<label for="profile-new-password">New Password</label> <label for="profile-username">Username</label>
<input type="text" id="profile-username" placeholder="Enter new username" required>
</div>
<button type="submit" class="btn btn-primary">Update Username</button>
</form>
<div class="divider"></div>
<form id="change-password-form" class="profile-form">
<div class="form-group">
<label for="profile-new-password">Password</label>
<input type="password" id="profile-new-password" placeholder="Enter new password" required> <input type="password" id="profile-new-password" placeholder="Enter new password" required>
</div> </div>
<button type="submit" class="btn btn-primary">Update Password</button> <button type="submit" class="btn btn-primary">Update Password</button>
</form> </form>
<div id="profile-success" class="success-msg" style="display: none;">Password updated successfully!</div> <div id="profile-success" class="success-msg" style="display: none;">Profile updated successfully!</div>
</div> </div>
</div> </div>
</main> </main>

View file

@ -197,8 +197,11 @@ input:focus {
.btn-primary { .btn-primary {
background-color: var(--primary); background-color: var(--primary);
color: white; color: white;
}
.login-card .btn-primary {
width: 100%; width: 100%;
margin-top: 0.5rem; margin-top: 1rem;
} }
.btn-primary:hover { .btn-primary:hover {
@ -440,6 +443,23 @@ input:focus {
color: var(--text-muted); color: var(--text-muted);
} }
.profile-form {
display: flex;
flex-direction: column;
gap: 1.25rem;
max-width: 400px;
}
.profile-form .btn-primary {
align-self: flex-start;
}
.divider {
height: 1px;
background-color: var(--border-color);
margin: 2rem 0;
}
.success-msg { .success-msg {
color: var(--status-online); color: var(--status-online);
text-align: center; text-align: center;