User profile, activity editing, search, OSM links, moderator role,
opt-in /<username>/list, and a feedback channel Six related features that touch the user model and activity UX: 1. **User profile** (display_name, username, public_list_enabled). New `display_name`, `username` (UNIQUE, slug-shaped), and `public_list_enabled` columns. PATCH /api/auth/profile is a partial update — pass only the fields you want to change, null to clear. MeResponse exposes all three. Display name is shown on public activities and in the nav; falls back to the email prefix when unset. 2. **Change password from the profile editor.** Existing /api/auth/password endpoint surfaced in the new Profile.svelte; the local-decrypt failure path on a wrong current password is mapped to a clean error. 3. **Edit existing activities.** ActivityForm becomes dual-purpose (create or edit). Title, tags, date/time, location, and visibility are all editable. Visibility transitions decrypt or re-encrypt client-side as needed before PATCH, and the IndexedDB private-tag index is kept in sync diff-style. 4. **Search.** A search input on Home filters across visible activities. Private rows are searched against their decrypted cleartext (decrypted once and memoised via $derived, so the work is amortised across keystrokes). Matches across title, tags, location label, and (for public) author display name. 5. **OpenStreetMap links.** Each row with a location renders the label as an OSM link. Smart: coords if present (?mlat=&mlon=&map=15/lat/lng → pinned view), else /search?query=. Built with the WHATWG URL constructor so Norwegian characters and commas survive. 6. **Moderator role + semi-delete by owner.** New is_moderator column on users. Owners always delete their own rows; moderators can additionally delete any semi or public activity (private is excluded — it's invisible to others, so there's no moderation case). README documents the manual promotion via sqlite3. 7. **Opt-in /<username>/list.** New server route /api/users/:username/list returns the user's public activities when both `username` is set AND `public_list_enabled = 1`. 404 when either condition fails — same response in both cases so the route doesn't leak username existence for users who haven't opted in. SPA-side, App.svelte parses window.location.pathname on mount; falls back to "/" via history.replaceState after authenticating from a deep link. 8. **Feedback channel.** New `feedback` table. POST /api/feedback for any authenticated user; GET /api/feedback gated to moderators. The Feedback.svelte component is dual-mode — the form is universal; the list view auto-loads only for moderators. Submitter identity (email + display name) is shown to moderators so they can follow up; not exposed to the submitter themselves. Schema migrations land via the existing ensureColumn() helper so scaffold DBs upgrade cleanly. The username UNIQUE constraint is applied as a partial unique index (WHERE username IS NOT NULL) so multiple users with NULL usernames don't collide. All 26 existing tests still pass; typecheck clean for both tsconfigs; Vite build succeeds.
This commit is contained in:
parent
add76be486
commit
6f4c11c7a6
16 changed files with 1152 additions and 107 deletions
|
|
@ -70,13 +70,53 @@ export interface RecoveryCompleteRequest {
|
|||
export interface MeResponse {
|
||||
id: string;
|
||||
email: string;
|
||||
display_name: string | null;
|
||||
is_moderator: boolean;
|
||||
username: string | null;
|
||||
public_list_enabled: boolean;
|
||||
}
|
||||
|
||||
export interface ProfileUpdateRequest {
|
||||
// All optional — omit a field to leave it alone. Pass `null` to clear.
|
||||
display_name?: string | null;
|
||||
username?: string | null;
|
||||
public_list_enabled?: boolean;
|
||||
}
|
||||
|
||||
/** Response shape for GET /api/users/:username/list (opt-in public list). */
|
||||
export interface PublicListResponse {
|
||||
username: string;
|
||||
display_name: string | null;
|
||||
activities: ActivityPublic[];
|
||||
}
|
||||
|
||||
// --- Feedback ---------------------------------------------------------------
|
||||
export type FeedbackKind = 'feature' | 'bug';
|
||||
|
||||
export interface FeedbackSubmitRequest {
|
||||
kind: FeedbackKind;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface FeedbackEntry {
|
||||
id: string;
|
||||
kind: FeedbackKind;
|
||||
body: string;
|
||||
created_at: number;
|
||||
// Moderator-only fields; included when the caller is a moderator viewing
|
||||
// the list. (The submit endpoint doesn't return these — a submitter doesn't
|
||||
// need to see them.)
|
||||
user_id?: string;
|
||||
user_email?: string;
|
||||
user_display?: string | null;
|
||||
}
|
||||
|
||||
// --- Activities --------------------------------------------------------------
|
||||
export interface ActivityPublic {
|
||||
id: string;
|
||||
visibility: 'public';
|
||||
owner_id: string; // serialized for public
|
||||
owner_id: string; // serialized for public
|
||||
owner_display: string; // display_name OR derived handle (email prefix)
|
||||
title: string;
|
||||
tags: string[];
|
||||
loc_label: string | null;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue