AI DEV portal 초기 커밋

Express+EJS+Postgres 사내 개발 포털.
- Keycloak OIDC(사번 로그인), GitHub 감성 UI
- 사이트 모음 + 프로젝트 공유/코멘트/스타
- DB: OpenEverest appdb portal schema (portal_app role)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
0310700
2026-06-22 16:16:40 +09:00
commit 9eb3fd1b4c
18 changed files with 1919 additions and 0 deletions

17
views/_header.ejs Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= brand %></title>
<link rel="stylesheet" href="/public/style.css" />
</head>
<body>
<div class="header">
<a class="brand" href="/"><span>🧠</span><%= brand %></a>
<div class="spacer"></div>
<% if (user) { %>
<span class="who"><%= user.name %> (<%= user.sabun %>)</span>
<a class="navlink" href="/logout">로그아웃</a>
<% } %>
</div>

79
views/dashboard.ejs Normal file
View File

@@ -0,0 +1,79 @@
<%- include("_header") %>
<div class="container">
<div class="layout">
<!-- 메인: 사이트 모음 + 공유 프로젝트 -->
<div>
<div class="box">
<div class="box-header">🔗 개발 사이트 바로가기</div>
<div class="box-body">
<% siteGroups.forEach(function(g){ %>
<div class="group-title"><%= g.group %></div>
<div class="site-grid">
<% g.sites.forEach(function(s){ %>
<a class="site-card" href="<%= s.url %>" target="_blank" rel="noopener">
<span class="ico"><%= s.icon %></span>
<span>
<span class="name"><%= s.name %></span><br/>
<span class="desc"><%= s.desc %></span>
</span>
</a>
<% }); %>
</div>
<% }); %>
</div>
</div>
<div class="box" style="margin-top:24px">
<div class="box-header">📂 공유 프로젝트</div>
<% if (projects.length === 0) { %>
<div class="box-body muted">아직 공유된 프로젝트가 없습니다. 오른쪽에서 추가해 보세요.</div>
<% } else { %>
<% projects.forEach(function(p){ %>
<div class="proj-item">
<a class="proj-title" href="/projects/<%= p.id %>"><%= p.title %></a>
<div class="proj-meta">
<%= p.owner_name %> · <%= new Date(p.updated_at).toLocaleDateString("ko-KR") %>
&nbsp; <span class="counter">⭐ <%= p.star_count %></span>
&nbsp; <span class="counter">💬 <%= p.comment_count %></span>
</div>
<% if (p.description) { %><div class="muted" style="margin-top:6px"><%= p.description %></div><% } %>
<% if (p.tags) { %>
<div style="margin-top:8px">
<% p.tags.split(",").map(t=>t.trim()).filter(Boolean).forEach(function(t){ %>
<span class="tag"><%= t %></span>
<% }); %>
</div>
<% } %>
</div>
<% }); %>
<% } %>
</div>
</div>
<!-- 사이드바: 프로젝트 공유 폼 -->
<div>
<div class="box">
<div class="box-header"> 프로젝트 공유</div>
<div class="box-body">
<form method="post" action="/projects">
<label>제목 *</label>
<input type="text" name="title" required placeholder="내 앱 이름" />
<label>설명</label>
<textarea name="description" placeholder="무엇을 만들었나요?"></textarea>
<label>Repo URL</label>
<input type="url" name="repo_url" placeholder="https://gitea.bokdev.in/..." />
<label>앱 URL</label>
<input type="url" name="app_url" placeholder="https://....apps.bokdev.in" />
<label>태그 (콤마 구분)</label>
<input type="text" name="tags" placeholder="node, ai" />
<div style="margin-top:16px">
<button class="btn btn-primary" style="width:100%" type="submit">공유하기</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

14
views/login.ejs Normal file
View File

@@ -0,0 +1,14 @@
<%- include("_header") %>
<div class="login-wrap">
<div class="box login-card">
<div class="login-logo">🧠</div>
<div class="login-title"><%= brand %></div>
<% if (typeof error !== "undefined" && error) { %>
<div class="alert"><%= error %></div>
<% } %>
<a class="btn btn-primary" style="width:100%" href="/auth/login">Keycloak 으로 로그인</a>
<p class="muted" style="margin-top:16px">사번으로 로그인합니다. (사내 SSO)</p>
</div>
</div>
</body>
</html>

46
views/project.ejs Normal file
View File

@@ -0,0 +1,46 @@
<%- include("_header") %>
<div class="container" style="max-width:760px">
<a href="/" class="muted">← 대시보드</a>
<div class="box" style="margin-top:12px">
<div class="box-body">
<h2 style="margin:0 0 4px"><%= project.title %></h2>
<div class="proj-meta"><%= project.owner_name %> 님이 공유 · <%= new Date(project.created_at).toLocaleDateString("ko-KR") %></div>
<% if (project.description) { %><p style="margin-top:12px; white-space:pre-wrap"><%= project.description %></p><% } %>
<div style="margin-top:12px; display:flex; gap:8px; align-items:center; flex-wrap:wrap">
<% if (project.repo_url) { %><a class="btn btn-sm" href="<%= project.repo_url %>" target="_blank">🍵 Repo</a><% } %>
<% if (project.app_url) { %><a class="btn btn-sm" href="<%= project.app_url %>" target="_blank">🚀 앱 열기</a><% } %>
<form method="post" action="/projects/<%= project.id %>/star" style="display:inline">
<button class="btn btn-sm" type="submit"><%= starred ? "★ 스타 취소" : "☆ 스타" %> (<%= project.star_count %>)</button>
</form>
</div>
<% if (project.tags) { %>
<div style="margin-top:12px">
<% project.tags.split(",").map(t=>t.trim()).filter(Boolean).forEach(function(t){ %>
<span class="tag"><%= t %></span>
<% }); %>
</div>
<% } %>
</div>
</div>
<h3 style="margin:24px 0 12px">💬 코멘트 (<%= comments.length %>)</h3>
<% comments.forEach(function(c){ %>
<div class="comment">
<div class="comment-head"><strong><%= c.author_name %></strong> · <%= new Date(c.created_at).toLocaleString("ko-KR") %></div>
<div class="comment-body"><%= c.body %></div>
</div>
<% }); %>
<div class="box" style="margin-top:16px">
<div class="box-body">
<form method="post" action="/projects/<%= project.id %>/comments">
<textarea name="body" placeholder="코멘트를 남겨보세요" required></textarea>
<div style="margin-top:8px; text-align:right">
<button class="btn btn-primary" type="submit">코멘트 등록</button>
</div>
</form>
</div>
</div>
</div>
</body>
</html>