Skip to content

Commit d2d9ac5

Browse files
committed
chore: rss, new additions sorting
1 parent e378b7c commit d2d9ac5

9 files changed

Lines changed: 776 additions & 488 deletions

File tree

scripts/data_import.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,14 +300,74 @@ def generate_recent_updates(repo_dir, output_json_path="static/recent_updates.js
300300

301301
# Sort by cert_date descending
302302

303+
# Sort initially by date to ensure we pick latest from each language
303304
def get_date(x):
304305
try:
305306
return x.get('cert_date', '')
306307
except:
307308
return ''
308309

309310
new_films.sort(key=get_date, reverse=True)
310-
recent_updates = new_films[:limit]
311+
312+
# Deduplicate same movie across different languages (keep most recent version)
313+
seen_movie_names = {}
314+
deduplicated_films = []
315+
316+
for film in new_films:
317+
movie_name = film.get('movie_name', '').strip().lower()
318+
if not movie_name:
319+
continue
320+
321+
if movie_name not in seen_movie_names:
322+
seen_movie_names[movie_name] = film
323+
deduplicated_films.append(film)
324+
325+
new_films = deduplicated_films
326+
327+
# Priority Diversity Selection
328+
# User requested to prioritize: English, Hindi, Telugu, Kannada, Malayalam (and Tamil implicitly as major)
329+
PRIORITY_LANGS = {'English', 'Hindi', 'Telugu', 'Kannada', 'Malayalam', 'Tamil'}
330+
331+
priority_groups = {lang: [] for lang in PRIORITY_LANGS}
332+
other_films = []
333+
334+
for film in new_films:
335+
# Simple normalization
336+
lang_raw = film.get('language', '').strip()
337+
# Handle cases like "Hindi (3D)" or leading/trailing spaces
338+
lang_base = lang_raw.split('(')[0].strip()
339+
340+
if lang_base in PRIORITY_LANGS:
341+
priority_groups[lang_base].append(film)
342+
else:
343+
other_films.append(film)
344+
345+
# Round-Robin Selection from Priority Groups
346+
selected_films = []
347+
348+
# While we have space and priority films available
349+
while len(selected_films) < limit:
350+
added_in_round = False
351+
for lang in sorted(PRIORITY_LANGS): # Deterministic order
352+
if priority_groups[lang]:
353+
selected_films.append(priority_groups[lang].pop(0))
354+
added_in_round = True
355+
if len(selected_films) >= limit:
356+
break
357+
358+
if not added_in_round:
359+
# Exhausted all priority films
360+
break
361+
362+
# Fill remaining slots with Other films (sorted by date)
363+
if len(selected_films) < limit:
364+
remaining_slots = limit - len(selected_films)
365+
# others are already sorted by date due to initial sort
366+
selected_films.extend(other_films[:remaining_slots])
367+
368+
# Re-sort final diverse selection by date
369+
selected_films.sort(key=get_date, reverse=True)
370+
recent_updates = selected_films
311371

312372
os.makedirs(os.path.dirname(output_json_path), exist_ok=True)
313373
with open(output_json_path, 'w') as f:
@@ -432,7 +492,7 @@ def generate_rss_feed(films, output_path="static/rss.xml"):
432492
except Exception as e:
433493
print(f"Error saving RSS feed: {e}")
434494

435-
def fetch_remote_data(output_path="src/lib/data/data.csv", limit=20):
495+
def fetch_remote_data(output_path="src/lib/data/data.csv", limit=50):
436496
"""Fetch latest data from remote source by cloning the repo."""
437497
os.makedirs(os.path.dirname(output_path), exist_ok=True)
438498

src/lib/components/Card.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159
class="from-sepia-med to-sepia-dark flex h-full w-full items-center justify-center bg-linear-to-br"
160160
>
161161
<div class="text-sepia-brown text-center">
162-
<div class="text-xs font-medium">No Poster</div>
162+
<div class="text-xs font-medium">{truncatedTitle}</div>
163163
</div>
164164
</div>
165165
{/if}
@@ -367,7 +367,7 @@
367367
>
368368
<div class="text-sepia-brown text-center">
369369
<div class="text-3xl opacity-60">🎬</div>
370-
<div class="text-sm font-medium">No Poster</div>
370+
<div class="text-sm font-medium">{truncatedTitle}</div>
371371
</div>
372372
</div>
373373
{/if}

src/lib/components/Footer.svelte

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,24 @@
5050
>About</a
5151
>
5252
</li>
53-
53+
<li>
54+
<a href="/faq" class="underline underline-offset-2 transition-colors hover:text-black"
55+
>FAQ</a
56+
>
57+
</li>
58+
<li>
59+
<a
60+
href="/changelog"
61+
class="underline underline-offset-2 transition-colors hover:text-black"
62+
>Change Log</a
63+
>
64+
</li>
65+
<li>
66+
<a
67+
href="/rss.xml"
68+
class="underline underline-offset-2 transition-colors hover:text-black">RSS Feed</a
69+
>
70+
</li>
5471
<li>
5572
<a
5673
href="https://github.com/diagram-chasing/cbfc-watch"

src/routes/+page.svelte

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,28 @@
5656
const response = await fetch('/recent_updates.json');
5757
if (response.ok) {
5858
const rawData = await response.json();
59-
newAdditions = rawData.map((m) => ({
60-
...m,
61-
name: m.movie_name,
62-
year: m.imdb_year
63-
? parseInt(m.imdb_year)
64-
: m.cert_date
65-
? new Date(m.cert_date).getFullYear()
66-
: '',
67-
posterUrl: m.imdb_poster_url,
68-
languages: m.language ? [m.language] : []
69-
}));
59+
newAdditions = rawData
60+
.map((m) => ({
61+
...m,
62+
name: m.movie_name,
63+
year: m.imdb_year
64+
? parseInt(m.imdb_year)
65+
: m.cert_date
66+
? new Date(m.cert_date).getFullYear()
67+
: '',
68+
posterUrl: m.imdb_poster_url,
69+
languages: m.language ? [m.language] : []
70+
}))
71+
.sort((a, b) => {
72+
// Primary sort: movies with posters first
73+
const aHasPoster = !!a.posterUrl?.trim();
74+
const bHasPoster = !!b.posterUrl?.trim();
75+
if (aHasPoster !== bHasPoster) {
76+
return aHasPoster ? -1 : 1;
77+
}
78+
// Secondary sort: by date (most recent first)
79+
return (b.cert_date || '').localeCompare(a.cert_date || '');
80+
});
7081
}
7182
} catch (e) {
7283
console.error('Failed to load recent updates', e);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
import { json } from '@sveltejs/kit';
3+
import type { RequestHandler } from './$types';
4+
5+
export const GET: RequestHandler = async ({ platform, url }) => {
6+
const page = Number(url.searchParams.get('page')) || 1;
7+
const limit = 100;
8+
const offset = (page - 1) * limit;
9+
10+
try {
11+
const db = platform?.env?.DB;
12+
13+
if (!db) {
14+
return json({ films: [], hasNext: false }, { status: 404 });
15+
}
16+
17+
// Fetch films sorted by cert_date DESC
18+
const results = await db.prepare(`
19+
SELECT
20+
slug,
21+
name,
22+
year,
23+
language,
24+
rating,
25+
cert_date,
26+
imdb_rating
27+
FROM films
28+
WHERE cert_date IS NOT NULL
29+
ORDER BY cert_date DESC
30+
LIMIT ? OFFSET ?
31+
`).bind(limit + 1, offset).all();
32+
33+
const films = results.results || [];
34+
const hasNext = films.length > limit;
35+
if (hasNext) {
36+
films.pop();
37+
}
38+
39+
return json({
40+
films,
41+
page,
42+
hasNext
43+
});
44+
45+
} catch (e) {
46+
console.error("Error fetching changelog:", e);
47+
return json({ error: "Failed to load data" }, { status: 500 });
48+
}
49+
};

src/routes/changelog/+page.svelte

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<script lang="ts">
2+
import { Calendar, ChevronRight } from 'lucide-svelte';
3+
import Button from '$lib/components/ui/button/button.svelte';
4+
import {
5+
Pagination,
6+
PaginationContent,
7+
PaginationItem,
8+
PaginationLink,
9+
PaginationNextButton as PaginationNext,
10+
PaginationPrevButton as PaginationPrevious,
11+
PaginationEllipsis
12+
} from '$lib/components/ui/pagination';
13+
14+
let { data } = $props();
15+
16+
// Grouping Logic
17+
let groupedFilms = $derived(data.films ? groupFilmsByMonth(data.films) : {});
18+
19+
function groupFilmsByMonth(films: any[]) {
20+
const groups: Record<string, any[]> = {};
21+
films.forEach((film) => {
22+
if (!film.cert_date) return;
23+
const date = new Date(film.cert_date);
24+
const key = date.toLocaleString('default', { month: 'long', year: 'numeric' });
25+
if (!groups[key]) groups[key] = [];
26+
groups[key].push(film);
27+
});
28+
return groups;
29+
}
30+
31+
// Preserve Month Order (keys might be scrambled)
32+
let sortedGroupKeys = $derived(
33+
Object.keys(groupedFilms).sort((a, b) => {
34+
return new Date(b).getTime() - new Date(a).getTime();
35+
})
36+
);
37+
</script>
38+
39+
<div class="mx-auto w-full max-w-4xl">
40+
<!-- Header Section -->
41+
<div class="grain-effect mb-6">
42+
<div class="space-y-3 py-6">
43+
<h1
44+
class="font-gothic flex items-center gap-3 text-4xl font-bold tracking-tight text-black md:text-5xl"
45+
>
46+
<Calendar class="h-8 w-8 md:h-10 md:w-10" />
47+
Certification Log
48+
</h1>
49+
<p class="font-atkinson text-base leading-relaxed text-gray-700 md:text-lg">
50+
Complete chronological history of CBFC certifications
51+
</p>
52+
</div>
53+
</div>
54+
55+
{#if data.error}
56+
<div class="border-sepia-dark rounded-xs border bg-red-50 p-4 shadow-xs">
57+
<p class="font-atkinson text-sm text-red-800">{data.error}</p>
58+
</div>
59+
{:else if data.films.length === 0}
60+
<div class="bg-sepia-light border-sepia-dark rounded-xs border py-12 text-center shadow-xs">
61+
<p class="font-atkinson text-sepia-brown">No records found.</p>
62+
</div>
63+
{:else}
64+
<div class="space-y-8">
65+
{#each sortedGroupKeys as month}
66+
<section>
67+
<!-- Simple month heading -->
68+
<h2 class="font-gothic mb-3 text-2xl font-medium tracking-tight text-black">
69+
{month}
70+
</h2>
71+
72+
<!-- Simple list of films -->
73+
<ul class="space-y-2 pl-4">
74+
{#each groupedFilms[month] as film}
75+
<li class="font-atkinson text-sm leading-relaxed">
76+
<a
77+
href="/film/{film.slug}"
78+
class="text-sepia-brown inline transition-colors hover:text-black"
79+
>
80+
<span class="font-medium">{film.name}</span>
81+
<span class="text-gray-500"> ({film.year})</span>
82+
</a>
83+
<span class="text-gray-500"> · </span>
84+
<span class="text-gray-600">{film.language}</span>
85+
{#if film.rating}
86+
<span class="text-gray-500"> · </span>
87+
<span class="text-gray-600">{film.rating}</span>
88+
{/if}
89+
</li>
90+
{/each}
91+
</ul>
92+
</section>
93+
{/each}
94+
</div>
95+
96+
<!-- Pagination -->
97+
{#if data.page > 1 || data.hasNext}
98+
<div class="border-sepia-dark mt-8 flex items-center justify-between border-t pt-6">
99+
{#if data.page > 1}
100+
<Button href="/changelog?page={data.page - 1}" variant="secondary" size="sm">
101+
← Previous
102+
</Button>
103+
{:else}
104+
<div></div>
105+
{/if}
106+
107+
<span class="font-atkinson text-sepia-brown text-sm font-medium">Page {data.page}</span>
108+
109+
{#if data.hasNext}
110+
<Button href="/changelog?page={data.page + 1}" variant="secondary" size="sm">
111+
Next →
112+
</Button>
113+
{:else}
114+
<div></div>
115+
{/if}
116+
</div>
117+
{/if}
118+
{/if}
119+
</div>

src/routes/changelog/+page.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
import type { PageLoad } from './$types';
3+
import { error } from '@sveltejs/kit';
4+
5+
export const load: PageLoad = async ({ fetch, url }) => {
6+
const page = Number(url.searchParams.get('page')) || 1;
7+
8+
try {
9+
const response = await fetch(`/api/changelog?page=${page}`);
10+
11+
if (!response.ok) {
12+
throw error(response.status, 'Failed to fetch changelog');
13+
}
14+
15+
const data = await response.json();
16+
17+
return {
18+
films: data.films,
19+
page: data.page,
20+
hasNext: data.hasNext,
21+
error: data.error
22+
};
23+
} catch (e) {
24+
console.error("Load error:", e);
25+
return {
26+
films: [],
27+
page,
28+
hasNext: false,
29+
error: "Failed to load data"
30+
};
31+
}
32+
};

0 commit comments

Comments
 (0)