Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ func main() {
app.OnRecordUpdateRequest("cps").BindFunc(validateCharactersOwnership)

app.OnRecordCreateRequest().BindFunc(validateIdImmutable)
app.OnRecordUpdateRequest().BindFunc(validateIdImmutable)

Comment thread
BretRen marked this conversation as resolved.
app.OnRecordCreateRequest().BindFunc(restrictToSuperuserOrAuth)
app.OnRecordUpdateRequest().BindFunc(restrictToSuperuserOrAuth)
Expand Down
6 changes: 5 additions & 1 deletion website/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import pb from '$lib/pocketbase';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';

onMount(() => {
if (pb.authStore.isValid) {
Expand Down Expand Up @@ -38,7 +39,7 @@
</span>
</div>
</button>
<ul class="dropdown-content menu z-1 mt-3 w-52 menu-sm rounded-box bg-base-300 p-2 shadow">
<ul class="dropdown-content menu z-1 mt-3 w-52 menu-sm rounded-box bg-base-300 p-2 shadow flex flex-col gap-2">
<li>
<button
onclick={() => {
Expand All @@ -48,6 +49,9 @@
class="w-full text-left text-error">Logout</button
>
</li>
<li>
<button onclick={() => goto("/mystuff")}>My Stuff</button>
</li>
</ul>
</div>
{:else}
Expand Down
90 changes: 90 additions & 0 deletions website/src/routes/mystuff/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<script lang="ts">
import pb from '$lib/pocketbase';
import { goto } from '$app/navigation';

type ViewMode = 'cps' | 'characters';
let mode = $state<ViewMode>('cps');
let items = $state<any[]>([]);
let isLoading = $state(true);

async function fetchMyData(currentMode: ViewMode) {
// 安全守卫:如果没有登录,直接跳走
if (!pb.authStore.isValid || !pb.authStore.record) {
goto('/login');
return;
}
Comment thread
BretRen marked this conversation as resolved.

isLoading = true;
const userId = pb.authStore.record.id;

try {
const collection = currentMode === 'cps' ? 'cps' : 'characters';
const options = {
// 关键点:只获取 owner 是当前用户的记录
filter: `owner = "${userId}"`,
Comment thread
BretRen marked this conversation as resolved.
sort: '-created',
expand: currentMode === 'cps' ? 'characters' : ''
};

items = await pb.collection(collection).getFullList(options);
} catch (err) {
console.error("Fetch error:", err);
} finally {
isLoading = false;
Comment on lines +29 to +33
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard against stale responses when switching tabs.

Line 39 can start overlapping requests; a slower previous response can still assign items after mode changed, rendering CPs under the Characters tab or vice versa. Errors also keep the previous items visible.

🐛 Proposed fix to ignore stale loads
     let mode = $state<ViewMode>('cps');
     let items = $state<any[]>([]);
     let isLoading = $state(true);
+    let fetchVersion = 0;
 
     async function fetchMyData(currentMode: ViewMode) {
+        const version = ++fetchVersion;
+
         // 安全守卫:如果没有登录,直接跳走
         if (!pb.authStore.isValid || !pb.authStore.record) {
-            goto('/login');
+            isLoading = false;
+            await goto('/login');
             return;
         }
 
         isLoading = true;
         const userId = pb.authStore.record.id;
@@
-            items = await pb.collection(collection).getFullList(options);
+            const nextItems = await pb.collection(collection).getFullList(options);
+            if (version === fetchVersion) {
+                items = nextItems;
+            }
         } catch (err) {
             console.error("Fetch error:", err);
+            if (version === fetchVersion) {
+                items = [];
+            }
         } finally {
-            isLoading = false;
+            if (version === fetchVersion) {
+                isLoading = false;
+            }
         }
     }

Also applies to: 37-40

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@website/src/routes/mystuff/`+page.svelte around lines 29 - 33, Guard against
stale responses by tracking each load with a request id and only updating state
if the response corresponds to the most recent load: add a component-scoped
counter (e.g., currentLoadId) and, inside the function before calling
pb.collection(...).getFullList, capture const myLoadId = ++currentLoadId and
const myMode = mode; after the await (and in the catch), only assign items,
clear items on error, or set isLoading = false if myLoadId === currentLoadId and
mode === myMode; otherwise ignore the result so a slower previous response
cannot overwrite items or isLoading for a different tab. Ensure references to
items, mode, and isLoading are guarded by that check.

}
}

// 监听 mode 变化自动刷新
$effect(() => {
fetchMyData(mode);
});
</script>

<div class="mx-auto max-w-6xl px-6 py-16">
<div class="mb-12">
<h1 class="text-4xl font-bold md:text-5xl">My Stuff</h1>
<p class="text-base-content/60 mt-2">Manage the CPs and Characters you've created.</p>

<div role="tablist" class="tabs tabs-boxed mt-6 w-fit">
<button
role="tab"
class="tab {mode === 'cps' ? 'tab-active' : ''}"
onclick={() => mode = 'cps'}
>My CPs</button>
<button
role="tab"
class="tab {mode === 'characters' ? 'tab-active' : ''}"
onclick={() => mode = 'characters'}
>My Characters</button>
Comment on lines +48 to +58
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Expose the active tab state to assistive technology.

These buttons declare role="tab", but the selected state is only represented by CSS. Add aria-selected so screen readers can identify the active tab.

♿ Proposed tab accessibility fix
             <button 
+                type="button"
                 role="tab" 
+                aria-selected={mode === 'cps'}
                 class="tab {mode === 'cps' ? 'tab-active' : ''}" 
                 onclick={() => mode = 'cps'}
             >My CPs</button>
             <button 
+                type="button"
                 role="tab" 
+                aria-selected={mode === 'characters'}
                 class="tab {mode === 'characters' ? 'tab-active' : ''}" 
                 onclick={() => mode = 'characters'}
             >My Characters</button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div role="tablist" class="tabs tabs-boxed mt-6 w-fit">
<button
role="tab"
class="tab {mode === 'cps' ? 'tab-active' : ''}"
onclick={() => mode = 'cps'}
>My CPs</button>
<button
role="tab"
class="tab {mode === 'characters' ? 'tab-active' : ''}"
onclick={() => mode = 'characters'}
>My Characters</button>
<div role="tablist" class="tabs tabs-boxed mt-6 w-fit">
<button
type="button"
role="tab"
aria-selected={mode === 'cps'}
class="tab {mode === 'cps' ? 'tab-active' : ''}"
onclick={() => mode = 'cps'}
>My CPs</button>
<button
type="button"
role="tab"
aria-selected={mode === 'characters'}
class="tab {mode === 'characters' ? 'tab-active' : ''}"
onclick={() => mode = 'characters'}
>My Characters</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@website/src/routes/mystuff/`+page.svelte around lines 48 - 58, The tab
buttons in +page.svelte use role="tab" but don't expose selection to AT; update
both button elements (the ones using class "tab {mode === 'cps' ? 'tab-active' :
''}" and "tab {mode === 'characters' ? 'tab-active' : ''}") to include
aria-selected attributes such that aria-selected="true" when mode === 'cps' (for
the My CPs button) or mode === 'characters' (for the My Characters button) and
aria-selected="false" otherwise; keep the existing onclick handlers (onclick={()
=> mode = 'cps'} / onclick={() => mode = 'characters'}) and class logic
unchanged so visual and programmatic state remain synchronized.

</div>
</div>

{#if isLoading}
<div class="flex justify-center py-20">
<span class="loading loading-spinner loading-lg text-primary"></span>
</div>
{:else if items.length === 0}
<div class="card bg-base-200 py-20 text-center border-2 border-dashed border-base-300">
<div class="card-body items-center">
<h2 class="card-title text-2xl opacity-40">You haven't created any {mode} yet.</h2>
<div class="card-actions mt-4">
<a href="/create" class="btn btn-primary">Create Your First {mode === 'cps' ? 'CP' : 'Char'}</a>
</div>
</div>
</div>
{:else}
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{#each items as item}
<div class="card bg-base-100 shadow-xl border border-base-200">
<div class="card-body p-5">
<h2 class="card-title">{item.name}</h2>
<div class="card-actions justify-end mt-4">
<a href="/edit/{mode}/{item.id}" class="btn btn-sm btn-ghost">Edit</a>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify that SvelteKit route files exist for the edit URLs generated by My Stuff.
# Expected: route files/directories matching /edit/cps/[id] and /edit/characters/[id], or update the hrefs to the actual edit route shape.

printf 'Generated edit hrefs in Svelte files:\n'
rg -nP 'href="/edit/\{mode\}/\{item\.id\}"' -C2 --glob '*.svelte' || true

printf '\nRelevant SvelteKit route files:\n'
fd -p '\+page\.svelte$' website/src/routes \
  | sort \
  | rg '/edit/|/cps/|/characters/' || true

Repository: pdnode-team/cp

Length of output: 790


🏁 Script executed:

#!/bin/bash
# Check if /edit directory exists and list all route files
printf 'Checking for /edit directory:\n'
ls -la website/src/routes/ | grep edit || echo "No /edit directory found"

printf '\nAll +page.svelte files in website/src/routes:\n'
find website/src/routes -name '+page.svelte' -type f | sort

printf '\nChecking for any edit route patterns:\n'
find website/src/routes -type d -name edit || echo "No /edit directory found"

Repository: pdnode-team/cp

Length of output: 555


🏁 Script executed:

#!/bin/bash
# Search for any edit route references or edit functionality
printf 'Searching for edit-related functionality:\n'
rg -i 'edit.*route|route.*edit' website/src/routes --type svelte -l || echo "No edit route references found"

printf '\nSearching for +layout or server files that might handle edit:\n'
find website/src/routes/edit website/src/routes/cps website/src/routes/characters -name '+layout*' -o -name '+server*' 2>/dev/null || echo "No edit layout/server files found"

printf '\nChecking mystuff page for edit functionality context:\n'
grep -n -i 'edit' website/src/routes/mystuff/+page.svelte

Repository: pdnode-team/cp

Length of output: 432


Update the Edit link destination or create the edit routes.

The edit routes at /edit/cps/[id] and /edit/characters/[id] do not exist in the codebase. The Edit button on line 82 will generate 404 errors. Either create these routes to handle edit functionality or change the href to an existing route.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@website/src/routes/mystuff/`+page.svelte at line 82, The Edit anchor in
+page.svelte uses a dynamic href "/edit/{mode}/{item.id}" which points to
non-existent routes (/edit/cps/[id] and /edit/characters/[id]) and will 404;
either implement route handlers for those paths (create +page.svelte or route
files for /edit/cps/[id] and /edit/characters/[id] to accept mode and id and
render the edit forms) or change the anchor href in +page.svelte (the <a
href="/edit/{mode}/{item.id}"> link) to an existing route pattern used by the
app (e.g., the current item edit route) so clicks resolve to a real page.

<a href="/{mode}/{item.id}" class="btn btn-sm btn-primary">View</a>
</div>
</div>
</div>
{/each}
</div>
{/if}
</div>