mirror of
https://github.com/Hackdex-App/hackdex-website.git
synced 2026-03-21 17:54:09 -05:00
Use is_archive for checking hack archive status
This commit is contained in:
parent
b5a4e53368
commit
3d5db2ed3c
|
|
@ -59,15 +59,16 @@ export const getDownloadsSeriesAll = async ({ days = 30 }: { days?: number }): P
|
|||
// Get hacks owned by user
|
||||
const { data: ownedHacks } = await supa
|
||||
.from("hacks")
|
||||
.select("slug,created_by,current_patch,original_author,permission_from")
|
||||
.select("slug,created_by,current_patch,original_author,permission_from,is_archive")
|
||||
.is("is_archive", false)
|
||||
.eq("created_by", user.id);
|
||||
const ownedSlugs = (ownedHacks ?? []).map((h) => h.slug);
|
||||
|
||||
// Get archive hacks that user can edit as archiver
|
||||
const { data: allArchiveHacks } = await supa
|
||||
.from("hacks")
|
||||
.select("slug,created_by,current_patch,original_author,permission_from")
|
||||
.not("original_author", "is", null);
|
||||
.select("slug,created_by,current_patch,original_author,permission_from,is_archive")
|
||||
.eq("is_archive", true);
|
||||
|
||||
const accessibleArchiveSlugs: string[] = [];
|
||||
if (allArchiveHacks) {
|
||||
|
|
@ -167,7 +168,7 @@ export const getHackInsights = async ({ slug }: { slug: string }): Promise<HackI
|
|||
if (!user) throw new Error("Unauthorized");
|
||||
const { data: hack } = await supa
|
||||
.from("hacks")
|
||||
.select("slug,created_by,current_patch,created_at,original_author,permission_from")
|
||||
.select("slug,created_by,current_patch,created_at,original_author,permission_from,is_archive")
|
||||
.eq("slug", slug)
|
||||
.maybeSingle();
|
||||
if (!hack) throw new Error("Not found");
|
||||
|
|
|
|||
|
|
@ -35,9 +35,8 @@ export async function getArchives(args: {
|
|||
|
||||
let query = serviceClient
|
||||
.from("hacks")
|
||||
.select("slug,title,original_author,base_rom,created_at,created_by,approved,permission_from,current_patch", { count: "exact" })
|
||||
.not("original_author", "is", null)
|
||||
.or("current_patch.is.null,permission_from.not.is.null")
|
||||
.select("slug,title,original_author,base_rom,created_at,created_by,approved,permission_from,current_patch,is_archive", { count: "exact" })
|
||||
.eq("is_archive", true)
|
||||
.order(sortBy, { ascending: sortOrder === "asc" })
|
||||
.range(offset, offset + limit - 1);
|
||||
|
||||
|
|
@ -82,6 +81,7 @@ export async function getArchives(args: {
|
|||
creator_username: usernameById.get(h.created_by as string) || null,
|
||||
approved: h.approved,
|
||||
current_patch: h.current_patch,
|
||||
is_archive: h.is_archive,
|
||||
}));
|
||||
|
||||
return {
|
||||
|
|
@ -110,7 +110,7 @@ export async function deleteArchive(slug: string) {
|
|||
// Verify it's an Archive hack
|
||||
const { data: hack } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug, original_author, current_patch")
|
||||
.select("slug, is_archive")
|
||||
.eq("slug", slug)
|
||||
.maybeSingle();
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ export async function deleteArchive(slug: string) {
|
|||
return { ok: false, error: "Archive not found" } as const;
|
||||
}
|
||||
|
||||
if (hack.original_author == null || hack.current_patch != null) {
|
||||
if (!hack.is_archive) {
|
||||
return { ok: false, error: "This is not an Archive hack" } as const;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@ export default async function DashboardPage() {
|
|||
|
||||
const { data: hacks } = await supa
|
||||
.from("hacks")
|
||||
.select("slug,title,approved,updated_at,downloads,current_patch(id,version),created_at,original_author")
|
||||
.select("slug,title,approved,updated_at,downloads,current_patch(id,version),created_at,original_author,is_archive")
|
||||
.is("is_archive", false)
|
||||
.eq("created_by", user.id)
|
||||
.order("updated_at", { ascending: false });
|
||||
|
||||
|
|
|
|||
|
|
@ -34,23 +34,22 @@ export async function getDiscoverData(sort: DiscoverSortOption): Promise<Discove
|
|||
// Build base query for hacks (public/anon view: only approved hacks)
|
||||
let query = supabase
|
||||
.from("hacks")
|
||||
.select("slug,title,summary,description,base_rom,downloads,created_by,updated_at,current_patch,original_author,approved_at")
|
||||
.select("slug,title,summary,description,base_rom,downloads,created_by,updated_at,current_patch,original_author,approved_at,is_archive")
|
||||
.eq("approved", true);
|
||||
|
||||
// Apply sorting based on sort type
|
||||
if (sort === "popular") {
|
||||
// When sorting by popularity, always show non-archive hacks first.
|
||||
// Archives are defined as rows where original_author IS NOT NULL and current_patch IS NULL,
|
||||
// so ordering by current_patch with NULLS LAST effectively pushes archives to the end.
|
||||
// Archives are defined by the `is_archive` flag, so we order by that after downloads.
|
||||
query = query
|
||||
.order("downloads", { ascending: false })
|
||||
.order("current_patch", { ascending: false, nullsFirst: false });
|
||||
.order("is_archive", { ascending: true });
|
||||
} else if (sort === "trending") {
|
||||
// For trending, we'll fetch all and calculate scores in JS
|
||||
// Still order by downloads first for efficiency
|
||||
// Still order by downloads first for efficiency, then `is_archive` to keep non-archives first.
|
||||
query = query
|
||||
.order("downloads", { ascending: false })
|
||||
.order("current_patch", { ascending: false, nullsFirst: false });
|
||||
.order("is_archive", { ascending: true });
|
||||
} else if (sort === "updated") {
|
||||
// Will sort by current patch published_at in JS after fetching patches
|
||||
} else if (sort === "alphabetical") {
|
||||
|
|
@ -224,7 +223,7 @@ export async function getDiscoverData(sort: DiscoverSortOption): Promise<Discove
|
|||
const publishedAt = publishedAtByPatchId.get(r.current_patch) ?? null;
|
||||
publishedAtBySlug.set(r.slug, publishedAt);
|
||||
} else {
|
||||
mappedVersions.set(r.slug, r.original_author ? "Archive" : "Pre-release");
|
||||
mappedVersions.set(r.slug, r.is_archive ? "Archive" : "Pre-release");
|
||||
publishedAtBySlug.set(r.slug, null);
|
||||
}
|
||||
});
|
||||
|
|
@ -256,7 +255,7 @@ export async function getDiscoverData(sort: DiscoverSortOption): Promise<Discove
|
|||
version: mappedVersions.get(r.slug) || "Pre-release",
|
||||
summary: r.summary,
|
||||
description: r.description,
|
||||
isArchive: r.original_author != null && r.current_patch === null,
|
||||
is_archive: r.is_archive,
|
||||
}));
|
||||
|
||||
// Sort by current patch published_at for "updated" sort
|
||||
|
|
@ -275,8 +274,8 @@ export async function getDiscoverData(sort: DiscoverSortOption): Promise<Discove
|
|||
|
||||
// Secondary sort: when times are equal, push archives to end
|
||||
if (aTime === bTime) {
|
||||
if (a.isArchive && !b.isArchive) return 1;
|
||||
if (!a.isArchive && b.isArchive) return -1;
|
||||
if (a.is_archive && !b.is_archive) return 1;
|
||||
if (!a.is_archive && b.is_archive) return -1;
|
||||
}
|
||||
|
||||
return bTime - aTime; // Descending order (newest first)
|
||||
|
|
@ -291,8 +290,8 @@ export async function getDiscoverData(sort: DiscoverSortOption): Promise<Discove
|
|||
|
||||
// Secondary sort: push archives to end
|
||||
if (scoreA === scoreB) {
|
||||
if (a.isArchive && !b.isArchive) return 1;
|
||||
if (!a.isArchive && b.isArchive) return -1;
|
||||
if (a.is_archive && !b.is_archive) return 1;
|
||||
if (!a.is_archive && b.is_archive) return -1;
|
||||
}
|
||||
|
||||
return scoreB - scoreA; // Descending order
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default async function EditHackPage({ params }: EditPageProps) {
|
|||
|
||||
const { data: hack } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug,title,summary,description,base_rom,language,box_art,social_links,created_by,current_patch,original_author,permission_from")
|
||||
.select("slug,title,summary,description,base_rom,language,box_art,social_links,created_by,current_patch,original_author,permission_from,is_archive")
|
||||
.eq("slug", slug)
|
||||
.maybeSingle();
|
||||
if (!hack) return notFound();
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export async function generateMetadata({ params }: HackDetailProps): Promise<Met
|
|||
const supabase = await createClient();
|
||||
const { data: hack } = await supabase
|
||||
.from("hacks")
|
||||
.select("title,summary,approved,base_rom,box_art,created_by,created_at,updated_at,original_author,current_patch,permission_from")
|
||||
.select("title,summary,approved,base_rom,box_art,created_by,created_at,updated_at,original_author,current_patch,permission_from,is_archive")
|
||||
.eq("slug", slug)
|
||||
.maybeSingle();
|
||||
if (!hack) return { title: "Hack not found" };
|
||||
|
|
@ -120,7 +120,7 @@ export default async function HackDetail({ params }: HackDetailProps) {
|
|||
const supabase = await createClient();
|
||||
const { data: hack, error } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug,title,summary,description,base_rom,created_at,updated_at,downloads,current_patch,box_art,social_links,created_by,approved,original_author,permission_from,language")
|
||||
.select("slug,title,summary,description,base_rom,created_at,updated_at,downloads,current_patch,box_art,social_links,created_by,approved,original_author,permission_from,language,is_archive")
|
||||
.eq("slug", slug)
|
||||
.maybeSingle();
|
||||
if (error || !hack) return notFound();
|
||||
|
|
@ -156,15 +156,24 @@ export default async function HackDetail({ params }: HackDetailProps) {
|
|||
.maybeSingle();
|
||||
const author = hack.original_author ? hack.original_author : (profile?.username ? `@${profile.username}` : "Unknown");
|
||||
|
||||
// Get other approved hacks by the same author
|
||||
const { data: otherHacks } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug,title,summary")
|
||||
.eq("created_by", hack.created_by)
|
||||
.eq("approved", true)
|
||||
.neq("slug", hack.slug)
|
||||
.order("downloads", { ascending: false })
|
||||
.limit(10);
|
||||
// Get other approved hacks by the same author (non-archive hacks only)
|
||||
let otherHacks: {
|
||||
slug: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
}[] = [];
|
||||
if (!hack.is_archive) {
|
||||
const { data: otherHacksData } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug,title,summary")
|
||||
.eq("created_by", hack.created_by)
|
||||
.eq("approved", true)
|
||||
.eq("is_archive", false)
|
||||
.neq("slug", hack.slug)
|
||||
.order("downloads", { ascending: false })
|
||||
.limit(10);
|
||||
otherHacks = otherHacksData ?? [];
|
||||
}
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default async function HackStatsPage({ params: { slug } }: { params: { sl
|
|||
|
||||
const { data: hack } = await supa
|
||||
.from("hacks")
|
||||
.select("slug,created_by,title,original_author,current_patch,permission_from")
|
||||
.select("slug,created_by,title,original_author,current_patch,permission_from,is_archive")
|
||||
.eq("slug", slug)
|
||||
.maybeSingle();
|
||||
if (!hack) notFound();
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export async function updateHack(args: {
|
|||
|
||||
const { data: hack, error: hErr } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from, is_archive")
|
||||
.eq("slug", args.slug)
|
||||
.maybeSingle();
|
||||
if (hErr) return { ok: false, error: hErr.message } as const;
|
||||
|
|
@ -125,7 +125,7 @@ export async function saveHackCovers(args: { slug: string; coverUrls: string[] }
|
|||
|
||||
const { data: hack, error: hErr } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from, is_archive")
|
||||
.eq("slug", args.slug)
|
||||
.maybeSingle();
|
||||
if (hErr) return { ok: false, error: hErr.message } as const;
|
||||
|
|
@ -208,7 +208,7 @@ export async function presignNewPatchVersion(args: { slug: string; version: stri
|
|||
// Ensure hack exists and user has permission
|
||||
const { data: hack, error: hErr } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from, is_archive")
|
||||
.eq("slug", args.slug)
|
||||
.maybeSingle();
|
||||
if (hErr) return { ok: false, error: hErr.message } as const;
|
||||
|
|
@ -252,7 +252,7 @@ export async function presignCoverUpload(args: { slug: string; objectKey: string
|
|||
// Ensure hack exists and user has permission
|
||||
const { data: hack, error: hErr } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from, is_archive")
|
||||
.eq("slug", args.slug)
|
||||
.maybeSingle();
|
||||
if (hErr) return { ok: false, error: hErr.message } as const;
|
||||
|
|
|
|||
|
|
@ -22,9 +22,10 @@ export default async function Home() {
|
|||
// Fetch top 6 approved hacks ordered by downloads
|
||||
const { data: popularHacks } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug,title,summary,description,base_rom,downloads,created_by,current_patch,original_author")
|
||||
.select("slug,title,summary,description,base_rom,downloads,created_by,current_patch,original_author,is_archive")
|
||||
.eq("approved", true)
|
||||
.not("current_patch", "is", null)
|
||||
.is("is_archive", false)
|
||||
.order("downloads", { ascending: false })
|
||||
.limit(6);
|
||||
|
||||
|
|
@ -111,7 +112,7 @@ export default async function Home() {
|
|||
version: mappedVersions.get(r.slug) || "Pre-release",
|
||||
summary: r.summary,
|
||||
description: r.description,
|
||||
isArchive: r.original_author != null && r.current_patch === null,
|
||||
is_archive: false,
|
||||
}));
|
||||
}
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -49,15 +49,15 @@ export async function prepareSubmission(formData: FormData) {
|
|||
const tags = (formData.get("tags") as string)?.split(",").map((t) => t.trim()).filter(Boolean) || [];
|
||||
const original_author = (formData.get("original_author") as string)?.trim() || null;
|
||||
const permission_from = (formData.get("permission_from") as string)?.trim() || null;
|
||||
const isArchive = formData.get("isArchive") === "true";
|
||||
const is_archive = formData.get("is_archive") === "true";
|
||||
|
||||
// For archives, version is not required; for regular hacks, it is
|
||||
if (!title || !summary || !description || !base_rom || !language || (!isArchive && !version)) {
|
||||
if (!title || !summary || !description || !base_rom || !language || (!is_archive && !version)) {
|
||||
return { ok: false, error: "Missing required fields" } as const;
|
||||
}
|
||||
|
||||
// For archives, original_author is required
|
||||
if (isArchive && !original_author) {
|
||||
if (is_archive && !original_author) {
|
||||
return { ok: false, error: "Original author is required for Archive hacks" } as const;
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +86,8 @@ export async function prepareSubmission(formData: FormData) {
|
|||
downloads: 0,
|
||||
box_art,
|
||||
social_links,
|
||||
approved: isArchive, // Auto-approve archives
|
||||
approved: is_archive, // Auto-approve archives
|
||||
is_archive,
|
||||
patch_url: "",
|
||||
original_author: original_author || null,
|
||||
permission_from: permission_from || null,
|
||||
|
|
@ -143,7 +144,7 @@ export async function saveHackCovers(args: { slug: string; coverUrls: string[] }
|
|||
// Ensure hack exists and user has permission
|
||||
const { data: hack, error: hErr } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from, is_archive")
|
||||
.eq("slug", args.slug)
|
||||
.maybeSingle();
|
||||
if (hErr) return { ok: false, error: hErr.message } as const;
|
||||
|
|
@ -182,7 +183,7 @@ export async function presignPatchAndSaveCovers(args: {
|
|||
// Ensure hack exists and user has permission
|
||||
const { data: hack, error: hErr } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from")
|
||||
.select("slug, created_by, current_patch, original_author, permission_from, is_archive")
|
||||
.eq("slug", args.slug)
|
||||
.maybeSingle();
|
||||
if (hErr) return { ok: false, error: hErr.message } as const;
|
||||
|
|
@ -224,7 +225,7 @@ export async function confirmPatchUpload(args: { slug: string; objectKey: string
|
|||
|
||||
const { data: hack, error: hErr } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug, created_by, title, current_patch, original_author, permission_from")
|
||||
.select("slug, created_by, title, current_patch, original_author, permission_from, is_archive")
|
||||
.eq("slug", args.slug)
|
||||
.maybeSingle();
|
||||
if (hErr) return { ok: false, error: hErr.message } as const;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ type Archive = {
|
|||
creator_username: string | null;
|
||||
approved: boolean;
|
||||
current_patch: number | null;
|
||||
is_archive: boolean;
|
||||
};
|
||||
|
||||
type ArchivesData =
|
||||
|
|
|
|||
|
|
@ -436,7 +436,7 @@ export default function HackSubmitForm({
|
|||
if (github) fd.set('github', github);
|
||||
if (tags.length) fd.set('tags', tags.join(','));
|
||||
if (isArchive) {
|
||||
fd.set('isArchive', 'true');
|
||||
fd.set('is_archive', 'true');
|
||||
}
|
||||
if (originalAuthor) {
|
||||
fd.set('original_author', originalAuthor);
|
||||
|
|
@ -632,6 +632,7 @@ export default function HackSubmitForm({
|
|||
: undefined,
|
||||
createdAt: new Date().toISOString(),
|
||||
patchUrl: "",
|
||||
is_archive: isArchive,
|
||||
};
|
||||
|
||||
const hasBaseRom = !!baseRom.trim();
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ export interface HackCardAttributes {
|
|||
version: string;
|
||||
summary?: string;
|
||||
description?: string;
|
||||
isArchive?: boolean;
|
||||
is_archive: boolean;
|
||||
};
|
||||
|
||||
export default function HackCard({ hack, clickable = true, className = "" }: { hack: HackCardAttributes; clickable?: boolean; className?: string }) {
|
||||
const isArchive = !!hack.isArchive;
|
||||
const isArchive = hack.is_archive;
|
||||
const { isLinked, hasPermission, hasCached } = useBaseRoms();
|
||||
const match = baseRoms.find((r) => r.id === hack.baseRomId);
|
||||
const baseId = match?.id ?? undefined;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { SupabaseClient } from "@supabase/supabase-js";
|
|||
import { checkUserRoles } from "@/utils/user";
|
||||
|
||||
type HackWithArchiveFields = {
|
||||
is_archive: boolean;
|
||||
original_author: string | null;
|
||||
current_patch: number | null;
|
||||
permission_from?: string | null;
|
||||
|
|
@ -9,24 +10,24 @@ type HackWithArchiveFields = {
|
|||
};
|
||||
|
||||
/**
|
||||
* Check if a hack is an informational archive (has original_author but no patch)
|
||||
* Check if a hack is an informational archive (archive flag set, no patch)
|
||||
*/
|
||||
export function isInformationalArchiveHack(hack: HackWithArchiveFields): boolean {
|
||||
return hack.original_author != null && hack.current_patch === null;
|
||||
return hack.is_archive === true && hack.current_patch === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a hack is a downloadable archive (has original_author, patch, and permission_from)
|
||||
* Check if a hack is a downloadable archive (archive flag set with patch and permission)
|
||||
*/
|
||||
export function isDownloadableArchiveHack(hack: HackWithArchiveFields): boolean {
|
||||
return hack.original_author != null && hack.current_patch !== null && hack.permission_from != null;
|
||||
return hack.is_archive === true && hack.current_patch !== null && hack.permission_from != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a hack is any type of archive (informational or downloadable)
|
||||
*/
|
||||
export function isArchiveHack(hack: HackWithArchiveFields): boolean {
|
||||
return isInformationalArchiveHack(hack) || isDownloadableArchiveHack(hack);
|
||||
return hack.is_archive === true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user