利用規約の追加とエラーハンドリング、トップページの修正

This commit is contained in:
ntki72 2024-12-26 22:14:43 +09:00
parent c804494541
commit 29cebe239f
22 changed files with 540 additions and 214 deletions

View File

@ -16,6 +16,7 @@ services:
TZ: Asia/Tokyo
volumes:
- ./public/thumbnails:/app/public/thumbnails
- ./public/css:/app/public/css
db:
image: mysql:8.0
volumes:

31
public/css/errors.css Normal file
View File

@ -0,0 +1,31 @@
.error-container {
text-align: center;
margin-top: 100px;
font-family: Arial, sans-serif;
}
.error-container h1 {
font-size: 5em;
margin-bottom: 20px;
color: #ff6b6b;
}
.error-container p {
font-size: 1.5em;
margin-bottom: 30px;
color: #333;
}
.home-link {
display: inline-block;
padding: 10px 20px;
background-color: #007bff;
color: #fff;
text-decoration: none;
border-radius: 5px;
font-size: 1em;
}
.home-link:hover {
background-color: #0056b3;
}

48
public/css/header.css Normal file
View File

@ -0,0 +1,48 @@
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 60px;
background-color: #333;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 1000;
box-sizing: border-box;
}
.header-left a {
color: white;
font-size: 1.5em;
font-weight: bold;
text-decoration: none;
white-space: nowrap;
}
.header-right {
display: flex;
gap: 20px;
margin-right: 10px;
white-space: nowrap;
}
.header-link {
color: white;
text-decoration: none;
font-size: 1em;
transition: color 0.2s;
}
.header-link:hover {
color: #ddd;
}
@media (max-width: 768px) {
.header-right {
margin-right: 5px;
}
}

View File

@ -7,6 +7,7 @@
font-size: 1.5em;
margin: 20px 0;
font-weight: bold;
min-width: 550px;
}
.banners {
@ -14,7 +15,6 @@
flex-wrap: wrap;
gap: 20px;
}
.banner {
display: flex;
flex-direction: row;
@ -25,7 +25,9 @@
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
width: calc(50% - 10px); /* 2列になるように調整 */
flex: 1 1 calc(50% - 13px);
max-width: calc(50% - 13px);
min-width: 550px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
@ -57,3 +59,11 @@
font-weight: bold;
color: #555;
}
.header-spacer {
height: 60px;
}
.main-content {
margin-top: 20px;
}

47
public/css/terms.css Normal file
View File

