Skip to content

feat: 코인 어드민 로그인 로직 추가 - #9

Open
Soundbar91 wants to merge 7 commits into
mainfrom
feat/add-koin-admin-login
Open

feat: 코인 어드민 로그인 로직 추가#9
Soundbar91 wants to merge 7 commits into
mainfrom
feat/add-koin-admin-login

Conversation

@Soundbar91

@Soundbar91 Soundbar91 commented Aug 6, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features

    • Added secure administrator authentication for the Koin API.
    • Added configuration for the Koin API URL and administrator credentials.
    • Deployment environments now receive the required Koin settings automatically.
  • Bug Fixes

    • Added validation to ensure authentication responses contain a valid access token.

@Soundbar91
Soundbar91 requested a review from ff1451 August 6, 2026 14:47
@Soundbar91 Soundbar91 self-assigned this Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds Koin administrator environment variables, injects them during deployment, declares their types, and implements an administrator login request that returns a validated token.

Changes

Koin administrator authentication

Layer / File(s) Summary
Environment contract and deployment wiring
.env.example, src/types/index.d.ts, .github/workflows/deploy.yml
The Koin API URL, administrator email, and password variables are documented, typed, and written to deployment environment files from repository secrets.
Administrator login request
src/services/koin/adminLogin.ts
loginKoinAdmin posts the configured credentials to the Koin API, validates that the response contains a non-empty token, and returns the token.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant loginKoinAdmin
  participant KoinAPI
  loginKoinAdmin->>KoinAPI: POST administrator credentials
  KoinAPI-->>loginKoinAdmin: Return token payload
  loginKoinAdmin-->>loginKoinAdmin: Validate non-empty token
Loading

Suggested reviewers: ff1451

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding Koin administrator login logic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-koin-admin-login

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/services/koin/adminLogin.ts (1)

5-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for the authentication contract.

Cover successful login, missing configuration, non-2xx responses, null or malformed response bodies, whitespace-only tokens, and timeout handling. These cases exercise the external boundary and the failure paths in Lines 6-21.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/koin/adminLogin.ts` around lines 5 - 23, 운영환경 인증 계약을 검증하는 테스트를
추가하세요. loginKoinAdmin을 대상으로 성공 시 토큰 반환, 필수 환경 설정 누락, non-2xx 응답, null 또는 잘못된 응답
본문, 공백만 포함된 토큰, 타임아웃을 각각 검증하고, 외부 $fetch는 모킹하여 호출 URL·인증 정보와 오류 전파를 확인하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/services/koin/adminLogin.ts`:
- Around line 6-10: Validate KOIN_API_BASE_URL, KOIN_ADMIN_EMAIL, and
KOIN_ADMIN_PASSWORD in the admin login flow before invoking $fetch, treating
unset or blank values as missing. Reject with an error that identifies every
missing configuration key, while preserving the existing request behavior when
all values are valid.
- Around line 6-8: Update the admin credential access in the Koin login module
to use private Nitro runtime configuration via useRuntimeConfig() instead of
import.meta.env, while preserving the existing base URL behavior. Enforce a
server-only module boundary so KOIN_ADMIN_EMAIL and KOIN_ADMIN_PASSWORD remain
private runtime values.
- Around line 10-17: Update the $fetch call in the admin login flow to include
the service SLA as its per-request timeout, using ofetch’s timeout option.
Ensure timeout-triggered aborts are handled as failed logins through the
existing error path.
- Around line 1-3: Update the admin login response handling around
KoinAdminLoginResponse and the $fetch call to treat the payload as unknown
rather than trusting the generic assertion. Validate that it is a non-null
object with a non-empty string token before returning the trimmed token, and
route invalid payloads through the existing intended login error path instead of
accessing token unsafely.

---

