feat(profile): 프로필에 '한줄 소개' 추가 — 본인만 인라인 편집

- users.bio 컬럼(멱등) + db.setBio / getUserProfile 조회 포함
- POST /profile/bio (본인만, fetch JSON / 폼 POST 폴백)
- 프로필 히어로: 이름·사번 한 줄로 합치고 한줄 소개를 3줄째로 배치
- 본인은 연필 버튼으로 인라인 편집(저장/취소·Esc), 최대 80자
- word-break: keep-all 로 한글 단어 중간 잘림 방지

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 13:43:49 +09:00
parent 0ed41849ca
commit 84ce8567e7
6 changed files with 97 additions and 5 deletions

View File

@@ -25,6 +25,11 @@ export async function setAvatar({ sabun, buf, mime }) {
);
}
// 한줄 소개 저장(본인). 80자 이내로 잘라 보관.
export async function setBio({ sabun, bio }) {
await q(`UPDATE users SET bio=$2 WHERE sabun=$1`, [sabun, String(bio || "").slice(0, 80)]);
}
// 프로필 사진 조회 → { avatar(Buffer|null), avatar_mime }. 없으면 호출부에서 identicon 폴백.
export async function getAvatar(sabun) {
const { rows } = await q(`SELECT avatar, avatar_mime FROM users WHERE sabun=$1`, [sabun]);
@@ -34,7 +39,7 @@ export async function getAvatar(sabun) {
// 프로필 페이지용 사용자 정보 + 프로젝트 수(공개만 카운트가 아니라 전체 — 뷰에서 필요 시 구분).
export async function getUserProfile(sabun) {
const { rows } = await q(
`SELECT u.sabun, u.name, u.email, u.created_at,
`SELECT u.sabun, u.name, u.email, u.created_at, u.bio,
(u.avatar IS NOT NULL) AS has_avatar,
(SELECT count(*) FROM projects p WHERE p.owner_sabun=u.sabun)::int AS project_count
FROM users u WHERE u.sabun=$1`,

View File

@@ -122,6 +122,16 @@ app.post(
}
);
// --- 한줄 소개 저장(본인) ---
// JS 있으면 fetch(JSON), 없으면 폼 POST → 프로필로 리다이렉트(점진적 향상).
app.post("/profile/bio", requireAuth, async (req, res) => {
const sabun = req.session.user.sabun;
const bio = String(req.body.bio || "").trim().slice(0, 80);
await db.setBio({ sabun, bio });
if (isAjax(req)) return res.json({ ok: true, bio });
res.redirect("/u/" + encodeURIComponent(sabun));
});
// --- 프로필 페이지 (본인/타인) — 프로필 + 해당 사용자의 프로젝트 목록 ---
app.get("/u/:sabun", requireAuth, async (req, res) => {
const me = req.session.user.sabun;