Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ NEXT_PUBLIC_ADMIN_API_URL=https://api-dev.sopt.org/v2
NEXT_PUBLIC_ADMIN_API_KEY=
NEXT_PUBLIC_OPERATION_API_URL=https://operation.api.dev.sopt.org
NEXT_PUBLIC_AUTH_API_URL=https://auth.api.dev.sopt.org
NEXT_PUBLIC_CREW_API_URL=https://crew.api.dev.sopt.org
NEXT_PUBLIC_CREW_API_URL=https://crew-api-dev.sopt.org/
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "CREW_API_URL" --type=ts -C3

Repository: sopt-makers/sopt-playground-frontend

Length of output: 1589


🏁 Script executed:

rg -n "crew-api" --type=ts -C3

Repository: sopt-makers/sopt-playground-frontend

Length of output: 62


🏁 Script executed:

fd -type f -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" | head -20

Repository: sopt-makers/sopt-playground-frontend

Length of output: 250


🏁 Script executed:

# Check .env.development file for EOF newline
if [ -f .env.development ]; then
  echo "=== File content with cat -A (to show whitespace) ===" 
  cat -A .env.development | tail -3
  echo ""
  echo "=== Line count ==="
  wc -l .env.development
fi

Repository: sopt-makers/sopt-playground-frontend

Length of output: 350


🏁 Script executed:

# Find axios configuration and baseURL setup
rg -n "axiosCrewInstance|baseURL" --type=ts -C2

Repository: sopt-makers/sopt-playground-frontend

Length of output: 4614


🏁 Script executed:

# Check other API URLs for consistency
cat .env.development

Repository: sopt-makers/sopt-playground-frontend

Length of output: 746


🏁 Script executed:

# Search for how other endpoints use baseURL with paths
rg -n "axiosInstance\.(get|post|put|delete)" --type=ts -A1 | head -30

Repository: sopt-makers/sopt-playground-frontend

Length of output: 2017


🏁 Script executed:

# Check if there's any axios configuration that normalizes URLs
rg -n "normalize|url|slash" src/api/index.ts

Repository: sopt-makers/sopt-playground-frontend

Length of output: 62


Trailing slash를 제거하여 다른 API URL과 일관성을 맞추세요.