Nitpick comments:
In `@src/services/koin/adminLogin.ts`:
- Around line 5-23: 운영환경 인증 계약을 검증하는 테스트를 추가하세요. loginKoinAdmin을 대상으로 성공 시 토큰
반환, 필수 환경 설정 누락, non-2xx 응답, null 또는 잘못된 응답 본문, 공백만 포함된 토큰, 타임아웃을 각각 검증하고, 외부
$fetch는 모킹하여 호출 URL·인증 정보와 오류 전파를 확인하세요.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 91224b9b-64ba-442a-b161-4326fa8f408b

📥 Commits

Reviewing files that changed from the base of the PR and between c68deda and 80bb158.

📒 Files selected for processing (4)
  • .env.example
  • .github/workflows/deploy.yml
  • src/services/koin/adminLogin.ts
  • src/types/index.d.ts

Comment on lines +1 to +3
interface KoinAdminLoginResponse {
token: string;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching adminLogin.ts:"
fd -a 'adminLogin\.ts$' . || true

echo
echo "Relevant file contents:"
for f in $(fd 'adminLogin\.ts$' .); do
  echo "--- $f ---"
  cat -n "$f"
done

echo
echo "Search for KoinAdminLoginResponse:"
rg -n "KoinAdminLoginResponse|admin/user/login|response\.token" . || true

Repository: BCSDLab/B_BOT

Length of output: 1463


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Lock/npm files:"
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$' || true

echo
echo "Search for $fetch:"
rg -n "\$fetch" . --glob '!node_modules' --glob '!dist' --glob '!build' || true

Repository: BCSDLab/B_BOT

Length of output: 234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "package.json deps relevant:"
jq -r '.dependencies, .devDependencies' package.json | sed 's/^/--/; /^null$/d'

echo
echo "Search for fetch implementation:"
rg -n "function \$fetch|const \$fetch|export .*fetch|http|ky|ofetch|unenv" . --glob '!node_modules' --glob '!dist' --glob '!build' || true

Repository: BCSDLab/B_BOT

Length of output: 6385


🌐 Web query:

ofetch 1.4.1 $fetch default parseResponse JSON response null documentation

💡 Result:

In ofetch (including version 1.4.1), the default response parsing mechanism handles responses as follows when a JSON response is received [1]: 1. Parsing Logic: When the response type is resolved to "json" (which is the default if no Content-Type header is present or if the header indicates JSON) [2][3][1], ofetch calls response.text to get the response body as a string [1]. 2. Handling Empty/Null Responses: If the resulting string is empty or falsy, ofetch skips the parsing step [1]. Specifically, the implementation checks if (data) (where data is the text content) before attempting to parse it with the parseResponse function or the default JSON.parse [1]. 3. Result: Consequently, if the response body is empty, the _data property of the response remains undefined or is not assigned a parsed object, avoiding attempts to call JSON.parse() on an empty string, which would otherwise throw a SyntaxError [1]. If you are encountering a null response and need to ensure it is handled gracefully, you can provide a custom parseResponse function to override this default behavior [4][2][1]: await ofetch('/api/endpoint', { parseResponse: (text) => text? JSON.parse(text): null }) Additionally, note that for specific HTTP status codes (such as 204 No Content), ofetch intentionally excludes the body from being processed [1].

Citations:


🌐 Web query:

ofetch parseResponse JSON parsing response body type JSON.parse default null

💡 Result:

In ofetch, the default behavior for parsing JSON response bodies is to use destr (a secure, optimized JSON parser) rather than JSON.parse directly [1][2]. When the response type is json (which is the default when the Content-Type is application/json or missing), ofetch reads the response body as text [1][3]. It then checks if the response body text is non-empty [3]. If the text is present, it passes the string to the configured parser [3]. Regarding your query on null: 1. Default behavior: If the response body is empty or nullish, ofetch typically avoids attempting to parse it as JSON [3]. 2. Custom parsing: If you provide a custom parseResponse function, ofetch will use that function to handle the text [4][5]. If you explicitly set parseResponse: JSON.parse, and the response body is an empty string, JSON.parse('') will throw a SyntaxError. Therefore, if you are providing a custom parser, you should ensure it handles empty or null body scenarios appropriately [4][5]. In summary, ofetch internally uses destr by default for robustness [2], and only attempts to parse the text if it is not empty [3]. If you override this with JSON.parse, ensure your implementation accounts for empty response bodies [4][5].

Citations:


Guard the response before reading response.token.

$fetch<KoinAdminLoginResponse> is a type assertion and does not validate the returned JSON. If admin/user/login returns an unexpected payload, response.token can throw TypeError before the intended login error. Handle the value as unknown, check that it is a non-null object with a non-empty string token, then return response.token.trim().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/koin/adminLogin.ts` around lines 1 - 3, Update the admin login
response handling around KoinAdminLoginResponse and the $fetch call to treat the
payload as unknown rather than trusting the generic assertion. Validate that it
is a non-null object with a non-empty string token before returning the trimmed
token, and route invalid payloads through the existing intended login error path
instead of accessing token unsafely.

Comment on lines +6 to +8
const baseURL = import.meta.env.KOIN_API_BASE_URL;
const email = import.meta.env.KOIN_ADMIN_EMAIL;
const password = import.meta.env.KOIN_ADMIN_PASSWORD;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Expect: loginKoinAdmin is server-only and the project defines an intentional
# runtime configuration or envPrefix strategy for the Koin variables.
rg -n -C 4 \
  'loginKoinAdmin|KOIN_(API_BASE_URL|ADMIN_EMAIL|ADMIN_PASSWORD)|runtimeConfig|useRuntimeConfig|envPrefix|import\.meta\.env|process\.env' . || true

Repository: BCSDLab/B_BOT

Length of output: 18449


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repository files relevant to build/deploy config =="
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|\.nite|\.nitro|nitro\.config\.(ts|js|mjs|cjs)|nuxt\.config\.(ts|js|mjs|cjs)|wrangler\.toml|vercel\.json|netlify\.toml|package-lock\.json|yarn\.lock|\.env|\.output)' || true

echo
echo "== package scripts and deps =="
python3 - <<'PY'
import json
from pathlib import Path
p=Path('package.json')
if p.exists():
    data=json.loads(p.read_text())
    print("scripts:")
    print(json.dumps(data.get("scripts", {}), indent=2, sort_keys=True))
    print("\ndependencies:")
    for k,v in sorted((data.get("dependencies",{})|data.get("devDependencies",{})).items()):
        if any(s in k.lower() for s in ("nitro","nuxt","vite","ofetch","nuxi")):
            print(f"  {k}: {v}")
PY

echo
echo "== loginKoinAdmin definitions and call sites =="
rg -n -C 6 '\bloginKoinAdmin\b|import\s+.*\bkoin/adminLogin\b|from\s+["\']\s*~\s*(/koin/adminLogin|/services/koin/adminLogin|services/koin/adminLogin)\s?["\']' . || true

echo
echo "== nitro/nuxt config occurrences =="
rg -n -C 4 'envPrefix|runtimeConfig|import\.meta\.env\.KOIN|KOIN_(API_BASE_URL|ADMIN_EMAIL|ADMIN_PASSWORD)|srcDir|serverDir|compatibilityDate' . --glob '!pnpm-lock.yaml' || true

Repository: BCSDLab/B_BOT

Length of output: 514


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== nitro.config.ts =="
cat -n nitro.config.ts

echo
echo "== package.json =="
cat -n package.json

echo
echo "== relevant env example entries =="
rg -n -C 4 'KOIN_(API_BASE_URL|ADMIN_EMAIL|ADMIN_PASSWORD)' .env.example || true

echo
echo "== loginKoinAdmin usage =="
rg -n -C 6 '\bloginKoinAdmin\b|from ["\']\s*~(/koin/adminLogin|/services/koin/adminLogin|services/koin/adminLogin)\s?["\']' . || true

echo
echo "== runtimeConfig/envPrefix occurrences (excluding lockfile) =="
rg -n -C 4 'runtimeConfig|envPrefix|import\.meta\.env\.KOIN|KOIN_(API_BASE_URL|ADMIN_EMAIL|ADMIN_PASSWORD)|process\.env\.KOIN' --glob '!pnpm-lock.yaml' . || true

Repository: BCSDLab/B_BOT

Length of output: 2436


Use private nitro runtime config for Koin admin credentials.

KOIN-admin_EMAIL and KOIN admin_PASSWORD are accessed through import.meta.env, so they are compile-time env substitution under Nitro unless runtime config is used. Store these secrets as private nitro runtime config values and read them through useRuntimeConfig() by enforcing a server-only module boundary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/koin/adminLogin.ts` around lines 6 - 8, Update the admin
credential access in the Koin login module to use private Nitro runtime
configuration via useRuntimeConfig() instead of import.meta.env, while
preserving the existing base URL behavior. Enforce a server-only module boundary
so KOIN_ADMIN_EMAIL and KOIN_ADMIN_PASSWORD remain private runtime values.

Source: MCP tools

Comment on lines +6 to +10
const baseURL = import.meta.env.KOIN_API_BASE_URL;
const email = import.meta.env.KOIN_ADMIN_EMAIL;
const password = import.meta.env.KOIN_ADMIN_PASSWORD;

const response = await $fetch<KoinAdminLoginResponse>("admin/user/login", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)adminLogin\.ts$|env\.d\.ts$|admin.*login|Koin|koin' || true

echo "== target file =="
if [ -f src/services/koin/adminLogin.ts ]; then
  cat -n src/services/koin/adminLogin.ts
fi

echo "== env declarations =="
for f in $(git ls-files '*.d.ts' | rg 'env|types'); do
  echo "--- $f"
  sed -n '1,220p' "$f"
done

echo "== other Koin env usage =="
rg -n "import\.meta\.env\.(KOIN_API_BASE_URL|KOIN_ADMIN_EMAIL|KOIN_ADMIN_PASSWORD|Koin|KOIN)" . || true

echo "== fetch behavior probe (Node) =="
node - <<'JS'
const values = [undefined, "", " ", null, "ok"];
console.log(
  JSON.stringify(
    values.map(v => ({
      value: v,
      valueAsItWouldBeSent: v,
      trimResult: v && typeof v === "string" ? v.trim() : undefined,
      filtered: !v?.trim()
    })),
    null,
    2
  )
);
JS

Repository: BCSDLab/B_BOT

Length of output: 2654


Reject missing Koin configuration before the request.

ImportMetaEnv declarations are only TypeScript types. If KOIN_API_BASE_URL, KOIN_ADMIN_EMAIL, or KOIN_ADMIN_PASSWORD is unset or blank, $fetch receives an invalid baseURL, email, or password. Validate these values before the login request and report the missing key names.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/koin/adminLogin.ts` around lines 6 - 10, Validate
KOIN_API_BASE_URL, KOIN_ADMIN_EMAIL, and KOIN_ADMIN_PASSWORD in the admin login
flow before invoking $fetch, treating unset or blank values as missing. Reject
with an error that identifies every missing configuration key, while preserving
the existing request behavior when all values are valid.

Source: MCP tools

Comment on lines +10 to +17
const response = await $fetch<KoinAdminLoginResponse>("admin/user/login", {
baseURL,
method: "POST",
body: {
email,
password,
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate relevant files =="
git ls-files | rg '(^|/)adminLogin\.ts$|(^|/)(package\.json|pnpm-lock\.yaml|package-lock\.json|yarn\.lock)$|nuxt\.config|nuxt\.config\.' || true

echo
echo "== adminLogin.ts =="
if [ -f src/services/koin/adminLogin.ts ]; then
  nl -ba src/services/koin/adminLogin.ts | sed -n '1,120p'
fi

echo
echo "== package/lock references =="
for f in package.json pnpm-lock.yaml package-lock.json yarn.lock; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n '"?ofetch"?\s*|ofetch@|/ofetch/@|node_modules/ofetch' "$f" -C 2 || true
  fi
done

echo
echo "== fetch implementations =="
rg -n '\$fetch\(|$fetch\(|timeout\s*:' src -S || true

Repository: BCSDLab/B_BOT

Length of output: 301


🌐 Web query:

unjs ofetch disabled timeout by default request timeout option ofetch

💡 Result:

In unjs/ofetch, the timeout feature is disabled by default [1][2][3]. You can enable request timeouts by providing a timeout option in milliseconds to your fetch call [4][1][5]. When this option is set, ofetch uses the native AbortSignal.timeout() API to automatically abort the request if it exceeds the specified duration [4]. Key details regarding the timeout configuration include: - Global Configuration: You can set a default timeout for all requests created via $fetch.create() [4]. - Per-Request Override: You can override or disable the timeout for individual requests by passing a new value or undefined to the timeout option [4]. - Error Handling: If a request times out, it will be aborted with a TimeoutError [4]. - Retries: Note that the timeout functionality does not prevent retries by default; if retries are enabled, ofetch will retry the request even after a timeout occurs [4]. To prevent this, you should set retry: false or retry: 0 [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== adminLogin.ts =="
awk '{printf "%6d  %s\n", NR, $0}' src/services/koin/adminLogin.ts | sed -n '1,120p'

echo
echo "== package.json relevant deps =="
node - <<'JS'
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const keys = ['dependencies','devDependencies','peerDependencies','optionalDependencies'];
for (const k of keys) {
  console.log(`-- ${k} --`);
  if (pkg[k]) {
    for (const name of Object.keys(pkg[k]).filter(n => /nuxt|ofetch|fetch/i.test(n))) {
      console.log(`${name}: ${pkg[k][name]}`);
    }
  }
}
JS

echo
echo "== lockfile ofetch versions =="
awk '{printf "%6d  %s\n", NR, $0}' pnpm-lock.yaml | rg -n -C 3 '(^|/npm/ofetch|/ofetch@|/@nuxt/deeplink:|ofetch)' || true

echo
echo "== fetch and timeout usage in src =="
rg -n '\$fetch\(|fetch\(|\.timeout\(|timeout\s*:' src -S || true

Repository: BCSDLab/B_BOT

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== git diff focused on adminLogin =="
git diff -- src/services/koin/adminLogin.ts | sed -n '1,160p'

echo
echo "== any ofetch lock entries =="
arg {printf "%6d  %s\n", NR, $0}' pnpm-lock.yaml | rg -n -C 2 'ofetch@|/npm/ofetch|unjs/ofetch|^  /:|^  npm:|^  file:' || true

echo
echo "== local timeout usage =="
rg -n '\btimeout\s*:\s*[0-9]+|AbortSignal\.timeout|timeout' src package.json pnpm-lock.yaml -S || true

Repository: BCSDLab/B_BOT

Length of output: 280


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== focused git diff for adminLogin =="
git diff -- src/services/koin/adminLogin.ts | sed -n '1,160p' || true

echo
echo "== focused lockfile entries for ofetch =="
rg -n -C 3 'ofetch@|/npm/ofetch|unjs/ofetch|`@nuxt/deep`' pnpm-lock.yaml || true

echo
echo "== focused timeout usage =="
rg -n '\btimeout\s*:\s*[0-9]+|AbortSignal\.timeout|timeout' src package.json pnpm-lock.yaml -S || true

Repository: BCSDLab/B_BOT

Length of output: 1141


Bound the Koin login request.

$fetch has no timeout configured here, and ofetch disables timeouts by default. Set a bounded timeout that matches the service SLA and treat an abort as a failed login. ofetch is locked as ofetch@1.4.1, so the per-request timeout option applies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/koin/adminLogin.ts` around lines 10 - 17, Update the $fetch call
in the admin login flow to include the service SLA as its per-request timeout,
using ofetch’s timeout option. Ensure timeout-triggered aborts are handled as
failed logins through the existing error path.

Source: MCP tools

@ff1451 ff1451 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

고생하셨습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants