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
33 changes: 31 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,37 @@ concurrency:
cancel-in-progress: true

jobs:
test:
name: ci
ui:
name: ui
runs-on: ubuntu-24.04-arm
defaults:
run:
working-directory: ui/app
steps:
- name: checkout code
uses: actions/checkout@v6

- name: setup node
uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm
cache-dependency-path: ui/app/package-lock.json

- name: install ui dependencies
run: npm ci

- name: run ui type check
run: npm run typecheck

- name: build ui
run: npm run build

- name: verify public discovery artifacts
run: npm run verify:discovery

backend:
name: backend
runs-on: ubuntu-24.04-arm
steps:
- name: checkout code
Expand Down
67 changes: 67 additions & 0 deletions ui/app/docs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" />
<meta name="robots" content="index, follow" />
<meta
name="description"
content="한국어 욕설·비속어 검출 API의 인증, 요청 형식, 처리 모드, 오류 모델과 OpenAPI 명세를 확인하세요."
/>
<link rel="canonical" href="https://developers.kr-filter.com/docs" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="ko_KR" />
<meta property="og:site_name" content="말조심하세욧" />
<meta property="og:title" content="한국어 텍스트 처리 API 문서 | 말조심하세욧" />
<meta
property="og:description"
content="한국어 욕설·비속어 검출 API의 인증, 요청 형식, 처리 모드, 오류 모델과 OpenAPI 명세를 확인하세요."
/>
<meta property="og:url" content="https://developers.kr-filter.com/docs" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="한국어 텍스트 처리 API 문서 | 말조심하세욧" />
<meta
name="twitter:description"
content="한국어 욕설·비속어 검출 API의 인증, 요청 형식, 처리 모드, 오류 모델과 OpenAPI 명세를 확인하세요."
/>
<title>한국어 텍스트 처리 API 문서 | 말조심하세욧</title>
</head>
<body>
<div id="root">
<div class="app-shell" data-static-fallback>
<header class="global-header">
<a class="brand" href="/">
<strong>말조심하세욧</strong>
<span>한국어 욕설 필터 API</span>
</a>
<div class="static-public-actions">
<nav aria-label="공개 메뉴">
<a href="/">소개</a>
<a aria-current="page" href="/docs">API 문서</a>
</nav>
<a class="login-link" href="/login">로그인</a>
</div>
</header>
<main id="main-content">
<article class="static-docs-page page-width">
<p class="eyebrow">API documentation</p>
<h1>한국어 텍스트 처리 API 문서</h1>
<p class="lead">현재 제공되는 욕설·비속어 검출 기능의 인증, 요청 형식, 처리 모드와 오류 모델을 확인할 수 있습니다.</p>
<nav class="static-docs-links" aria-label="API 원문 문서">
<a href="https://api.kr-filter.com/overview.md">
<strong>API 개요</strong>
<span>인증, 처리 모드와 응답 정책을 확인합니다.</span>
</a>
<a href="https://api.kr-filter.com/openapi.json">
<strong>OpenAPI 명세</strong>
<span>기계가 읽을 수 있는 전체 API 계약을 확인합니다.</span>
</a>
</nav>
</article>
</main>
</div>
</div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
84 changes: 81 additions & 3 deletions ui/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,89 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" />
<meta name="description" content="한국어 욕설 필터 API 대시보드 프로토타입" />
<title>말조심하세욧</title>
<meta name="robots" content="index, follow" />
<meta
name="description"
content="한국어 문장의 욕설과 비속어를 검출하고 QUICK, NORMAL, FILTER 모드로 확인하거나 마스킹하는 REST API입니다."
/>
<link rel="canonical" href="https://developers.kr-filter.com/" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="ko_KR" />
<meta property="og:site_name" content="말조심하세욧" />
<meta property="og:title" content="한국어 욕설·비속어 필터 API | 말조심하세욧" />
<meta
property="og:description"
content="한국어 문장의 욕설과 비속어를 검출하고 필요한 방식으로 확인하거나 마스킹하는 REST API입니다."
/>
<meta property="og:url" content="https://developers.kr-filter.com/" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="한국어 욕설·비속어 필터 API | 말조심하세욧" />
<meta
name="twitter:description"
content="한국어 문장의 욕설과 비속어를 검출하고 필요한 방식으로 확인하거나 마스킹하는 REST API입니다."
/>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebSite",
"@id": "https://developers.kr-filter.com/#website",
"url": "https://developers.kr-filter.com/",
"name": "말조심하세욧",
"alternateName": "한국어 욕설 필터 API",
"inLanguage": "ko-KR"
},
{
"@type": "SoftwareApplication",
"@id": "https://developers.kr-filter.com/#application",
"name": "말조심하세욧",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Any",
"url": "https://developers.kr-filter.com/",
"description": "한국어 문장의 욕설과 비속어를 검출하고 필요한 방식으로 확인하거나 마스킹하는 REST API입니다.",
"inLanguage": "ko-KR"
}
]
}
</script>
<title>한국어 욕설·비속어 필터 API | 말조심하세욧</title>
</head>
<body>
<div id="root"></div>
<div id="root">
<div class="app-shell" data-static-fallback>
<header class="global-header">
<a class="brand" href="/">
<strong>말조심하세욧</strong>
<span>한국어 욕설 필터 API</span>
</a>
<div class="static-public-actions">
<nav aria-label="공개 메뉴">
<a aria-current="page" href="/">소개</a>
<a href="/docs">API 문서</a>
</nav>
<a class="login-link" href="/login">로그인</a>
</div>
</header>
<main id="main-content">
<div class="overview-page">
<section class="intro-page page-width">
<p class="eyebrow">Korean profanity filter API</p>
<h1>한국어 욕설·비속어<br />필터 API</h1>
<p class="lead">한국어 문장의 욕설과 비속어를 검출하고 필요한 방식으로 확인하거나 마스킹하는 API입니다.</p>
<div class="intro-actions">
<a class="primary-action" href="/login">로그인하여 시작</a>
<a class="text-action" href="/docs">API 문서 보기</a>
</div>
<div class="static-api-links" aria-label="공개 API 자료">
<a href="https://api.kr-filter.com/overview.md">API 개요</a>
<a href="https://api.kr-filter.com/openapi.json">OpenAPI 명세</a>
</div>
</section>
</div>
</main>
</div>
</div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
8 changes: 8 additions & 0 deletions ui/app/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ server {
root /usr/share/nginx/html;
index index.html;

location = /docs {
try_files /docs/index.html =404;
}

location = /docs/ {
return 308 /docs;
}

location / {
try_files $uri $uri/ /index.html;
}
Expand Down
3 changes: 2 additions & 1 deletion ui/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"dev": "vite",
"build": "tsc --noEmit && vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"verify:discovery": "node scripts/verify-public-discovery.mjs"
},
"dependencies": {
"@fontsource-variable/noto-sans-kr": "^5.2.10",
Expand Down
19 changes: 19 additions & 0 deletions ui/app/public/llms.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 말조심하세욧

> 한국어 문장의 욕설과 비속어를 검출하고 필요한 방식으로 확인하거나 마스킹하는 REST API입니다.

## Public pages

- [서비스 소개](https://developers.kr-filter.com/): 현재 제공 기능과 API Key 연동 흐름
- [API 문서](https://developers.kr-filter.com/docs): 인증, 처리 모드, 응답 정책과 API별 사용법

## Machine-readable API documents

- [API 개요](https://api.kr-filter.com/overview.md): 인증, 처리 모드와 오류 모델
- [OpenAPI 명세](https://api.kr-filter.com/openapi.json): 전체 공개 API 계약

## Current capabilities

- API Key를 사용하는 REST API
- QUICK, NORMAL, FILTER 처리 모드
- 동기 처리와 callback URL 기반 비동기 처리
14 changes: 14 additions & 0 deletions ui/app/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
User-agent: *
Allow: /
Disallow: /app
Disallow: /login

User-agent: OAI-SearchBot
Allow: /
Disallow: /app
Disallow: /login

User-agent: GPTBot
Disallow: /

Sitemap: https://developers.kr-filter.com/sitemap.xml
9 changes: 9 additions & 0 deletions ui/app/public/sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://developers.kr-filter.com/</loc>
</url>
<url>
<loc>https://developers.kr-filter.com/docs</loc>
</url>
</urlset>
65 changes: 65 additions & 0 deletions ui/app/scripts/verify-public-discovery.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const appDirectory = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const distDirectory = resolve(appDirectory, "dist");

async function readDistFile(path) {
return readFile(resolve(distDirectory, path), "utf8");
}

function titleOf(html) {
return html.match(/<title>([^<]+)<\/title>/)?.[1] ?? "";
}

function canonicalOf(html) {
return html.match(/<link[^>]+rel="canonical"[^>]+href="([^"]+)"/)?.[1] ?? "";
}

const [landing, docs, robots, sitemap, llms] = await Promise.all([
readDistFile("index.html"),
readDistFile("docs/index.html"),
readDistFile("robots.txt"),
readDistFile("sitemap.xml"),
readDistFile("llms.txt"),
]);

assert.equal(titleOf(landing), "한국어 욕설·비속어 필터 API | 말조심하세욧");
assert.equal(titleOf(docs), "한국어 텍스트 처리 API 문서 | 말조심하세욧");
assert.notEqual(titleOf(landing), titleOf(docs));
assert.equal(canonicalOf(landing), "https://developers.kr-filter.com/");
assert.equal(canonicalOf(docs), "https://developers.kr-filter.com/docs");
assert.match(landing, /<h1>한국어 욕설·비속어<br\s*\/?\s*>필터 API<\/h1>/);
assert.match(docs, /<h1>한국어 텍스트 처리 API 문서<\/h1>/);
assert.match(docs, /href="https:\/\/api\.kr-filter\.com\/overview\.md"/);
assert.match(docs, /href="https:\/\/api\.kr-filter\.com\/openapi\.json"/);

const jsonLdBlocks = [...landing.matchAll(/<script type="application\/ld\+json">\s*([\s\S]*?)\s*<\/script>/g)];
assert.ok(jsonLdBlocks.length > 0, "landing JSON-LD is required");
const jsonLdTypes = jsonLdBlocks
.flatMap((match) => {
const document = JSON.parse(match[1]);
return Array.isArray(document["@graph"]) ? document["@graph"] : [document];
})
.map((item) => item["@type"]);
assert.ok(jsonLdTypes.includes("WebSite"));
assert.ok(jsonLdTypes.includes("SoftwareApplication"));

assert.doesNotMatch(robots, /<html/i);
assert.match(robots, /User-agent: OAI-SearchBot[\s\S]*?Allow: \//);
assert.match(robots, /User-agent: GPTBot\s+Disallow: \//);
assert.match(robots, /Sitemap: https:\/\/developers\.kr-filter\.com\/sitemap\.xml/);

assert.match(sitemap, /^<\?xml version="1\.0" encoding="UTF-8"\?>/);
const sitemapLocations = [...sitemap.matchAll(/<loc>([^<]+)<\/loc>/g)].map((match) => match[1]);
assert.deepEqual(sitemapLocations, ["https://developers.kr-filter.com/", "https://developers.kr-filter.com/docs"]);

assert.doesNotMatch(llms, /<html/i);
assert.match(llms, /https:\/\/developers\.kr-filter\.com\/docs/);
assert.match(llms, /https:\/\/api\.kr-filter\.com\/overview\.md/);
assert.match(llms, /https:\/\/api\.kr-filter\.com\/openapi\.json/);
assert.doesNotMatch(`${landing}\n${docs}\n${llms}`, /입력을 저장하지|익명 학습|privacy-first|private/i);

console.log("Public discovery artifacts verified: 5 files");
Loading
Loading