diff --git a/.gitignore b/.gitignore index 0a311e4..c1a2613 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .DS_Store +tests/* + ################################ # Node.js/React (web/) 專屬 ################################ diff --git a/webpage/bug-report.md b/webpage/bug-report.md new file mode 100644 index 0000000..d45bef7 --- /dev/null +++ b/webpage/bug-report.md @@ -0,0 +1,126 @@ +# Bug Report File + + +## 2025/12/13 00:33 + +Trying to delete an alias from a image, but got error "500 Internal Server Error". + +Using uptrace and here's the log message: +``` +{ + "id": 0, + "groupId": "9814518078643611785", + "system": "log:error", + "name": "exception", + "displayName": "github.com/uptrace/bun/driver/pgdriver.Error: ERROR: syntax error at or near \")\" (SQLSTATE=42601)", + "time": "2025-12-12T16:31:55.153Z", + "attrs": { + "service_name": "go2025-backend", + "service_version": "v0.0.1", + "telemetry_sdk_language": "go", + "telemetry_sdk_name": "opentelemetry", + "exception_param_SQLSTATE": "42601", + "exception_type": "github.com/uptrace/bun/driver/pgdriver.Error", + "otel_library_name": "github.com/uptrace/bun", + "telemetry_sdk_version": "1.38.0", + "exception_param_log_severity": "ERROR", + "host_name": "7d8c05cf36bb", + "log_severity": "ERROR" + } +} +``` + + + +## 2025/12/12 23:41 + +getting 401 when trying to access backend API `GET /api/aliases` right after start up of docker compose, but after 1 ~ 2 minutes, it's ok. + +Log message: +``` +backend-1 | {"level":"info","ts":1765553837.4421663,"caller":"middlewares/accessLog.go:13","msg":"POST /auth/gen-login-url"} +backend-1 | {"level":"info","ts":1765553839.3232543,"caller":"middlewares/accessLog.go:13","msg":"OPTIONS /*any"} +backend-1 | {"level":"info","ts":1765553839.323296,"caller":"middlewares/accessLog.go:13","msg":"OPTIONS /*any"} +backend-1 | {"level":"info","ts":1765553839.3233085,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"} +backend-1 | {"level":"warn","ts":1765553839.3233905,"caller":"middlewares/errorHandler.go:78","msg":"user did not login","error":"http: named cookie not present"} +backend-1 | {"level":"info","ts":1765553839.324581,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"} +backend-1 | {"level":"warn","ts":1765553839.3246245,"caller":"middlewares/errorHandler.go:78","msg":"user did not login","error":"http: named cookie not present"} +backend-1 | {"level":"info","ts":1765553839.3252084,"caller":"middlewares/accessLog.go:13","msg":"POST /auth/login"} +backend-1 | {"level":"info","ts":1765553839.325493,"caller":"middlewares/accessLog.go:13","msg":"POST /auth/login"} +backend-1 | {"level":"info","ts":1765553839.3793862,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"} +backend-1 | {"level":"info","ts":1765553839.3855033,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"} +backend-1 | {"level":"info","ts":1765553872.7971,"caller":"middlewares/accessLog.go:13","msg":"POST /auth/login"} +backend-1 | {"level":"info","ts":1765553872.842072,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"} +backend-1 | {"level":"info","ts":1765553905.793059,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"} +backend-1 | {"level":"info","ts":1765553905.7976384,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"} +backend-1 | {"level":"info","ts":1765553905.7985706,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"} +backend-1 | {"level":"info","ts":1765553905.7997246,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"} +``` + +I suspect it's the race condition happening at App.tsx (conflicts between "login" and "fetch aliases"). Please investigate the possible issue. + + + + +## 2025/12/12 23:14 + + +When I tried to delete an alias from a image, I got err "500 Internal Server Error". Log message: +``` +:8080/api/image/2/aliases:1 + Failed to load resource: the server responded with a status of 500 (Internal Server Error) +api.ts:114 + PUT http://localhost:8080/api/image/2/aliases 500 (Internal Server Error) +updateImageAliases @ api.ts:114 +handleSaveImage @ App.tsx:101 +handleSave @ ImageModal.tsx:48 +``` + +When an image is deleted, the alias entries at side barand buttons "previous page" and "next page" at top & bottom of main region of webpage are not updated synchronously. + + + + +## 2025/12/12 22:54 + +The aliases handling mechanism seems to have some bugs: +I test with single image huh.png, added 16 testing aliases: ["huh", "what", "huh2", "huh3", 4, 5, 6, 7, 8, 9, 0, 1, 2, 3]. +- The side bar's numbering at page 1 isn't correct, it should be `#1, #2, #3, #4, #5`, but it's `#1, #3, #5, #6, #7`. Page 2, 3 are correct. +- There are more than 5 alias rows at the main region of webpage, and the alias string at each row is wrong. + - Page 1: `4, huh2, huh3, Alias #8, Alias #9, Alias #10, Alias #11, Alias #12, Alias #13, Alias #14, Alias #15, Alias #16` + - Page 2: `5, 6, 7, 8, 9, Alias #5, Alias #6, Alias #7, Alias #13, Alias #14, Alias #15, Alias #16` + - Page 3: `0, 1, 2, 3, Alias #5, Alias #6, Alias #7, Alias #8, Alias #9, Alias #10, Alias #11, Alias #12` +It seems I didn't modify the pagination mechanism correctly (originally you implemented 5 aliases per page, but I want it to be 10 aliases per page). + + + + +## 2025/12/12 22:01 + +It seems there is a permission issue when backend tried to run `make swagger`. + +``` +make 280ms  Fri Dec 12 22:00:30 2025 +go run github.com/swaggo/swag/cmd/swag@v1.16.4 fmt +go run github.com/swaggo/swag/cmd/swag@v1.16.4 init -o docs -g cmds/serve.go -pdl 1 +2025/12/12 22:00:34 Generate swagger docs.... +2025/12/12 22:00:34 Generate general API Info, search dir:./ +2025/12/12 22:00:34 pkg /Users/polarbear03617/Documents/交大/大三上/Go程式設計/backend/cmds cannot find all dependencies, +go: writing stat cache: open /Users/polarbear03617/go/pkg/mod/cache/download/github.com/go-resty/resty/v2/@v/v2.17.0.info640656495.tmp: permission denied +exit status 1 +make: *** [swagger] Error 1 +``` + + + + +## 2025/12/11 12:42 + +I correctly access the backend API with the cookie provided by backend, but why do I still see this error: +Failed to load resource: the server responded with a status of 401 (Unauthorized) (Login and seeing webpage is ok, but can't fetch aliases) + + + + + + diff --git a/webpage/spec.md b/webpage/spec.md index 360ebe1..20a3f82 100644 --- a/webpage/spec.md +++ b/webpage/spec.md @@ -1,12 +1,41 @@ # Webpage for Memebot + + +## Modify the uploadmodal's and imagemodal's alias adding/removing mechanism + +Modify the uploadmodal's and imagemodal's alias adding/removing mechanism to the following: +[ (press Enter or click ADD to add alias) ] [ADD] + +After typing alias and click ADD +[ test_alias ] [REMOVE] +[ (press Enter or click ADD to add alias) ] [ADD] + +The [ADD] and [REMOVE] are buttons, and should both have the same width. Also, the protection mechanism (zero alias = not allow to save modifications / upload images) should be retained. + + + + +## Modify the webpage + +Modify the webpage: We don't want one page shows only an alias. +We want a paging feature on aliases, so that each page shows a fixed number of aliases and their corresponding images (the fixed number can be stored in a variable in api.ts). And the "next page" and "previous page" buttons should be located at both the top (below search bar) and the bottom of the page. +The sidebar is also modified to show the current page number, the aliases on the current page, and the total number of pages. + + + + + + +## Initial Setup + This webpage is used to manage the images and aliases used by our Discord memebot. Relationship between images and aliases: many to many. -## Layout +### Layout React + TypeScript (with Tailwind CSS) @@ -33,7 +62,7 @@ The top is the Navbar: - The right side of the Navbar contains function buttons, currently only Upload Image and Login. -### Find +#### Find Find (Viewing and Searching) - The left sidebar serves as a table of content for all aliases, similar to the navigation feature in HackMD or mdBook. @@ -66,7 +95,7 @@ Find (Viewing and Searching) -### Upload Image +#### Upload Image When the user clicks this button, a floating window (modal) appears: @@ -89,12 +118,9 @@ When the user clicks this button, a floating window (modal) appears: ====================================== ``` -## Login (Authentication) +#### Login (Authentication) Authentication is used to verify the current Discord user operating the frontend. Users who are not logged in can only perform viewing-related operations; they cannot edit, upload, or delete. - diff --git a/webpage/src/App.tsx b/webpage/src/App.tsx index a372f6d..bb2aa75 100644 --- a/webpage/src/App.tsx +++ b/webpage/src/App.tsx @@ -12,6 +12,7 @@ import type { Image, Alias } from './types'; function App() { const [isLoginMode, setIsLoginMode] = useState(false); + const [isInitializing, setIsInitializing] = useState(true); const [images, setImages] = useState([]); const [allAliases, setAllAliases] = useState([]); const [searchQuery, setSearchQuery] = useState(''); @@ -20,8 +21,6 @@ function App() { const [showUploadModal, setShowUploadModal] = useState(false); const [loading, setLoading] = useState(true); - console.log("Hello"); - // Check if URL has a login token useEffect(() => { const urlParams = new URLSearchParams(window.location.search); @@ -36,22 +35,26 @@ function App() { window.history.replaceState({}, document.title, '/'); // Exit login mode and load data setIsLoginMode(false); + setIsInitializing(false); loadData(); }) .catch((error) => { console.error("Login failed:", error); - setIsLoginMode(true); // Show login page on error + setIsLoginMode(true); + setIsInitializing(false); }); } else { + setIsInitializing(false); loadData(); } }, []); useEffect(() => { - if (!isLoginMode) { + // Don't load data during initial mount when login is in progress + if (!isLoginMode && !isInitializing) { loadData(); } - }, [searchQuery, isLoginMode]); + }, [searchQuery, isLoginMode, isInitializing]); useEffect(() => { // Reset to page 1 when search query changes @@ -105,6 +108,8 @@ function App() { const handleDeleteImage = async (imageId: number) => { await api.deleteImage(imageId); await loadData(); + // After deletion, check if current page is still valid + // This will be handled by the useEffect below }; // Show login page if in login mode @@ -121,6 +126,14 @@ function App() { // Pagination calculations const totalPages = Math.max(1, Math.ceil(filteredAliases.length / ALIASES_PER_PAGE)); + + // Adjust current page if it's now out of bounds + useEffect(() => { + if (currentPage > totalPages) { + setCurrentPage(Math.max(1, totalPages)); + } + }, [totalPages, currentPage]); + const startIndex = (currentPage - 1) * ALIASES_PER_PAGE; const endIndex = startIndex + ALIASES_PER_PAGE; const currentPageAliases = filteredAliases.slice(startIndex, endIndex); diff --git a/webpage/src/api.ts b/webpage/src/api.ts index f4c9c94..86e777f 100644 --- a/webpage/src/api.ts +++ b/webpage/src/api.ts @@ -3,7 +3,7 @@ import type { Image, Alias } from './types'; const API_BASE_URL = 'http://localhost:8080'; // Pagination configuration -export const ALIASES_PER_PAGE = 5; // Number of aliases to show per page +export const ALIASES_PER_PAGE = 10; // Number of aliases to show per page class ApiService { // Authentication @@ -111,6 +111,8 @@ class ApiService { // PUT /api/image/{id}/aliases // Body: {aliases: string[]} async updateImageAliases(id: number, aliasNames: string[]): Promise { + console.log("Update aliases of image ", id); + console.log("Alias names: ", aliasNames); const response = await fetch(`${API_BASE_URL}/api/image/${id}/aliases`, { method: 'PUT', headers: { diff --git a/webpage/src/assets/tests/daisuke.gif b/webpage/src/assets/tests/daisuke.gif deleted file mode 100644 index 340116a..0000000 Binary files a/webpage/src/assets/tests/daisuke.gif and /dev/null differ diff --git a/webpage/src/assets/tests/fakedb.json b/webpage/src/assets/tests/fakedb.json deleted file mode 100644 index db4038c..0000000 --- a/webpage/src/assets/tests/fakedb.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "images": [ - { - "id": "1", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-20T12:00:00Z", - "aliases": ["daisuke"], - "url": "/api/images/1/file" - }, - { - "id": "2", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-20T12:05:00Z", - "aliases": ["huh"], - "url": "/api/images/2/file" - }, - { - "id": "3", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-21T09:00:00Z", - "aliases": ["killme"], - "url": "/api/images/3/file" - }, - { - "id": "4", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-21T09:30:00Z", - "aliases": ["nofriend"], - "url": "/api/images/4/file" - }, - { - "id": "5", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-21T10:00:00Z", - "aliases": ["orz"], - "url": "/api/images/5/file" - }, - { - "id": "6", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-22T11:20:00Z", - "aliases": ["ramen"], - "url": "/api/images/6/file" - }, - { - "id": "7", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-22T14:00:00Z", - "aliases": ["rrrr"], - "url": "/api/images/7/file" - }, - { - "id": "8", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-23T08:00:00Z", - "aliases": ["sleep"], - "url": "/api/images/8/file" - }, - { - "id": "9", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-23T08:05:00Z", - "aliases": ["sleep"], - "url": "/api/images/9/file" - }, - { - "id": "10", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-24T15:00:00Z", - "aliases": ["你要出多少"], - "url": "/api/images/10/file" - }, - { - "id": "11", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-24T16:00:00Z", - "aliases": ["好ㄘ"], - "url": "/api/images/11/file" - }, - { - "id": "12", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-25T10:00:00Z", - "aliases": ["宅斃了"], - "url": "/api/images/12/file" - }, - { - "id": "13", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-25T10:10:00Z", - "aliases": ["宅斃了"], - "url": "/api/images/13/file" - }, - { - "id": "14", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-25T10:20:00Z", - "aliases": ["宅斃了"], - "url": "/api/images/14/file" - }, - { - "id": "15", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-26T12:00:00Z", - "aliases": ["幹波大的"], - "url": "/api/images/15/file" - }, - { - "id": "16", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-26T13:00:00Z", - "aliases": ["我什麼都沒有"], - "url": "/api/images/16/file" - }, - { - "id": "17", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-27T09:00:00Z", - "aliases": ["欸嘿"], - "url": "/api/images/17/file" - }, - { - "id": "18", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-25T10:20:00Z", - "aliases": ["宅斃了"], - "url": "/api/images/18/file" - }, - { - "id": "19", - "uploaded_user_id": "konchin.shih", - "uploaded_at": "2023-10-25T10:20:00Z", - "aliases": ["宅斃了"], - "url": "/api/images/19/file" - } - ] -} diff --git a/webpage/src/assets/tests/huh.png b/webpage/src/assets/tests/huh.png deleted file mode 100644 index 91d7d65..0000000 Binary files a/webpage/src/assets/tests/huh.png and /dev/null differ diff --git a/webpage/src/assets/tests/killme.gif b/webpage/src/assets/tests/killme.gif deleted file mode 100644 index a654396..0000000 Binary files a/webpage/src/assets/tests/killme.gif and /dev/null differ diff --git a/webpage/src/assets/tests/nofriend.png b/webpage/src/assets/tests/nofriend.png deleted file mode 100644 index bd6b868..0000000 Binary files a/webpage/src/assets/tests/nofriend.png and /dev/null differ diff --git a/webpage/src/assets/tests/orz.gif b/webpage/src/assets/tests/orz.gif deleted file mode 100644 index 3d2f166..0000000 Binary files a/webpage/src/assets/tests/orz.gif and /dev/null differ diff --git a/webpage/src/assets/tests/ramen.jpg b/webpage/src/assets/tests/ramen.jpg deleted file mode 100644 index a540668..0000000 Binary files a/webpage/src/assets/tests/ramen.jpg and /dev/null differ diff --git a/webpage/src/assets/tests/rrrr.jpg b/webpage/src/assets/tests/rrrr.jpg deleted file mode 100644 index 31f0b1f..0000000 Binary files a/webpage/src/assets/tests/rrrr.jpg and /dev/null differ diff --git a/webpage/src/assets/tests/sleep.png b/webpage/src/assets/tests/sleep.png deleted file mode 100644 index c2c9896..0000000 Binary files a/webpage/src/assets/tests/sleep.png and /dev/null differ diff --git a/webpage/src/assets/tests/sleep^2.jpg b/webpage/src/assets/tests/sleep^2.jpg deleted file mode 100644 index aa4345f..0000000 Binary files a/webpage/src/assets/tests/sleep^2.jpg and /dev/null differ diff --git a/webpage/src/assets/tests/你要出多少.png b/webpage/src/assets/tests/你要出多少.png deleted file mode 100644 index 192c703..0000000 Binary files a/webpage/src/assets/tests/你要出多少.png and /dev/null differ diff --git a/webpage/src/assets/tests/好ㄘ.png b/webpage/src/assets/tests/好ㄘ.png deleted file mode 100644 index 3352550..0000000 Binary files a/webpage/src/assets/tests/好ㄘ.png and /dev/null differ diff --git a/webpage/src/assets/tests/宅斃了.jpg b/webpage/src/assets/tests/宅斃了.jpg deleted file mode 100644 index d7de4c8..0000000 Binary files a/webpage/src/assets/tests/宅斃了.jpg and /dev/null differ diff --git a/webpage/src/assets/tests/宅斃了^2.png b/webpage/src/assets/tests/宅斃了^2.png deleted file mode 100644 index f6e6a44..0000000 Binary files a/webpage/src/assets/tests/宅斃了^2.png and /dev/null differ diff --git a/webpage/src/assets/tests/宅斃了^3.png b/webpage/src/assets/tests/宅斃了^3.png deleted file mode 100644 index 1c901a1..0000000 Binary files a/webpage/src/assets/tests/宅斃了^3.png and /dev/null differ diff --git a/webpage/src/assets/tests/宅斃了^4.png b/webpage/src/assets/tests/宅斃了^4.png deleted file mode 100644 index f6e6a44..0000000 Binary files a/webpage/src/assets/tests/宅斃了^4.png and /dev/null differ diff --git a/webpage/src/assets/tests/宅斃了^5.png b/webpage/src/assets/tests/宅斃了^5.png deleted file mode 100644 index f6e6a44..0000000 Binary files a/webpage/src/assets/tests/宅斃了^5.png and /dev/null differ diff --git a/webpage/src/assets/tests/幹波大的.png b/webpage/src/assets/tests/幹波大的.png deleted file mode 100644 index 6e75a17..0000000 Binary files a/webpage/src/assets/tests/幹波大的.png and /dev/null differ diff --git a/webpage/src/assets/tests/我什麼都沒有.png b/webpage/src/assets/tests/我什麼都沒有.png deleted file mode 100644 index b1810f5..0000000 Binary files a/webpage/src/assets/tests/我什麼都沒有.png and /dev/null differ diff --git a/webpage/src/assets/tests/欸嘿.png b/webpage/src/assets/tests/欸嘿.png deleted file mode 100644 index a341859..0000000 Binary files a/webpage/src/assets/tests/欸嘿.png and /dev/null differ diff --git a/webpage/src/components/ImageGrid.tsx b/webpage/src/components/ImageGrid.tsx index 8583abc..8a80a8d 100644 --- a/webpage/src/components/ImageGrid.tsx +++ b/webpage/src/components/ImageGrid.tsx @@ -8,19 +8,23 @@ interface ImageGridProps { } export default function ImageGrid({ images, aliases, onImageClick }: ImageGridProps) { - // Create a map of alias ID to alias name + // Create a map of alias ID to alias name for current page aliases only const aliasMap = new Map(aliases.map(a => [a.id, a.name])); + const currentPageAliasIds = new Set(aliases.map(a => a.id)); - // Group images by alias + // Group images by alias, but only for aliases on the current page const groupedImages = images.reduce((acc, image) => { if (image.aliasesIds.length === 0) { if (!acc['__no_alias__']) acc['__no_alias__'] = []; acc['__no_alias__'].push(image); } else { + // Only process aliases that are on the current page image.aliasesIds.forEach((aliasId) => { - const aliasName = aliasMap.get(aliasId) || `Alias #${aliasId}`; - if (!acc[aliasName]) acc[aliasName] = []; - acc[aliasName].push(image); + if (currentPageAliasIds.has(aliasId)) { + const aliasName = aliasMap.get(aliasId) || `Alias #${aliasId}`; + if (!acc[aliasName]) acc[aliasName] = []; + acc[aliasName].push(image); + } }); } return acc; diff --git a/webpage/src/components/ImageModal.tsx b/webpage/src/components/ImageModal.tsx index dcb9eda..7e806a8 100644 --- a/webpage/src/components/ImageModal.tsx +++ b/webpage/src/components/ImageModal.tsx @@ -105,64 +105,36 @@ export default function ImageModal({
{aliases.map((alias) => (
- {( - - )} +
))} - {( -
- - setNewAlias(e.target.value)} - onKeyPress={(e) => e.key === 'Enter' && handleAddAlias()} - className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" - /> -
- )} +
+ setNewAlias(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleAddAlias()} + className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + +
@@ -188,7 +160,7 @@ export default function ImageModal({ {( + ))}
- {/* Green "+" Button: Add new alias to the list */} - setNewAlias(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && handleAddAlias()} className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" /> +