Skip to content

Commit

Permalink
譜面の一般公開機能、公開された譜面リスト機能
Browse files Browse the repository at this point in the history
  • Loading branch information
na-trium-144 committed Nov 23, 2024
1 parent 3ffe7e4 commit 053f9ec
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 10 deletions.
8 changes: 8 additions & 0 deletions app/api/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export async function getChartEntry(
res: { message: "Chart ID Not Found", status: 404 },
};
}
if (typeof entryCompressed.published !== "boolean") {
entryCompressed.published = false;
}

let entry: ChartEntry;
let chart: Chart;
Expand Down Expand Up @@ -69,6 +72,7 @@ export interface ChartEntryCompressed {
cid: string;
levelsCompressed: Binary | null;
deleted: boolean;
published: boolean;
ver: 6;
offset: number;
ytId: string;
Expand Down Expand Up @@ -127,6 +131,7 @@ export async function zipEntry(
return {
cid: entry.cid,
deleted: entry.deleted,
published: entry.published,
ver: entry.ver,
offset: entry.offset,
ytId: entry.ytId,
Expand All @@ -145,6 +150,7 @@ export function entryToChart(entry: ChartEntry): Chart {
return {
falling: "nikochan",
ver: entry.ver,
published: entry.published,
levels: entry.levels.map((level, i) => ({
name: entry.levelBrief.at(i)?.name || "",
type: entry.levelBrief.at(i)?.type || "",
Expand Down Expand Up @@ -186,6 +192,7 @@ export async function chartToEntry(
lua: level.lua,
})),
ver: chart.ver,
published: chart.published,
offset: chart.offset,
editPasswd: chart.editPasswd,
ytId: chartBrief.ytId,
Expand All @@ -206,5 +213,6 @@ export function entryToBrief(entry: ChartEntryCompressed): ChartBrief {
levels: entry.levelBrief,
updatedAt: entry.updatedAt,
playCount: entry.playCount,
published: entry.published,
};
}
7 changes: 7 additions & 0 deletions app/api/chartFile/[cid]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { MongoClient } from "mongodb";
import "dotenv/config";
import { chartToEntry, getChartEntry, zipEntry } from "@/api/chart";
import { revalidateBrief } from "@/api/brief/brief";
import { revalidateLatest } from "@/api/latest/latest";

// 他のAPIと違って編集用パスワードのチェックが入る
// クエリパラメータのpで渡す
Expand Down Expand Up @@ -106,6 +107,9 @@ export async function POST(
if (!newHashes.every((h, i) => h === prevHashes[i])) {
updatedAt = new Date().getTime();
}
if (chart.published || newChart.published) {
revalidateLatest();
}

await db.collection("chart").updateOne(
{ cid },
Expand Down Expand Up @@ -154,6 +158,9 @@ export async function DELETE(
}
);
revalidateBrief(cid);
if (chart.published) {
revalidateLatest();
}
return new Response(null);
} catch (e) {
console.error(e);
Expand Down
1 change: 1 addition & 0 deletions app/api/latest/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const numLatest = 5;
35 changes: 35 additions & 0 deletions app/api/latest/latest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MongoClient } from "mongodb";
import "dotenv/config";
import { revalidateTag, unstable_cache } from "next/cache";
import { NextRequest, NextResponse } from "next/server";
import { numLatest } from "./const";

async function getLatestImpl() {
console.log("getLatestImpl");
const client = new MongoClient(process.env.MONGODB_URI!);
try {
await client.connect();
const db = client.db("nikochan");
return await db
.collection("chart")
.find({ published: true })
.sort({ updatedAt: -1 })
.limit(numLatest)
.project({ _id: 0, cid: 1 })
.toArray();
} finally {
await client.close();
}
}
export function revalidateLatest() {
console.warn(`revalidate latest`);
revalidateTag(`latest`);
}

export async function getLatest() {
const getLatestCache = unstable_cache(getLatestImpl, [], {
tags: ["latest"],
revalidate: false,
});
return await getLatestCache();
}
8 changes: 8 additions & 0 deletions app/api/latest/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { NextRequest, NextResponse } from "next/server";
import { getLatest } from "./latest";

export async function GET(request: NextRequest) {
const latest = await getLatest();
console.log(latest)
return NextResponse.json(latest);
}
5 changes: 5 additions & 0 deletions app/api/newChartFile/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { MongoClient } from "mongodb";
import { chartToEntry, getChartEntry, zipEntry } from "../chart";
import "dotenv/config";
import { revalidateBrief } from "../brief/brief";
import { revalidateLatest } from "../latest/latest";

export async function GET() {
const headersList = await headers();
Expand Down Expand Up @@ -69,6 +70,10 @@ export async function POST(request: NextRequest) {
// update Time
const updatedAt = new Date().getTime();

if (chart.published) {
revalidateLatest();
}

let cid: string;
while (true) {
cid = Math.floor(Math.random() * 900000 + 100000).toString();
Expand Down
5 changes: 5 additions & 0 deletions app/chartFormat/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface ChartBrief {
chartCreator: string;
updatedAt: number;
playCount?: number;
published: boolean;
levels: {
name: string;
hash: string;
Expand Down Expand Up @@ -75,6 +76,7 @@ export interface Chart {
composer: string;
chartCreator: string;
editPasswd: string;
published: boolean;
}
export interface Level {
name: string;
Expand Down Expand Up @@ -119,6 +121,7 @@ export async function validateChart(
if (typeof chart.composer !== "string") chart.composer = "";
if (typeof chart.chartCreator !== "string") chart.chartCreator = "";
if (typeof chart.editPasswd !== "string") chart.editPasswd = "";
if (typeof chart.published !== "boolean") chart.published = false;
return chart;
}
export function validateLevel(level: Level): Level {
Expand Down Expand Up @@ -181,6 +184,7 @@ export function emptyChart(): Chart {
composer: "",
chartCreator: "",
editPasswd: "",
published: false,
};
return chart;
}
Expand Down Expand Up @@ -265,5 +269,6 @@ export async function createBrief(
chartCreator: chart.chartCreator,
levels: levelBrief,
updatedAt: updatedAt || 0,
published: chart.published,
};
}
47 changes: 41 additions & 6 deletions app/edit/[cid]/metaTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { linkStyle1, linkStyle2 } from "@/common/linkStyle";
import { ExternalLink } from "@/common/extLink";
import ProgressBar from "@/common/progressBar";
import YAML from "yaml";
import CheckBox from "@/common/checkBox";

interface Props {
chart?: Chart;
Expand All @@ -33,10 +34,16 @@ export function MetaEdit(props: Props) {
<Input
className=""
actualValue={props.chart?.ytId || ""}
updateValue={(v: string) =>
props.chart &&
props.setChart({ ...props.chart, ytId: getYouTubeId(v) })
}
updateValue={(v: string) => {
if (props.chart) {
const ytId = getYouTubeId(v);
props.setChart({
...props.chart,
ytId,
published: ytId ? props.chart.published : false,
});
}
}}
isValid={checkYouTubeId}
left
/>
Expand Down Expand Up @@ -82,17 +89,45 @@ export function MetaEdit(props: Props) {
className="font-title shrink w-40 "
actualValue={props.chart?.editPasswd || ""}
updateValue={(v: string) =>
props.chart && props.setChart({ ...props.chart, editPasswd: v })
props.chart &&
props.setChart({
...props.chart,
editPasswd: v,
published: v ? props.chart.published : false,
})
}
left
passwd={hidePasswd}
/>
<Button text="表示" onClick={() => setHidePasswd(!hidePasswd)} />
</span>
</p>
<p className="text-sm ml-2">
<p className="text-sm ml-2 mb-2">
(編集用パスワードは譜面を別のPCから編集するとき、ブラウザのキャッシュを消したときなどに必要になります)
</p>
<p>
<CheckBox
className="ml-0 "
value={props.chart?.published || false}
onChange={(v: boolean) =>
props.chart && props.setChart({ ...props.chart, published: v })
}
disabled={!props.chart?.editPasswd || !props.chart?.ytId}
>
この譜面を一般公開する
</CheckBox>
{!props.chart?.ytId ? (
<span className="inline-block ml-2 text-sm">
(YouTube 動画IDを指定してください)
</span>
) : (
!props.chart?.editPasswd && (
<span className="inline-block ml-2 text-sm">
(編集用パスワードを設定してください)
</span>
)
)}
</p>
</>
);
}
Expand Down
9 changes: 5 additions & 4 deletions app/main/chartList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { useState } from "react";

interface Props {
recentBrief?: { cid?: string; brief?: ChartBrief }[];
hasRecentAdditional: number;
hasRecentAdditional?: number;
recentBriefAdditional?: { cid?: string; brief?: ChartBrief }[];
fetchAdditional: () => void;
fetchAdditional?: () => void;
creator?: boolean;
href: (cid: string) => string;
newTab?: boolean;
Expand Down Expand Up @@ -43,7 +43,8 @@ export function ChartList(props: Props) {
)
)}
</ul>
{props.hasRecentAdditional > 0 &&
{props.hasRecentAdditional !== undefined &&
props.hasRecentAdditional > 0 &&
(additionalOpen ? (
<>
<p
Expand Down Expand Up @@ -77,7 +78,7 @@ export function ChartList(props: Props) {
className={"block relative ml-5 mt-1 " + linkStyle1}
onClick={() => {
setAdditionalOpen(!additionalOpen);
if (fetchingAdditional) {
if (fetchingAdditional && props.fetchAdditional) {
props.fetchAdditional();
}
}}
Expand Down
27 changes: 27 additions & 0 deletions app/main/play/clientPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ChartList, ChartListItem } from "../chartList";
import { LoadingSlime } from "@/common/loadingSlime";
import { Youtube } from "@icon-park/react";
import { ExternalLink } from "@/common/extLink";
import { numLatest } from "@/api/latest/const";

export default function PlayTab(props: {
sampleBrief: { cid: string; brief: ChartBrief | undefined }[];
Expand All @@ -20,6 +21,8 @@ export default function PlayTab(props: {
useState<{ cid?: string; brief?: ChartBrief }[]>();
const [recentBriefAdditional, setRecentBriefAdditional] =
useState<{ cid?: string; brief?: ChartBrief }[]>();
const [latestBrief, setLatestBrief] =
useState<{ cid?: string; brief?: ChartBrief }[]>();
const router = useRouter();

const fetchBrief = useCallback(async (cid: string) => {
Expand All @@ -44,6 +47,14 @@ export default function PlayTab(props: {
await Promise.all(recentCId.map((cid) => fetchBrief(cid)))
);
})();
void (async () => {
const latestCIds = (await (
await fetch(`/api/latest`, { cache: "default" })
).json()) as { cid: string }[];
setLatestBrief(
await Promise.all(latestCIds.map(({ cid }) => fetchBrief(cid)))
);
})();
}, [fetchBrief]);
const fetchAdditional = () => {
void (async () => {
Expand Down Expand Up @@ -125,6 +136,22 @@ export default function PlayTab(props: {
href={(cid) => `/share/${cid}`}
/>
</div>
<div className="mb-3">
<h3 className="text-xl font-bold font-title mb-2">新着譜面</h3>
<p className="pl-2 text-justify ">
最近作成・更新された譜面の一覧です。
<span className="text-sm ">(最新の{numLatest}件まで)</span>
</p>
<p className="pl-2 mb-1 text-justify text-sm ">
(譜面を制作する方へ:
譜面編集から「一般公開する」にチェックを入れたもののみが対象です)
</p>
<ChartList
recentBrief={latestBrief}
creator
href={(cid) => `/share/${cid}`}
/>
</div>
<div className="mb-3">
<h3 className="text-xl font-bold font-title mb-2">サンプル譜面</h3>
<p className="pl-2 mb-1 text-justify ">
Expand Down

0 comments on commit 053f9ec

Please sign in to comment.