Require guest contact for uploads
This commit is contained in:
@@ -198,6 +198,44 @@ h1 {
|
|||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.guest-fields {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 0 0 14px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-fields label {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
color: #45615c;
|
||||||
|
font-family: "Apple SD Gothic Neo", "Malgun Gothic", system-ui, sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-fields input {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 46px;
|
||||||
|
border: 1px solid rgba(137, 166, 158, 0.38);
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 0 14px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #3f3f3f;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-fields input::placeholder {
|
||||||
|
color: #9aa4a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-fields input:focus {
|
||||||
|
outline: 2px solid rgba(137, 170, 163, 0.6);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.file-picker input[type="file"] {
|
.file-picker input[type="file"] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
|||||||
@@ -22,6 +22,17 @@
|
|||||||
<form class="upload-panel" method="post" action="/r/{{slug}}" enctype="multipart/form-data">
|
<form class="upload-panel" method="post" action="/r/{{slug}}" enctype="multipart/form-data">
|
||||||
<p class="upload-panel__today">오늘의 순간</p>
|
<p class="upload-panel__today">오늘의 순간</p>
|
||||||
|
|
||||||
|
<div class="guest-fields" aria-label="업로드하시는 분 정보">
|
||||||
|
<label>
|
||||||
|
<span>성함</span>
|
||||||
|
<input type="text" name="guestName" autocomplete="name" placeholder="성함을 입력해 주세요." required>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>연락처</span>
|
||||||
|
<input type="tel" name="guestPhone" autocomplete="tel" inputmode="tel" placeholder="연락처를 입력해 주세요." required>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button class="upload-button" type="submit" aria-label="업로드" hidden>
|
<button class="upload-button" type="submit" aria-label="업로드" hidden>
|
||||||
<span>업로드하기</span>
|
<span>업로드하기</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -52,6 +63,8 @@
|
|||||||
<script>
|
<script>
|
||||||
document.querySelectorAll(".upload-panel").forEach((form) => {
|
document.querySelectorAll(".upload-panel").forEach((form) => {
|
||||||
const input = form.querySelector('input[type="file"][name="files"]');
|
const input = form.querySelector('input[type="file"][name="files"]');
|
||||||
|
const guestName = form.querySelector('input[name="guestName"]');
|
||||||
|
const guestPhone = form.querySelector('input[name="guestPhone"]');
|
||||||
const submit = form.querySelector('button[type="submit"][aria-label="업로드"]');
|
const submit = form.querySelector('button[type="submit"][aria-label="업로드"]');
|
||||||
const summary = form.querySelector(".file-summary");
|
const summary = form.querySelector(".file-summary");
|
||||||
const empty = form.querySelector(".file-summary__empty");
|
const empty = form.querySelector(".file-summary__empty");
|
||||||
@@ -143,10 +156,15 @@
|
|||||||
|
|
||||||
form.addEventListener("submit", async (event) => {
|
form.addEventListener("submit", async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
if (!form.reportValidity()) return;
|
||||||
if (!input.files || input.files.length === 0) {
|
if (!input.files || input.files.length === 0) {
|
||||||
setStatus("먼저 사진을 선택해 주세요.", "error");
|
setStatus("먼저 사진을 선택해 주세요.", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!guestName.value.trim() || !guestPhone.value.trim()) {
|
||||||
|
setStatus("성함과 연락처를 모두 입력해 주세요.", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
submit.disabled = true;
|
submit.disabled = true;
|
||||||
form.dataset.uploading = "true";
|
form.dataset.uploading = "true";
|
||||||
|
|||||||
@@ -55,6 +55,15 @@ function sanitizeFileName(value) {
|
|||||||
return `${base}${ext}`;
|
return `${base}${ext}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanGuestField(value) {
|
||||||
|
return String(value || "")
|
||||||
|
.normalize("NFKC")
|
||||||
|
.replace(/[\r\n\t]/g, " ")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim()
|
||||||
|
.slice(0, 80);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeExts(value) {
|
function normalizeExts(value) {
|
||||||
return String(value || "")
|
return String(value || "")
|
||||||
.split(/[\s,;]+/)
|
.split(/[\s,;]+/)
|
||||||
@@ -204,6 +213,10 @@ async function uploadPage(request) {
|
|||||||
<section class="panel">
|
<section class="panel">
|
||||||
<h1>${values.title}</h1>
|
<h1>${values.title}</h1>
|
||||||
<form method="post" action="/r/${values.slug}" enctype="multipart/form-data">
|
<form method="post" action="/r/${values.slug}" enctype="multipart/form-data">
|
||||||
|
<label>성함</label>
|
||||||
|
<input type="text" name="guestName" autocomplete="name" required>
|
||||||
|
<label>연락처</label>
|
||||||
|
<input type="tel" name="guestPhone" autocomplete="tel" required>
|
||||||
<div class="upload-box">
|
<div class="upload-box">
|
||||||
<label>사진/영상 선택</label>
|
<label>사진/영상 선택</label>
|
||||||
<input type="file" name="files" accept="${values.accept}" multiple required>
|
<input type="file" name="files" accept="${values.accept}" multiple required>
|
||||||
@@ -244,6 +257,20 @@ async function moveUploadedFiles(files, targetDir) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function appendUploadMetadata(targetDir, { guestName, guestPhone, files }) {
|
||||||
|
const entry = {
|
||||||
|
uploadedAt: new Date().toISOString(),
|
||||||
|
guestName,
|
||||||
|
guestPhone,
|
||||||
|
files: files.map((file) => ({
|
||||||
|
storedName: file.filename,
|
||||||
|
originalName: file.originalname,
|
||||||
|
size: file.size
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
await fsp.appendFile(path.join(targetDir, "upload-metadata.jsonl"), `${JSON.stringify(entry)}\n`, "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
app.get("/", (req, res) => res.redirect("/admin"));
|
app.get("/", (req, res) => res.redirect("/admin"));
|
||||||
|
|
||||||
app.get("/admin/login", (req, res) => {
|
app.get("/admin/login", (req, res) => {
|
||||||
@@ -417,6 +444,18 @@ app.post("/r/:slug", async (req, res) => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const files = req.files || [];
|
const files = req.files || [];
|
||||||
|
const guestName = cleanGuestField(req.body.guestName);
|
||||||
|
const guestPhone = cleanGuestField(req.body.guestPhone);
|
||||||
|
if (!guestName || !guestPhone) {
|
||||||
|
await removeFiles(files);
|
||||||
|
const message = "성함과 연락처를 모두 입력해 주세요.";
|
||||||
|
if (wantsJson(req)) {
|
||||||
|
res.status(400).json({ ok: false, message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.status(400).send(uploadResultPage(request, { heading: "업로드 실패", message, isError: true }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const targetDir = path.join(uploadsDir, request.folderName);
|
const targetDir = path.join(uploadsDir, request.folderName);
|
||||||
const existingBytes = await directorySize(targetDir);
|
const existingBytes = await directorySize(targetDir);
|
||||||
const incomingBytes = files.reduce((total, file) => total + file.size, 0);
|
const incomingBytes = files.reduce((total, file) => total + file.size, 0);
|
||||||
@@ -431,6 +470,7 @@ app.post("/r/:slug", async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await moveUploadedFiles(files, targetDir);
|
await moveUploadedFiles(files, targetDir);
|
||||||
|
await appendUploadMetadata(targetDir, { guestName, guestPhone, files });
|
||||||
if (wantsJson(req)) {
|
if (wantsJson(req)) {
|
||||||
res.json({ ok: true, count: files.length });
|
res.json({ ok: true, count: files.length });
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user