NEXT_PUBLIC_CREW_API_URL의 trailing slash는 API 엔드포인트(/internal/meeting/stats/likes 등)와 결합될 때 이중 슬래시(//)를 유발합니다. 다른 API URL들(NEXT_PUBLIC_API_URL, NEXT_PUBLIC_AUTH_API_URL, NEXT_PUBLIC_OPERATION_API_URL)은 모두 trailing slash가 없으므로 제거가 필요합니다.

🔧 수정 제안
-NEXT_PUBLIC_CREW_API_URL=https://crew-api-dev.sopt.org/
+NEXT_PUBLIC_CREW_API_URL=https://crew-api-dev.sopt.org

파일 끝에 빈 줄을 추가하세요(EOF newline 규칙).

🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 14-14: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 14-14: [UnorderedKey] The NEXT_PUBLIC_CREW_API_URL key should go before the NEXT_PUBLIC_DEBUG key

(UnorderedKey)

🤖 Prompt for AI Agents
In @.env.development at line 14, Remove the trailing slash from
NEXT_PUBLIC_CREW_API_URL so it matches the other env vars (NEXT_PUBLIC_API_URL,
NEXT_PUBLIC_AUTH_API_URL, NEXT_PUBLIC_OPERATION_API_URL) and avoid double
slashes when concatenating endpoints; update the value for
NEXT_PUBLIC_CREW_API_URL to not end with "/" and ensure the file ends with a
single newline (EOF newline) after the change.

8 changes: 7 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,15 @@ jobs:
- name: Build
run: yarn build && yarn next export

- name: Prepare deployment package
run: |
mkdir -p deploy
cp -r out/* deploy/
cp -r functions deploy/

- uses: actions/upload-artifact@v4
with:
name: built-app
path: out
path: deploy
if-no-files-found: error
retention-days: 7
19 changes: 19 additions & 0 deletions functions/.well-known/apple-app-site-association.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const APPLE_APP_SITE_ASSOCIATION = `{
"applinks": {
"apps": [],
"details": [
{
"appID": "95YWTT5L8K.com.sopt-stamp-iOS.release",
"paths": ["*"]
}
]
}
}`;

export const onRequest: PagesFunction = () => {
return new Response(APPLE_APP_SITE_ASSOCIATION, {
headers: {
'Content-Type': 'application/json',
},
});
};
20 changes: 20 additions & 0 deletions functions/.well-known/assetlinks.json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const ASSETLINKS_JSON = `[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "org.sopt.official",
"sha256_cert_fingerprints": [
"29:23:1F:E3:7A:FD:76:9C:58:CA:F3:E2:D9:7C:A1:69:CF:04:A8:6E:5B:8A:C6:56:6A:6F:E8:FD:D8:FF:47:43"
]
}
}
]`;

export const onRequest: PagesFunction = () => {
return new Response(ASSETLINKS_JSON, {
headers: {
'Content-Type': 'application/json',
},
});
};
33 changes: 13 additions & 20 deletions public/.well-known/apple-app-site-association
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
{\rtf1\ansi\ansicpg1252\cocoartf2822
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\froman\fcharset0 Times-Roman;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;}
\paperw11900\paperh16840\margl1440\margr1440\vieww11520\viewh8400\viewkind0
\deftab720
\pard\pardeftab720\partightenfactor0

\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
\{\
\'93applinks\'94: \{\
\'93apps\'94: [],\
\'93details\'94: [\
\{\
\'93appID\'94: \'9395YWTT5L8K.com.sopt-stamp-iOS.release\'94,\
\'93paths\'94: [\'93*\'94]\
\}\
]\
\}\
\}}
{
"applinks": {
"apps": [],
"details": [
{
"appID": "95YWTT5L8K.com.sopt-stamp-iOS.release",
"paths": [
"*"
]
}
]
}
}
Comment on lines +1 to +13
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if both files exist in the codebase
fd "apple-app-site-association" -t f

Repository: sopt-makers/sopt-playground-frontend

Length of output: 62


🏁 Script executed:

# List files in the functions and public directories to understand structure
git ls-files | grep -E "(functions|public)" | grep -E "\.well-known|apple" | head -20

Repository: sopt-makers/sopt-playground-frontend

Length of output: 253


🌐 Web query:

Cloudflare Pages functions routing priority static assets

💡 Result:

On Cloudflare Pages (Functions mode), routing generally works like this:

  • Functions are checked first (based on your /functions file-based routes). If a Function route matches the request, it runs; if no Function matches, Pages falls back to a static asset (if one exists); otherwise it uses the normal Pages static routing behavior (e.g., SPA/404 behavior depending on your setup). [1]
  • Route specificity matters for Functions: more specific routes win over less specific/wildcard (dynamic) routes. [1]

The key control: _routes.json (Function invocation vs static)

If your project has a functions/ directory, Pages will (by default) invoke Functions on requests unless you restrict invocation using _routes.json. To “prioritize static assets” (avoid invoking Functions for static paths), configure _routes.json:

  • include: paths that should invoke Functions
  • exclude: paths that should not invoke Functions (exclude always wins over include) [1]

So, to ensure static assets win for things like /assets/*, put them in exclude. [1]

If you’re using Advanced Mode (_worker.js)

In Advanced Mode, you must explicitly serve static assets from your Worker (typically by calling env.ASSETS.fetch(request) for non-dynamic paths), otherwise your static files won’t be returned. [2]

Related note (Pages vs Workers static assets defaults)

Cloudflare’s docs also highlight a conceptual difference when moving to Workers: Pages defaults to running Functions ahead of static assets (customizable via _routes.json), whereas Workers + static assets default to serving assets first unless configured otherwise. [3]

Sources: Cloudflare Pages routing + _routes.json docs [1], Advanced Mode docs [2], Workers static-assets compatibility notes re: Pages _routes.json behavior [3].


🏁 Script executed:

# Check if _routes.json exists
fd "_routes.json" -t f

Repository: sopt-makers/sopt-playground-frontend

Length of output: 62


🏁 Script executed:

# Read both files to compare content
cat -n "public/.well-known/apple-app-site-association"

Repository: sopt-makers/sopt-playground-frontend

Length of output: 350


🏁 Script executed:

# Read the TypeScript function
cat -n "functions/.well-known/apple-app-site-association.ts"

Repository: sopt-makers/sopt-playground-frontend

Length of output: 584


정적 파일 public/.well-known/apple-app-site-association 제거 필요

functions/.well-known/apple-app-site-association.ts 함수가 존재하면, Cloudflare Pages는 기본적으로 함수를 먼저 실행하므로 정적 파일은 도달 불가능한 죽은 코드입니다. 두 파일의 내용이 동일하므로 정적 파일을 제거하고 함수만 유지하세요. (함수는 명시적 Content-Type 헤더 설정으로 더 안전합니다.)

🤖 Prompt for AI Agents
In `@public/.well-known/apple-app-site-association` around lines 1 - 13, Remove
the dead static apple-app-site-association JSON file and keep the dynamic
handler instead: delete the static apple-app-site-association file under your
public assets so Cloudflare Pages will route to the existing
apple-app-site-association server function (the function handler in
apple-app-site-association.ts). Ensure the function remains and explicitly sets
the Content-Type header to "application/json" in its response so clients receive
the correct MIME type.

4 changes: 4 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ export const handleTokenError = async (error: AxiosError<unknown>) => {
// 개인정보 처리방침 페이지는 로그인 필요 없음, 구글 정책상 오픈
return Promise.reject(error);
}
// 에스크 문자를 통한 딥링크 접근을 위해 허용
if (window.location.pathname === '/.well-known/apple-app-site-association' || '/.well-known/assetlinks.json') {
return Promise.reject(error);
}
Comment on lines +98 to +101
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

크리티컬 버그: 조건문이 항상 true로 평가되어 모든 401 응답에서 토큰 갱신이 실행되지 않습니다.

'/.well-known/assetlinks.json'은 비어있지 않은 문자열이므로 항상 truthy입니다. 즉, 이 if 블록은 모든 401 에러에 대해 실행되어 토큰 리프레시 로직을 완전히 우회합니다.

🐛 수정 제안
-    if (window.location.pathname === '/.well-known/apple-app-site-association' || '/.well-known/assetlinks.json') {
+    if (window.location.pathname === '/.well-known/apple-app-site-association' || window.location.pathname === '/.well-known/assetlinks.json') {
       return Promise.reject(error);
     }
📝 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
// 에스크 문자를 통한 딥링크 접근을 위해 허용
if (window.location.pathname === '/.well-known/apple-app-site-association' || '/.well-known/assetlinks.json') {
return Promise.reject(error);
}
// 에스크 문자를 통한 딥링크 접근을 위해 허용
if (window.location.pathname === '/.well-known/apple-app-site-association' || window.location.pathname === '/.well-known/assetlinks.json') {
return Promise.reject(error);
}
🤖 Prompt for AI Agents
In `@src/api/index.ts` around lines 98 - 101, The if condition incorrectly uses
"if (window.location.pathname === '/.well-known/apple-app-site-association' ||
'/.well-known/assetlinks.json')" which always evaluates true because the second
operand is a non-empty string; change the boolean check so both paths are
compared against window.location.pathname (i.e., window.location.pathname ===
'/.well-known/apple-app-site-association' || window.location.pathname ===
'/.well-known/assetlinks.json') so that Promise.reject(error) only runs when the
request path exactly matches one of those two well-known endpoints (affecting
the 401 handling logic that triggers token refresh).

/** 토큰이 없으면 refresh 시도하지 않고 바로 intro로 이동 */
const currentToken = tokenStorage.get();
if (currentToken === null && window.location.pathname !== '/intro') {
Expand Down
4 changes: 2 additions & 2 deletions src/components/members/main/MemberList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { colors } from '@sopt-makers/colors';
import { fonts } from '@sopt-makers/fonts';
import { IconSwitchVertical } from '@sopt-makers/icons';
import { SearchField } from '@sopt-makers/ui';
import { Spacing } from '@toss/emotion-utils';
import { debounce, uniq } from 'lodash-es';
import Link from 'next/link';
import { useRouter } from 'next/router';
Expand Down Expand Up @@ -45,7 +46,6 @@ import { useRunOnce } from '@/hooks/useRunOnce';
import IconDiagonalArrow from '@/public/icons/icon-diagonal-arrow.svg';
import { MB_BIG_MEDIA_QUERY } from '@/styles/mediaQuery';
import { MOBILE_MEDIA_QUERY } from '@/styles/mediaQuery';
import { Spacing } from '@toss/emotion-utils';
const PAGE_LIMIT = 24;

interface MemberListProps {
Expand Down Expand Up @@ -492,7 +492,7 @@ const MemberList: FC<MemberListProps> = ({ banner }) => {
const badges = sorted
.filter((activity) => activity.generation && activity.part)
.map((activity) => ({
content: `${activity.generation}기 ${activity.part}`,
content: `${activity.generation}기 ${activity.team || activity.part}`,
isActive: activity.generation === LATEST_GENERATION,
}));

Expand Down
Loading