@ -0,0 +1,47 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.terms-container {
max-width: 800px;
margin: 80px auto; /* ヘッダーの高さを考慮して調整 */
padding: 20px;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 2em;
margin-bottom: 20px;
text-align: center;
color: #333333;
}
h2 {
font-size: 1.5em;
margin-top: 20px;
margin-bottom: 10px;
color: #444444;
border-bottom: 2px solid #ddd;
padding-bottom: 5px;
}
p {
font-size: 1em;
line-height: 1.6;
margin-bottom: 15px;
color: #555555;
}
a {
color: #0066cc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}

View File

@ -3,6 +3,7 @@ import cron from "node-cron";
import express from "express";
import apiRoutes from "./routes/api";
import adminRoutes from "./routes/admin";
import userRoutes from "./routes/user";
import basicAuth from "express-basic-auth";
import { runMigrations } from "./utils/migrate";
import methodOverride from "method-override";
@ -46,16 +47,33 @@ const startServer = async () => {
// 管理画面ルート
app.use("/admin", adminRoutes);
// ユーザー向けルート
app.use("/", userRoutes);
// 管理画面 API
app.use("/admin/api", apiRoutes);
// ユーザー向けルート
// トップページ
app.use("/", scheduleRoutes);
// 静的ファイルの提供
app.use("/thumbnails", express.static("public/thumbnails"));
app.use("/css", express.static("public/css"));
app.use("/js", express.static("public/js"));
// エラーハンドリング
app.use((req, res, next) => {
res.status(404).render("errors/404");
});
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error("Unhandled error:", err);
if (err.message.includes("permission")) {
res.status(403).render("errors/403");
} else if (res.statusCode === 503) {
res.status(503).render("errors/503");
} else {
res.status(500).render("errors/500");
}
});
// サーバー起動
app.listen(WEB_PORT, () => {

View File

@ -1,5 +1,6 @@
import express from "express";
import { LiveSchedule, Channel } from "../models";
import { Op } from "sequelize";
import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
@ -11,8 +12,14 @@ const router = express.Router();
router.get("/", async (req, res) => {
try {
const now = dayjs().tz("Asia/Tokyo").startOf("day"); // 当日0時
const liveSchedules = await LiveSchedule.findAll({
include: [{ model: Channel }],
where: {
start_time: {
[Op.gte]: now.toDate(), // Sequelize の Op を使用
},
},
order: [["start_time", "ASC"]],
});

14
src/routes/user.ts Normal file
View File

@ -0,0 +1,14 @@
import express from "express";
import scheduleRoutes from "./schedule";
const router = express.Router();
// スケジュールページ(トップページ)
router.use("/", scheduleRoutes);
// 利用規約ページ
router.get("/terms", (req, res) => {
res.render("terms");
});
export default router;

View File

@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Channel List</title>
</head>
<%- include("../partials/head") %>
<body>
<h1>Channel List</h1>
<a href="/admin/channels/new">Add New Channel</a>

View File

@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
</head>
<%- include("../partials/head") %>
<body>
<h1><%= title %></h1>
<form action="/admin/channels/<%= channel.id %>?_method=PUT" method="POST">

View File

@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
</head>
<%- include("../partials/head") %>
<body>
<h1><%= title %></h1>
<p>Welcome to the Admin Dashboard!</p>

View File

@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add New Channel</title>
</head>
<%- include("../partials/head") %>
<body>
<h1>Add New Channel</h1>
<form action="/admin/channels/new" method="POST">

View File

@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
</head>
<%- include("../partials/head") %>
<body>
<h1><%= title %></h1>
<h2>Channel: <%= channel.name %> (<%= channel.youtube_id %>)</h2>

16
views/errors/400.ejs Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/errors.css">
</head>
<%- include("../partials/head") %>
<body>
<div class="error-container">
<h1>400</h1>
<p>送信されたリクエストに問題がありました。</p>
<a href="/" class="home-link">トップページへ戻る</a>
</div>
</body>
</html>

16
views/errors/403.ejs Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/errors.css">
</head>
<%- include("../partials/head") %>
<body>
<div class="error-container">
<h1>403</h1>
<p>このページへのアクセスが拒否されました。</p>
<a href="/" class="home-link">トップページへ戻る</a>
</div>
</body>
</html>

16
views/errors/404.ejs Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/errors.css">
</head>
<%- include("../partials/head") %>
<body>
<div class="error-container">
<h1>404</h1>
<p>お探しのページが見つかりませんでした。</p>
<a href="/" class="home-link">トップページへ戻る</a>
</div>
</body>
</html>

16
views/errors/500.ejs Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/errors.css">
</head>
<%- include("../partials/head") %>
<body>
<div class="error-container">
<h1>500</h1>
<p>予期せぬエラーが発生しました。少し時間をおいて再度お試しください。</p>
<a href="/" class="home-link">トップページへ戻る</a>
</div>
</body>
</html>

16
views/errors/503.ejs Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/errors.css">
</head>
<%- include("../partials/head") %>
<body>
<div class="error-container">
<h1>503</h1>
<p>現在サーバーが一時的に利用できません。時間をおいて再度お試しください。</p>
<a href="/" class="home-link">トップページへ戻る</a>
</div>
</body>
</html>

9
views/partials/head.ejs Normal file
View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>神椿配信スケジュール</title>
<link rel="stylesheet" href="/css/header.css">
<link rel="stylesheet" href="/css/errors.css">
</head>

View File

@ -0,0 +1,8 @@
<header class="header">
<div class="header-left">
<a href="/">神椿配信スケジュール</a>
</div>
<div class="header-right">
<a href="/terms" class="header-link">利用規約</a>
</div>
</header>

View File

@ -3,10 +3,15 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Schedule</title>
<link rel="stylesheet" href="/css/schedule.css">
<link rel="stylesheet" href="/css/header.css">
</head>
<%- include("partials/head") %>
<body>
<%- include("partials/header") %>
<div class="header-spacer"></div>
<div class="main-content">
<div class="content">
<div class="schedule-container">
<% Object.keys(groupedSchedules).forEach(date => { %>
<div class="date-header">
@ -30,5 +35,7 @@
</div>
<% }) %>
</div>
</div>
</div>
</body>
</html>

46
views/terms.ejs Normal file
View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/header.css">
<link rel="stylesheet" href="/css/terms.css">
</head>
<%- include("partials/head") %>
<body>
<%- include("partials/header") %>
<div class="terms-container">
<h1>利用規約</h1>
<section>
<h2>サイトの運営について</h2>
<p>
本サイトは、<strong>KAMITSUBAKI STUDIO</strong>およびその関連企業・団体とは一切関係のない非公式のウェブサイトです。<br>
本サイトは、個人が趣味の一環として運営しており、情報の正確性や完全性について保証するものではありません。
</p>
</section>
<section>
<h2>免責事項</h2>
<p>
本サイトに掲載されている情報は、できる限り正確であるよう努めていますが、その内容に誤りがある場合や、<br>
配信スケジュールの変更・キャンセル等に関して責任を負うものではありません。
</p>
<p>
また、本サイトを利用することで生じた損害について、一切の責任を負いません。利用者はご自身の責任において本サイトをご利用ください。<br>
</p>
</section>
<section>
<h2>著作権について</h2>
<p>
本サイトに掲載されている文章や画像などの著作物は、著作権法で認められる範囲内で利用していますが、<br>
著作権者からの要請があった場合、速やかに対応いたします。
</p>
</section>
<section>
<h2>お問い合わせ</h2>
<p>
本サイトに関するお問い合わせは、管理者宛てにご連絡ください。ただし、返信や対応を保証するものではありません。
</p>
</section>
</div>
</body>
</html>