Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,4 @@ pnpm-debug.log*

# temporary files
web-scraper/temp/
web-scraper/files/
web-scraper/files2/
web-scraper/files*
17 changes: 4 additions & 13 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,11 @@ import playformCompress from '@playform/compress';
export default defineConfig({
site: 'https://cuza.pages.dev',
trailingSlash: 'never',
redirects: {
'/fizica': '/',
},
build: {
format: 'file',
inlineStylesheets: 'always',
assets: 'file',
},
vite: {
build: {
chunkSizeWarningLimit: 2000,
rollupOptions: {
output: {
assetFileNames: `file/[name][extname]`,
},
},
},
plugins: [tailwindcss()],
},
markdown: {
Expand All @@ -33,7 +21,10 @@ export default defineConfig({
},
integrations: [
sitemap({
filter: (page) => !page.includes('/install') && !page.includes('/upload'),
filter: (page) =>
!page.includes('/install') &&
!page.includes('/upload') &&
!page.includes('/contribute'),
}),
react(),
playformCompress(),
Expand Down
103 changes: 54 additions & 49 deletions cuza-worker/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@ function getSubtree(
return current;
}

/**
* Get a subtree as an object, returning {} if the path doesn't exist or is a leaf.
*/
function getSubtreeAsObject(
index: FileStructure,
segments: string[],
): FileStructure {
const subtree = getSubtree(index, segments);
return subtree !== null && typeof subtree !== 'string' ? subtree : {};
}

/**
* Set a value at a nested path, creating intermediate objects as needed.
*/
Expand Down Expand Up @@ -270,18 +281,34 @@ function pruneMissingIndexLeaves(
return cleaned;
}

const VALID_PAGES = new Set(['bac', 'teste', 'sim']);
const VALID_PAGES = new Set(['bac', 'sim']);
const VALID_SIMULATIONS = new Set(['judetene', 'locale']);
const VALID_TYPE2 = new Set(['var', 'bar']);
const BAC_TITLE_TO_CODE: Record<string, string> = {
Model: 'SM',
Simulare: 'sim',
'Sesiunea-I': 'S1',
'Sesiunea-II': 'S2',
'Sesiune-speciala': 'SS',
};
const VALID_VARIANTS = new Set([
'01',
'02',
'03',
'04',
'05',
'06',
'07',
'08',
'09',
'10',
]);
const VARIANT_REQUIRED_TITLES = new Set([
'Sesiunea-I',
'Sesiunea-II',
'Sesiunea-speciala',
]);
const BAC_TITLES = [
'Model',
'Simulare',
'Sesiunea-I',
'Sesiunea-II',
'Sesiunea-speciala',
] as const;
const YEAR_RE = /^20[1-3]\d$/;
const TEST_NUMBER_RE = /^\d{1,2}$/;
const MAX_FILE_BYTES = 20 * 1024 * 1024; // 20 MB

/**
Expand Down Expand Up @@ -319,20 +346,14 @@ function validateFormData(formData: FormData): string | null {
// ── Page-specific validation ───────────────────────────────────────────────
if (page === 'bac') {
const title = formData.get('title') as string | null;
if (!title) {
return 'Lipsă titlu';
}
if (!(title in BAC_TITLE_TO_CODE)) {
return 'Titlu BAC invalid';
}
}
if (page === 'teste') {
const testNumber = formData.get('testNumber') as string | null;
if (!testNumber) {
return 'Lipsă număr test';
if (!title || !(BAC_TITLES as readonly string[]).includes(title)) {
return 'Lipsă tip sau tip invalid';
}
if (!TEST_NUMBER_RE.test(testNumber)) {
return 'Număr test invalid';
if (VARIANT_REQUIRED_TITLES.has(title)) {
const variant = formData.get('variant') as string | null;
if (!variant || !VALID_VARIANTS.has(variant)) {
return 'Lipsă variantă sau variantă invalidă (01-10)';
}
}
}
if (page === 'sim') {
Expand All @@ -358,32 +379,25 @@ interface UploadPathData {
page: string;
year: string;
title: string | null;
variant: string | null;
type2: string;
reserve: boolean;
testNumber: string | null;
simulation: string | null;
county: string | null;
local: string | null;
}

function generateUploadPath(data: UploadPathData): { r2Key: string } {
const { page, year, type2, testNumber, simulation, reserve } = data;
const { page, year, variant, simulation, reserve, type2 } = data;
const title = sanitizePathSegment(data.title);
const county = sanitizePathSegment(data.county);
const local = sanitizePathSegment(data.local);

if (page === 'bac') {
const baseCode = BAC_TITLE_TO_CODE[data.title ?? ''];
const code = reserve ? `${baseCode}R` : baseCode;
return {
r2Key: `fizica/pages/teoretic/${year}/${title}/E_d_fizica_${year}_${code}_${type2}.pdf`,
};
}
if (page === 'teste') {
const testNumberNormalized = (testNumber ?? '').padStart(2, '0');
const testType = type2 === 'bar' ? 'Bar' : 'Test';
const folderName = reserve ? `${title}-rezerva` : title;
const varSuffix = variant ? `_${variant}` : '_00';
return {
r2Key: `fizica/pages/teoretic/teste-de-antrenament/${year}/E_d_fizica_${year}_${testType}_${testNumberNormalized}.pdf`,
r2Key: `fizica/pages/teoretic/bac/${year}/${folderName}/E_d_fizica_teoretic_vocational_${year}_${type2}${varSuffix}.pdf`,
};
}
const location = simulation === 'judetene' ? county : local;
Expand All @@ -410,23 +424,14 @@ export function registerRoutes(app: Hono<{ Bindings: Bindings }>): void {

const index = await getIndex(c.env.FILES);
const segments = resolvePathSegments(subject, page);
const subtree = getSubtree(index, segments);
const content: FileStructure =
subtree !== null && typeof subtree !== 'string'
? (subtree as FileStructure)
: {};
const content = getSubtreeAsObject(index, segments);
const years = extractYears(content);

// Extra content
const extraSegments = resolvePathSegments(
subject,
subject.toLowerCase() === 'admitere' ? `${page}/extra` : 'extra',
);
const extraSubtree = getSubtree(index, extraSegments);
const extra: FileStructure =
extraSubtree !== null && typeof extraSubtree !== 'string'
? (extraSubtree as FileStructure)
: {};
const extra = getSubtreeAsObject(index, extraSegments);

return c.json({ content, extra, years });
});
Expand Down Expand Up @@ -475,25 +480,25 @@ export function registerRoutes(app: Hono<{ Bindings: Bindings }>): void {
const page = formData.get('page') as string;
const year = formData.get('year') as string;
const title = formData.get('title') as string | null;
const type2 = formData.get('type2') as string;
const variant = formData.get('variant') as string | null;
const reserveRaw = formData.get('reserve') as string | null;
const reserve = reserveRaw === 'on' || reserveRaw === 'true';
const testNumber = formData.get('testNumber') as string | null;
const simulation = formData.get('simulation') as string | null;
const county = formData.get('county') as string | null;
const local = formData.get('local') as string | null;
const type2 = formData.get('type2') as string;
const file = formData.get('file') as unknown as File;

const { r2Key } = generateUploadPath({
page,
year,
title,
type2,
variant,
reserve,
testNumber,
simulation,
county,
local,
type2,
});

await c.env.FILES.put(r2Key, file, {
Expand Down
9 changes: 9 additions & 0 deletions cuza-worker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ app.use('*', async (c, next) => {
return next();
});

app.use('*', async (c, next) => {
await next();
c.res.headers.set('X-Robots-Tag', 'noindex');
});

app.get('/robots.txt', (c) => {
return c.text('User-agent: *\nDisallow: /\n');
});

app.use(
'*',
cors({
Expand Down
37 changes: 0 additions & 37 deletions functions/api/[[route]].ts

This file was deleted.

2 changes: 1 addition & 1 deletion public/robots.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
User-agent: *
Allow: /
Allow: /api/file/
Disallow: /install
Disallow: /upload
Disallow: /contribute

Sitemap: https://cuza.pages.dev/sitemap-index.xml
2 changes: 1 addition & 1 deletion public/sw.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var CACHE = 'cuza-pages-v3';
var CACHE = 'cuza-pages-v4';

self.addEventListener('install', function (_event) {
self.skipWaiting();
Expand Down
7 changes: 5 additions & 2 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ export class ApiService {

constructor(baseUrl?: string) {
this.baseUrl =
(baseUrl ?? import.meta.env.PUBLIC_WORKER_URL.replace(/\/$/, '')) ||
DEFAULT_WORKER_URL;
baseUrl ??
(import.meta.env.PUBLIC_WORKER_URL ?? DEFAULT_WORKER_URL).replace(
/\/$/,
'',
);
}

private async fetchJson<T>(url: string): Promise<T | null> {
Expand Down
65 changes: 41 additions & 24 deletions src/components/Extra.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,50 @@ interface Props {
page: string;
extraData: FileStructure;
years?: number[];
contentFolders?: { key: string; label: string }[];
}

const { subject, page, extraData, years } = Astro.props as Props;
const {
subject,
page,
extraData,
years,
contentFolders = [],
} = Astro.props as Props;

const isAdmitere = subject === 'admitere';
---

<p class="text-lg font-bold text-slate-300">Extra</p>
{
(subject === 'admitere' && page === 'fizica') || subject === 'fizica' ? (
<a
class="extra-link"
href="https://drive.google.com/drive/folders/1RUHqNmCLyEN-FV3IXjw_aIpZEaE-qJWq?usp=sharing"
target="_blank"
>
Culegeri fizică
</a>
) : null
}
{
isAdmitere ? (
<List subject="admitere" page={`${page}/extra`} content={extraData} />
) : (
<List subject={subject} page="extra" content={extraData} />
)
}

<Shortcuts years={years} />
const shouldFilter2007 =
(subject === 'fizica' && page === 'teoretic') ||
(subject === 'mate' && page === 'mate-info');
const yearsfix = shouldFilter2007 ? years?.filter((y) => y !== 2007) : years;
---

<ToggleExpansion />
<div class="bg-zinc-950/90 rounded-3xl py-3 px-4 flex flex-col">
<div>
{
Object.keys(extraData).length > 0 && (
<>
<p class="text-lg font-bold text-slate-300">Extra</p>
{(subject === 'admitere' && page === 'fizica') ||
subject === 'fizica' ? (
<a
class="extra-link"
href="https://drive.google.com/drive/folders/1RUHqNmCLyEN-FV3IXjw_aIpZEaE-qJWq?usp=sharing"
target="_blank"
>
Culegeri fizică
</a>
) : null}
{isAdmitere ? (
<List subject="admitere" page={`${page}/extra`} content={extraData} />
) : (
<List subject={subject} page="extra" content={extraData} />
)}
</>
)
}
</div>
<Shortcuts years={yearsfix} folders={contentFolders} />
<ToggleExpansion />
</div>
2 changes: 1 addition & 1 deletion src/components/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
class="flex flex-col sm:flex-row justify-between items-center pt-3 gap-2"
>
<p class="mb-0">
Copyright © {new Date().getFullYear()}
Copyright © {new Date().getFullYear()} &nbsp;
<a
class="hover:text-slate-800"
href="https://github.com/DynoW"
Expand Down
Loading