mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-05-13 13:17:45 +00:00
Compare commits
No commits in common. "7bd84ea4543b6763f3c19d500cbbb2ac6b358fc2" and "a42a4126ecbc5947e70039bd3e96937f53a56312" have entirely different histories.
7bd84ea454
...
a42a4126ec
15 changed files with 31 additions and 46 deletions
|
|
@ -5,7 +5,7 @@ REDIS_URL="redis://localhost:6379/0"
|
||||||
|
|
||||||
# Used for metadata, sitemaps, etc.
|
# Used for metadata, sitemaps, etc.
|
||||||
NEXT_PUBLIC_BASE_URL=http://localhost:3000
|
NEXT_PUBLIC_BASE_URL=http://localhost:3000
|
||||||
NEXT_PUBLIC_FRONTEND_URL=http://localhost:5173
|
FRONTEND_URL=http://localhost:4321
|
||||||
|
|
||||||
CLOUDFLARE_ZONE_ID=XXXXXXXXXXXXXXXX
|
CLOUDFLARE_ZONE_ID=XXXXXXXXXXXXXXXX
|
||||||
CLOUDFLARE_API_TOKEN=XXXXXXXXXXXXXXXX
|
CLOUDFLARE_API_TOKEN=XXXXXXXXXXXXXXXX
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "miis_in_queue_quarantined_createdAt_idx" ON "miis"("in_queue", "quarantined", "createdAt" DESC);
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "miis" ADD COLUMN "likeCount" INTEGER NOT NULL DEFAULT 0;
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "miis_likeCount_idx" ON "miis"("likeCount" DESC);
|
|
||||||
|
|
@ -91,22 +91,19 @@ model Mii {
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
likeCount Int @default(0)
|
likedBy Like[]
|
||||||
|
|
||||||
punishmentId Int?
|
punishmentId Int?
|
||||||
punishments MiiPunishment[]
|
punishments MiiPunishment[]
|
||||||
likedBy Like[]
|
|
||||||
|
|
||||||
@@index([tags], type: Gin)
|
@@index([tags], type: Gin)
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
@@index([likeCount(sort: Desc)])
|
|
||||||
@@index([quarantined, createdAt(sort: Desc)])
|
@@index([quarantined, createdAt(sort: Desc)])
|
||||||
@@index([platform, createdAt(sort: Desc)])
|
@@index([platform, createdAt(sort: Desc)])
|
||||||
@@index([userId, createdAt(sort: Desc)])
|
@@index([userId, createdAt(sort: Desc)])
|
||||||
@@index([gender])
|
@@index([gender])
|
||||||
@@index([makeup])
|
@@index([makeup])
|
||||||
@@index([quarantined, id])
|
@@index([quarantined, id])
|
||||||
@@index([in_queue, quarantined, createdAt(sort: Desc)])
|
|
||||||
@@map("miis")
|
@@map("miis")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,5 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!mii) return NextResponse.json({ error: "Mii not found" }, { status: 404 });
|
|
||||||
|
|
||||||
return NextResponse.json(mii);
|
return NextResponse.json(mii);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingLike) {
|
if (existingLike) {
|
||||||
|
// Remove the like if it exists
|
||||||
await tx.like.delete({
|
await tx.like.delete({
|
||||||
where: {
|
where: {
|
||||||
userId_miiId: {
|
userId_miiId: {
|
||||||
|
|
@ -37,25 +38,22 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await tx.mii.update({
|
|
||||||
where: { id: miiId },
|
|
||||||
data: { likeCount: { decrement: 1 } },
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
|
// Add a like if it doesn't exist
|
||||||
await tx.like.create({
|
await tx.like.create({
|
||||||
data: {
|
data: {
|
||||||
userId: Number(session.user?.id),
|
userId: Number(session.user?.id),
|
||||||
miiId,
|
miiId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await tx.mii.update({
|
|
||||||
where: { id: miiId },
|
|
||||||
data: { likeCount: { increment: 1 } },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { liked: !existingLike };
|
const likeCount = await tx.like.count({
|
||||||
|
where: { miiId },
|
||||||
});
|
});
|
||||||
|
|
||||||
return rateLimit.sendResponse({ success: true, liked: result.liked });
|
return { liked: !existingLike, count: likeCount };
|
||||||
|
});
|
||||||
|
|
||||||
|
return rateLimit.sendResponse({ success: true, liked: result.liked, count: result.count });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,6 @@ export async function GET(request: NextRequest) {
|
||||||
allowedCopying: true,
|
allowedCopying: true,
|
||||||
quarantined: true,
|
quarantined: true,
|
||||||
in_queue: true,
|
in_queue: true,
|
||||||
likeCount: true,
|
|
||||||
// Mii liked check
|
// Mii liked check
|
||||||
...(session?.user?.id && {
|
...(session?.user?.id && {
|
||||||
likedBy: {
|
likedBy: {
|
||||||
|
|
@ -86,6 +85,10 @@ export async function GET(request: NextRequest) {
|
||||||
select: { userId: true },
|
select: { userId: true },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
// Like count
|
||||||
|
_count: {
|
||||||
|
select: { likedBy: true },
|
||||||
|
},
|
||||||
// Admin
|
// Admin
|
||||||
...(parentPage === "admin" && {
|
...(parentPage === "admin" && {
|
||||||
description: true,
|
description: true,
|
||||||
|
|
@ -99,7 +102,7 @@ export async function GET(request: NextRequest) {
|
||||||
let orderBy: Prisma.MiiOrderByWithRelationInput[];
|
let orderBy: Prisma.MiiOrderByWithRelationInput[];
|
||||||
|
|
||||||
if (sort === "likes") {
|
if (sort === "likes") {
|
||||||
orderBy = [{ likeCount: "desc" }, { name: "asc" }];
|
orderBy = [{ likedBy: { _count: "desc" } }, { name: "asc" }];
|
||||||
} else if (sort === "oldest") {
|
} else if (sort === "oldest") {
|
||||||
orderBy = [{ createdAt: "asc" }, { name: "asc" }];
|
orderBy = [{ createdAt: "asc" }, { name: "asc" }];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,5 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) return NextResponse.json({ error: "User not found" }, { status: 404 });
|
|
||||||
|
|
||||||
return NextResponse.json(user);
|
return NextResponse.json(user);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||||
},
|
},
|
||||||
|
|
||||||
async redirect({ url, baseUrl }) {
|
async redirect({ url, baseUrl }) {
|
||||||
if (url.startsWith(baseUrl)) return url;
|
|
||||||
return process.env.NEXT_PUBLIC_FRONTEND_URL ?? "http://localhost:4321";
|
return process.env.NEXT_PUBLIC_FRONTEND_URL ?? "http://localhost:4321";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ export default function LikeButton({ likes, miiId, isLiked, disabled, abbreviate
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevLiked = isLikedState;
|
const prevLiked = isLikedState;
|
||||||
|
const prevLikes = likesState;
|
||||||
setIsLikedState(!prevLiked);
|
setIsLikedState(!prevLiked);
|
||||||
setLikesState(prevLiked ? likesState - 1 : likesState + 1);
|
setLikesState(prevLiked ? likesState - 1 : likesState + 1);
|
||||||
|
|
||||||
|
|
@ -41,10 +42,12 @@ export default function LikeButton({ likes, miiId, isLiked, disabled, abbreviate
|
||||||
|
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/mii/${miiId}/like`, { method: "POST", credentials: "include" });
|
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/mii/${miiId}/like`, { method: "POST", credentials: "include" });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const { liked } = await response.json();
|
const { liked, count } = await response.json();
|
||||||
setIsLikedState(liked);
|
setIsLikedState(liked);
|
||||||
|
setLikesState(count);
|
||||||
} else {
|
} else {
|
||||||
setIsLikedState(prevLiked);
|
setIsLikedState(prevLiked);
|
||||||
|
setLikesState(prevLikes);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export default function AuthorButtons({ mii }: Props) {
|
||||||
<Icon icon="mdi:pencil" />
|
<Icon icon="mdi:pencil" />
|
||||||
<span>Edit</span>
|
<span>Edit</span>
|
||||||
</Link>
|
</Link>
|
||||||
<DeleteMiiButton miiId={mii.id} miiName={mii.name} likes={mii.likeCount ?? 0} inMiiPage />
|
<DeleteMiiButton miiId={mii.id} miiName={mii.name} likes={mii._count.likedBy ?? 0} inMiiPage />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ export default function MiiList({ parentPage, userId }: Props) {
|
||||||
{parentPage === "admin" && mii.description && <Description text={mii.description} />}
|
{parentPage === "admin" && mii.description && <Description text={mii.description} />}
|
||||||
|
|
||||||
<div className="mt-auto grid grid-cols-2 items-center">
|
<div className="mt-auto grid grid-cols-2 items-center">
|
||||||
<LikeButton likes={mii.likeCount} miiId={mii.id} isLiked={likedIds.has(mii.id)} abbreviate />
|
<LikeButton likes={mii._count.likedBy} miiId={mii.id} isLiked={likedIds.has(mii.id)} abbreviate />
|
||||||
|
|
||||||
{!userId && (
|
{!userId && (
|
||||||
<Link to={`/profile/${mii.user?.id}`} className="text-sm text-right overflow-hidden text-ellipsis whitespace-nowrap">
|
<Link to={`/profile/${mii.user?.id}`} className="text-sm text-right overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
|
@ -185,7 +185,7 @@ export default function MiiList({ parentPage, userId }: Props) {
|
||||||
<Link to={`/edit/${mii.id}`} title="Edit Mii" aria-label="Edit Mii" data-tooltip="Edit">
|
<Link to={`/edit/${mii.id}`} title="Edit Mii" aria-label="Edit Mii" data-tooltip="Edit">
|
||||||
<Icon icon="mdi:pencil" />
|
<Icon icon="mdi:pencil" />
|
||||||
</Link>
|
</Link>
|
||||||
<DeleteMiiButton miiId={mii.id} miiName={mii.name} likes={mii.likeCount} />
|
<DeleteMiiButton miiId={mii.id} miiName={mii.name} likes={mii._count.likedBy} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -202,7 +202,7 @@ export default function MiiList({ parentPage, userId }: Props) {
|
||||||
<Icon icon="material-symbols:check-rounded" />
|
<Icon icon="material-symbols:check-rounded" />
|
||||||
</button>
|
</button>
|
||||||
<div className="text-zinc-400 hover:text-red-500 transition-colors p-1 bg-white rounded-md shadow-sm border border-zinc-200 hover:border-red-500 flex items-center justify-center">
|
<div className="text-zinc-400 hover:text-red-500 transition-colors p-1 bg-white rounded-md shadow-sm border border-zinc-200 hover:border-red-500 flex items-center justify-center">
|
||||||
<DeleteMiiButton miiId={mii.id} miiName={mii.name} likes={mii.likeCount} />
|
<DeleteMiiButton miiId={mii.id} miiName={mii.name} likes={mii._count.likedBy} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export default function ShareMiiButton({ miiId }: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyImage = async () => {
|
const handleCopyImage = async () => {
|
||||||
const response = await fetch(`${import.meta.env.VITE_BASE_URL}/mii/${miiId}/image?type=metadata`);
|
const response = await fetch(`${import.meta.env.VITE_API_URL}/mii/${miiId}/image?type=metadata`);
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
|
|
||||||
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
|
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,6 @@ export default function MiiPage() {
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data) throw new Error("Mii not found");
|
|
||||||
|
|
||||||
setMii(data);
|
setMii(data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
|
|
@ -266,12 +264,12 @@ export default function MiiPage() {
|
||||||
{/* Submission name */}
|
{/* Submission name */}
|
||||||
<h1 className="text-4xl font-extrabold wrap-break-word whitespace-break-spaces text-amber-700 flex-1 min-w-0">{mii.name}</h1>
|
<h1 className="text-4xl font-extrabold wrap-break-word whitespace-break-spaces text-amber-700 flex-1 min-w-0">{mii.name}</h1>
|
||||||
{/* Like button */}
|
{/* Like button */}
|
||||||
<LikeButton likes={mii.likeCount ?? 0} miiId={mii.id} isLiked={isLiked} big />
|
<LikeButton likes={mii._count?.likedBy ?? 0} miiId={mii.id} isLiked={isLiked} big />
|
||||||
</div>
|
</div>
|
||||||
{/* Tags */}
|
{/* Tags */}
|
||||||
<div id="tags" className="flex flex-wrap gap-1 mt-1 *:px-2 *:py-1 *:bg-orange-300 *:rounded-full *:text-xs">
|
<div id="tags" className="flex flex-wrap gap-1 mt-1 *:px-2 *:py-1 *:bg-orange-300 *:rounded-full *:text-xs">
|
||||||
{mii.tags.map((tag: string) => (
|
{mii.tags.map((tag: string) => (
|
||||||
<Link to={`/?tags=${encodeURIComponent(tag)}`} key={tag}>
|
<Link to={`/tags=${tag}`} key={tag}>
|
||||||
{tag}
|
{tag}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@ export default function ProfileLayout() {
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data) throw new Error("Profile not found");
|
|
||||||
|
|
||||||
setUser(data);
|
setUser(data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
|
|
@ -44,11 +42,11 @@ export default function ProfileLayout() {
|
||||||
return <div className="p-6 text-center">Loading...</div>;
|
return <div className="p-6 text-center">Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionUserId = $session?.user?.id ? Number($session.user.id) : null;
|
const currentUser = user ?? $session?.user;
|
||||||
const page = location.pathname;
|
const page = location.pathname;
|
||||||
const isAdmin = sessionUserId === Number(import.meta.env.VITE_ADMIN_USER_ID);
|
const isAdmin = currentUser?.id === Number(import.meta.env.VITE_ADMIN_USER_ID);
|
||||||
const isContributor = import.meta.env.VITE_CONTRIBUTORS_USER_IDS?.split(",").includes(String(user?.id));
|
const isContributor = import.meta.env.VITE_CONTRIBUTORS_USER_IDS?.split(",").includes(user?.id);
|
||||||
const isOwnProfile = sessionUserId === user?.id;
|
const isOwnProfile = currentUser?.id === user?.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue