From a8da60b1ce8bbfc6cce9c9e32dce93fc01c261db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lipt=C3=A1k=20P=C3=A9ter?= Date: Thu, 30 Jul 2026 22:23:26 +0200 Subject: [PATCH 01/23] feat: add career page data model with sections, highlights and job applications --- src/collections/CareerSettings.ts | 121 + src/collections/JobApplications.ts | 169 + src/collections/Recruitment.ts | 157 +- src/lib/google-sheets.ts | 59 + src/lib/payload-cms.ts | 15 + .../20260729_182451_karrier_oldal.json | 3911 +++++++++++++++++ .../20260729_182451_karrier_oldal.ts | 101 + src/migrations/index.ts | 8 +- src/payload-types.ts | 342 +- src/payload.config.ts | 5 +- 10 files changed, 4840 insertions(+), 48 deletions(-) create mode 100644 src/collections/CareerSettings.ts create mode 100644 src/collections/JobApplications.ts create mode 100644 src/lib/google-sheets.ts create mode 100644 src/migrations/20260729_182451_karrier_oldal.json create mode 100644 src/migrations/20260729_182451_karrier_oldal.ts diff --git a/src/collections/CareerSettings.ts b/src/collections/CareerSettings.ts new file mode 100644 index 0000000..a2596a4 --- /dev/null +++ b/src/collections/CareerSettings.ts @@ -0,0 +1,121 @@ +import { FixedToolbarFeature, lexicalEditor } from "@payloadcms/richtext-lexical"; +import type { GlobalConfig } from "payload"; + +export const CareerSettings: GlobalConfig = { + slug: "career-settings", + label: { + en: "Career page", + hu: "Karrier oldal", + }, + admin: { + description: + "A Karrier oldal bevezetője és a jelentkezés módjának beállítása.", + }, + access: { + read: () => true, + update: ({ req: { user } }) => !!user, + }, + fields: [ + { + type: "tabs", + tabs: [ + { + label: { + en: "Intro", + hu: "Bevezető", + }, + fields: [ + { + name: "intro", + type: "richText", + required: false, + label: "Általános leírás", + admin: { + description: "Az oldal tetején, a csoportok listája fölött jelenik meg.", + }, + editor: lexicalEditor({ + features: ({ defaultFeatures }) => [ + ...defaultFeatures, + FixedToolbarFeature(), + ], + }), + }, + { + name: "introEng", + type: "richText", + required: false, + label: "Általános leírás (angolul)", + editor: lexicalEditor({ + features: ({ defaultFeatures }) => [ + ...defaultFeatures, + FixedToolbarFeature(), + ], + }), + }, + ], + }, + { + label: { + en: "Application", + hu: "Jelentkezés", + }, + fields: [ + { + name: "applicationMode", + type: "select", + required: false, + defaultValue: "builtIn", + label: "Jelentkezés módja", + options: [ + { label: "Beépített űrlap az oldal alján", value: "builtIn" }, + { label: "Külső Google űrlap", value: "googleForm" }, + ], + admin: { + description: + "Beépített űrlap esetén a jelentkezések a Payload adminba érkeznek (és a Google táblázatba, ha be van állítva a webhook).", + }, + }, + { + name: "googleFormUrl", + type: "text", + required: false, + label: "Google űrlap linkje", + admin: { + description: "Csak a „Külső Google űrlap” mód esetén használjuk.", + condition: (data) => data?.applicationMode === "googleForm", + }, + }, + { + name: "applicationsOpen", + type: "checkbox", + defaultValue: true, + label: "Jelentkezés nyitva", + admin: { + description: + "Kikapcsolva az oldal továbbra is elérhető, de a jelentkezési űrlap és a „Jelentkezz” gomb helyett egy tájékoztató szöveg jelenik meg.", + }, + }, + { + name: "applicationsClosedText", + type: "text", + required: false, + label: "Szöveg zárt jelentkezés esetén", + admin: { + condition: (data) => data?.applicationsOpen === false, + }, + }, + { + name: "applicationsClosedTextEng", + type: "text", + required: false, + label: "Szöveg zárt jelentkezés esetén (angolul)", + admin: { + condition: (data) => data?.applicationsOpen === false, + }, + }, + ], + }, + ], + }, + ], +}; diff --git a/src/collections/JobApplications.ts b/src/collections/JobApplications.ts new file mode 100644 index 0000000..010d365 --- /dev/null +++ b/src/collections/JobApplications.ts @@ -0,0 +1,169 @@ +import { sendApplicationToSheet } from "@/lib/google-sheets"; +import type { CollectionAfterChangeHook, CollectionConfig } from "payload"; + +/** + * Új jelentkezés kiírása a Google Sheets táblázatba, majd a szinkron + * eredményének visszaírása a dokumentumra. A hiba sosem bukik ki a hívóhoz: + * a jelentkezés a Payloadban akkor is elmentve marad. + */ +const syncToGoogleSheet: CollectionAfterChangeHook = async ({ doc, operation, req, context }) => { + if (operation !== "create" || context?.skipSheetSync) { + return doc; + } + + const result = await sendApplicationToSheet({ + id: doc.id, + submittedAt: doc.createdAt, + name: doc.name, + email: doc.email, + phone: doc.phone ?? "", + university: doc.university ?? "", + major: doc.major ?? "", + semester: doc.semester ?? "", + group: doc.groupName ?? "", + position: doc.positionName ?? "", + motivation: doc.motivation ?? "", + }); + + if (result.status === "error") { + req.payload.logger.error( + `A(z) #${doc.id} jelentkezés Google Sheets szinkronizálása sikertelen: ${result.message}`, + ); + } + + try { + await req.payload.update({ + collection: "job-applications", + id: doc.id, + data: { + sheetSyncStatus: result.status, + sheetSyncError: result.status === "error" ? result.message : null, + }, + overrideAccess: true, + // A create tranzakciójában vagyunk — req nélkül a friss sor még nem látszana. + req, + context: { skipSheetSync: true }, + }); + } catch (error) { + req.payload.logger.error( + `A(z) #${doc.id} jelentkezés szinkron-státuszának mentése sikertelen: ${error}`, + ); + } + + return doc; +}; + +export const JobApplications: CollectionConfig = { + slug: "job-applications", + labels: { + singular: "Jelentkezés", + plural: "Jelentkezések", + }, + admin: { + description: "A Karrier oldalon beérkezett jelentkezések.", + useAsTitle: "name", + defaultColumns: ["name", "email", "positionName", "groupName", "createdAt"], + }, + access: { + // Létrehozás kizárólag a szerveroldali API route-on keresztül (overrideAccess), + // hogy a nyilvános REST végponton ne lehessen közvetlenül beküldeni. + create: () => false, + read: ({ req: { user } }) => !!user, + update: ({ req: { user } }) => !!user, + delete: ({ req: { user } }) => !!user, + }, + fields: [ + { + name: "name", + type: "text", + required: true, + label: "Név", + }, + { + name: "email", + type: "email", + required: true, + label: "E-mail cím", + }, + { + name: "phone", + type: "text", + required: false, + label: "Telefonszám", + }, + { + name: "university", + type: "text", + required: false, + label: "Egyetem / kar", + }, + { + name: "major", + type: "text", + required: false, + label: "Szak", + }, + { + name: "semester", + type: "text", + required: false, + label: "Hányadik félév", + }, + { + name: "groupName", + type: "text", + required: false, + label: "Megpályázott csoport", + }, + { + name: "positionName", + type: "text", + required: false, + label: "Megpályázott pozíció", + }, + { + name: "motivation", + type: "textarea", + required: false, + label: "Motiváció / üzenet", + }, + { + name: "consent", + type: "checkbox", + required: false, + label: "Adatkezelési hozzájárulás", + admin: { + readOnly: true, + }, + }, + { + name: "sheetSyncStatus", + type: "select", + required: false, + label: "Google Sheets szinkron", + options: [ + { label: "Kiírva", value: "ok" }, + { label: "Kihagyva (nincs webhook beállítva)", value: "skipped" }, + { label: "Hiba", value: "error" }, + ], + admin: { + readOnly: true, + position: "sidebar", + }, + }, + { + name: "sheetSyncError", + type: "text", + required: false, + label: "Szinkron hibaüzenete", + admin: { + readOnly: true, + position: "sidebar", + condition: (data) => data?.sheetSyncStatus === "error", + }, + }, + ], + hooks: { + afterChange: [syncToGoogleSheet], + }, +}; diff --git a/src/collections/Recruitment.ts b/src/collections/Recruitment.ts index 1f5e7b2..b8f9e7e 100644 --- a/src/collections/Recruitment.ts +++ b/src/collections/Recruitment.ts @@ -4,13 +4,14 @@ import { FixedToolbarFeature, lexicalEditor} from '@payloadcms/richtext-lexical' export const Recruitment: CollectionConfig = { slug: "recruitment", labels: { - singular: "Toborzás", - plural: "Toborzás", + singular: "Karrier csoport", + plural: "Karrier csoportok", }, admin: { description: - "Nyitott pozíciók a csoportokon belül, funkcionális területek szerint csoportosítva.", + "A Karrier oldalon megjelenő csoportok és a hozzájuk tartozó pozíciók.", useAsTitle: "groupName", + defaultColumns: ["groupName", "order"], }, fields: [ { @@ -25,6 +26,25 @@ export const Recruitment: CollectionConfig = { required: true, label: "Csoport neve angolul", }, + { + name: "image", + type: "upload", + relationTo: "media", + required: false, + label: "Csoport képe", + admin: { + description: "A csoport sávjának bal oldalán jelenik meg. Fekvő (kb. 4:3) kép ajánlott.", + }, + }, + { + name: "order", + type: "number", + required: false, + label: "Rendezési szám", + admin: { + description: "A csoportok növekvő sorrendben jelennek meg. Üresen hagyva a lista végére kerül.", + }, + }, { name: "description", type: "richText", @@ -68,11 +88,23 @@ export const Recruitment: CollectionConfig = { required: true, label: "Pozíció neve (angolul)", }, + { + name: "positionOpen", + type: "checkbox", + required: false, + label: "Nyitott pozíció", + admin: { + description: "Csak a bepipált pozíciók jelennek meg a Karrier oldalon.", + }, + }, { name: "positionDescription", type: "richText", - required: true, - label: "Pozíció leírása", + required: false, + label: "Pozíció bevezetője", + admin: { + description: "Rövid felvezető szöveg, a szekciók fölött jelenik meg. Opcionális.", + }, editor: lexicalEditor({ features: ({ defaultFeatures }) => [ ...defaultFeatures, @@ -83,8 +115,8 @@ export const Recruitment: CollectionConfig = { { name: "positionDescriptionEng", type: "richText", - required: true, - label: "Pozíció leírása (angolul)", + required: false, + label: "Pozíció bevezetője (angolul)", editor: lexicalEditor({ features: ({ defaultFeatures }) => [ ...defaultFeatures, @@ -93,12 +125,113 @@ export const Recruitment: CollectionConfig = { }) }, { - name: "positionOpen", - type: "checkbox", - required: true, - label: "Nyitott pozíció", + name: "sections", + type: "array", + required: false, + label: "Leírás szekciói", + labels: { + singular: "Szekció", + plural: "Szekciók", + }, + admin: { + description: + "A pozíció leírása szekciókra bontva, pl. Feladatok, Szükséges skillek, Miben fejlődhetsz, Előnyök.", + }, + fields: [ + { + name: "sectionTitle", + type: "text", + required: true, + label: "Szekció címe", + }, + { + name: "sectionTitleEng", + type: "text", + required: true, + label: "Szekció címe (angolul)", + }, + { + name: "sectionContent", + type: "richText", + required: true, + label: "Szekció tartalma", + editor: lexicalEditor({ + features: ({ defaultFeatures }) => [ + ...defaultFeatures, + FixedToolbarFeature(), + ] + }) + }, + { + name: "sectionContentEng", + type: "richText", + required: true, + label: "Szekció tartalma (angolul)", + editor: lexicalEditor({ + features: ({ defaultFeatures }) => [ + ...defaultFeatures, + FixedToolbarFeature(), + ] + }) + }, + ], + }, + { + name: "highlights", + type: "group", + label: "Kiemelt tudnivalók", + admin: { + description: "A pozíció leírása alatt, ikonokkal kiemelve jelennek meg. Az üresen hagyott mezők nem jelennek meg.", + }, + fields: [ + { + name: "joining", + type: "text", + required: false, + label: "Csatlakozás", + admin: { + description: "Pl. „2. félévtől, gépészmérnök hallgatóknak” vagy „Bármely szakos hallgatónak”.", + }, + }, + { + name: "joiningEng", + type: "text", + required: false, + label: "Csatlakozás (angolul)", + }, + { + name: "timeCommitment", + type: "text", + required: false, + label: "Időráfordítás", + admin: { + description: "Pl. „heti 10-15 óra”.", + }, + }, + { + name: "timeCommitmentEng", + type: "text", + required: false, + label: "Időráfordítás (angolul)", + }, + { + name: "subsystem", + type: "text", + required: false, + label: "Részegység", + admin: { + description: "Pl. „Futómű”, „Akkumulátor”, „Aerodinamika”.", + }, + }, + { + name: "subsystemEng", + type: "text", + required: false, + label: "Részegység (angolul)", + }, + ], }, ], }, ], -}; \ No newline at end of file +}; diff --git a/src/lib/google-sheets.ts b/src/lib/google-sheets.ts new file mode 100644 index 0000000..f261f5f --- /dev/null +++ b/src/lib/google-sheets.ts @@ -0,0 +1,59 @@ +/** + * Jelentkezések továbbítása egy Google Sheets táblázatba. + * + * A táblázat oldalán egy Google Apps Script webalkalmazás fogadja a POST kérést + * és beszúrja az új sort. A beállítás lépései a README-ben találhatók. + * + * Szükséges környezeti változók: + * GOOGLE_SHEETS_WEBHOOK_URL — az Apps Script webalkalmazás /exec URL-je + * GOOGLE_SHEETS_WEBHOOK_SECRET — közös titok, a szkript ezt ellenőrzi (opcionális, de ajánlott) + * + * Ha a GOOGLE_SHEETS_WEBHOOK_URL nincs beállítva, a szinkronizálás csendben kimarad: + * a jelentkezés ilyenkor is elmentődik a Payload adminban. + */ + +export type SheetSyncResult = + | { status: "skipped" } + | { status: "ok" } + | { status: "error"; message: string }; + +// A jelentkező a válaszra vár, ezért rövid a türelmi idő: ha a webhook lassú, +// a jelentkezés attól még elmentve marad a Payloadban. +const WEBHOOK_TIMEOUT_MS = 5_000; + +export async function sendApplicationToSheet( + row: Record, +): Promise { + const url = process.env.GOOGLE_SHEETS_WEBHOOK_URL; + + if (!url) { + return { status: "skipped" }; + } + + try { + const response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + secret: process.env.GOOGLE_SHEETS_WEBHOOK_SECRET ?? "", + ...row, + }), + signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS), + redirect: "follow", + }); + + if (!response.ok) { + return { + status: "error", + message: `A Google Sheets webhook ${response.status} státusszal válaszolt.`, + }; + } + + return { status: "ok" }; + } catch (error) { + return { + status: "error", + message: error instanceof Error ? error.message : "Ismeretlen hiba", + }; + } +} diff --git a/src/lib/payload-cms.ts b/src/lib/payload-cms.ts index 41fa3b9..85e606f 100644 --- a/src/lib/payload-cms.ts +++ b/src/lib/payload-cms.ts @@ -1,5 +1,6 @@ import { Car, + CareerSetting, Contact, Event, FormulaStudent, @@ -28,10 +29,24 @@ export async function getRecruitmentData(): Promise { const recruitmentData = await payload.find({ collection: "recruitment", limit: 1000, + sort: "order", + depth: 1, // hogy a csoport képe (upload) is betöltődjön }); return recruitmentData.docs; } +export async function getCareerSettings(): Promise { + const payload = await getPayload({ config }); + try { + return (await payload.findGlobal({ + slug: "career-settings", + })) as CareerSetting; + } catch (error) { + console.error("Error fetching career settings:", error); + return null; + } +} + export async function getCars(): Promise { const payload = await getPayload({ config }); const cars = await payload.find({ diff --git a/src/migrations/20260729_182451_karrier_oldal.json b/src/migrations/20260729_182451_karrier_oldal.json new file mode 100644 index 0000000..9f0cde0 --- /dev/null +++ b/src/migrations/20260729_182451_karrier_oldal.json @@ -0,0 +1,3911 @@ +{ + "version": "7", + "dialect": "postgresql", + "tables": { + "public.users_sessions": { + "name": "users_sessions", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "users_sessions_order_idx": { + "name": "users_sessions_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_sessions_parent_id_idx": { + "name": "users_sessions_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users_sessions_parent_id_fk": { + "name": "users_sessions_parent_id_fk", + "tableFrom": "users_sessions", + "tableTo": "users", + "columnsFrom": [ + "_parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "email": { + "name": "email", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "reset_password_token": { + "name": "reset_password_token", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "reset_password_expiration": { + "name": "reset_password_expiration", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "salt": { + "name": "salt", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "login_attempts": { + "name": "login_attempts", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "lock_until": { + "name": "lock_until", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "users_updated_at_idx": { + "name": "users_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_created_at_idx": { + "name": "users_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media": { + "name": "media", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "alt": { + "name": "alt", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "thumbnail_u_r_l": { + "name": "thumbnail_u_r_l", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "mime_type": { + "name": "mime_type", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "filesize": { + "name": "filesize", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "focal_x": { + "name": "focal_x", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "focal_y": { + "name": "focal_y", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "media_updated_at_idx": { + "name": "media_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "media_created_at_idx": { + "name": "media_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "media_filename_idx": { + "name": "media_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.articles_gallery": { + "name": "articles_gallery", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "image_id": { + "name": "image_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "articles_gallery_order_idx": { + "name": "articles_gallery_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "articles_gallery_parent_id_idx": { + "name": "articles_gallery_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "articles_gallery_image_idx": { + "name": "articles_gallery_image_idx", + "columns": [ + { + "expression": "image_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "articles_gallery_image_id_media_id_fk": { + "name": "articles_gallery_image_id_media_id_fk", + "tableFrom": "articles_gallery", + "tableTo": "media", + "columnsFrom": [ + "image_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "articles_gallery_parent_id_fk": { + "name": "articles_gallery_parent_id_fk", + "tableFrom": "articles_gallery", + "tableTo": "articles", + "columnsFrom": [ + "_parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.articles": { + "name": "articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "title_eng": { + "name": "title_eng", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "short_description": { + "name": "short_description", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "short_description_eng": { + "name": "short_description_eng", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "content_eng": { + "name": "content_eng", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "published_date": { + "name": "published_date", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true + }, + "featured_image_id": { + "name": "featured_image_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "category_eng": { + "name": "category_eng", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "articles_featured_image_idx": { + "name": "articles_featured_image_idx", + "columns": [ + { + "expression": "featured_image_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "articles_slug_idx": { + "name": "articles_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "articles_updated_at_idx": { + "name": "articles_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "articles_created_at_idx": { + "name": "articles_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "articles_featured_image_id_media_id_fk": { + "name": "articles_featured_image_id_media_id_fk", + "tableFrom": "articles", + "tableTo": "media", + "columnsFrom": [ + "featured_image_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "title_eng": { + "name": "title_eng", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "description_eng": { + "name": "description_eng", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "image_id": { + "name": "image_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "facebook_event_link": { + "name": "facebook_event_link", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "link_to_picture_from_event_id": { + "name": "link_to_picture_from_event_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "events_image_idx": { + "name": "events_image_idx", + "columns": [ + { + "expression": "image_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "events_link_to_picture_from_event_idx": { + "name": "events_link_to_picture_from_event_idx", + "columns": [ + { + "expression": "link_to_picture_from_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "events_slug_idx": { + "name": "events_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "events_updated_at_idx": { + "name": "events_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "events_created_at_idx": { + "name": "events_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "events_image_id_media_id_fk": { + "name": "events_image_id_media_id_fk", + "tableFrom": "events", + "tableTo": "media", + "columnsFrom": [ + "image_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "events_link_to_picture_from_event_id_gallery_id_fk": { + "name": "events_link_to_picture_from_event_id_gallery_id_fk", + "tableFrom": "events", + "tableTo": "gallery", + "columnsFrom": [ + "link_to_picture_from_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cars_gallery": { + "name": "cars_gallery", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "image_id": { + "name": "image_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "cars_gallery_order_idx": { + "name": "cars_gallery_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cars_gallery_parent_id_idx": { + "name": "cars_gallery_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cars_gallery_image_idx": { + "name": "cars_gallery_image_idx", + "columns": [ + { + "expression": "image_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cars_gallery_image_id_media_id_fk": { + "name": "cars_gallery_image_id_media_id_fk", + "tableFrom": "cars_gallery", + "tableTo": "media", + "columnsFrom": [ + "image_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cars_gallery_parent_id_fk": { + "name": "cars_gallery_parent_id_fk", + "tableFrom": "cars_gallery", + "tableTo": "cars", + "columnsFrom": [ + "_parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cars": { + "name": "cars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "description_eng": { + "name": "description_eng", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "image_id": { + "name": "image_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "interactive_model_id": { + "name": "interactive_model_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cars_image_idx": { + "name": "cars_image_idx", + "columns": [ + { + "expression": "image_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cars_interactive_model_idx": { + "name": "cars_interactive_model_idx", + "columns": [ + { + "expression": "interactive_model_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cars_updated_at_idx": { + "name": "cars_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cars_created_at_idx": { + "name": "cars_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cars_image_id_media_id_fk": { + "name": "cars_image_id_media_id_fk", + "tableFrom": "cars", + "tableTo": "media", + "columnsFrom": [ + "image_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cars_interactive_model_id_media_id_fk": { + "name": "cars_interactive_model_id_media_id_fk", + "tableFrom": "cars", + "tableTo": "media", + "columnsFrom": [ + "interactive_model_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gallery_images": { + "name": "gallery_images", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "image_id": { + "name": "image_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "gallery_images_order_idx": { + "name": "gallery_images_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "gallery_images_parent_id_idx": { + "name": "gallery_images_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "gallery_images_image_idx": { + "name": "gallery_images_image_idx", + "columns": [ + { + "expression": "image_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "gallery_images_image_id_media_id_fk": { + "name": "gallery_images_image_id_media_id_fk", + "tableFrom": "gallery_images", + "tableTo": "media", + "columnsFrom": [ + "image_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "gallery_images_parent_id_fk": { + "name": "gallery_images_parent_id_fk", + "tableFrom": "gallery_images", + "tableTo": "gallery", + "columnsFrom": [ + "_parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gallery": { + "name": "gallery", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "title_eng": { + "name": "title_eng", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "gallery_slug_idx": { + "name": "gallery_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "gallery_updated_at_idx": { + "name": "gallery_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "gallery_created_at_idx": { + "name": "gallery_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.publications": { + "name": "publications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "title_eng": { + "name": "title_eng", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "author": { + "name": "author", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "description_eng": { + "name": "description_eng", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "link": { + "name": "link", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "publications_updated_at_idx": { + "name": "publications_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "publications_created_at_idx": { + "name": "publications_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.members": { + "name": "members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "position_en": { + "name": "position_en", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "picture_id": { + "name": "picture_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "linkedin": { + "name": "linkedin", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "members_group_idx": { + "name": "members_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "members_picture_idx": { + "name": "members_picture_idx", + "columns": [ + { + "expression": "picture_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "members_updated_at_idx": { + "name": "members_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "members_created_at_idx": { + "name": "members_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "members_group_id_groups_id_fk": { + "name": "members_group_id_groups_id_fk", + "tableFrom": "members", + "tableTo": "groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "members_picture_id_media_id_fk": { + "name": "members_picture_id_media_id_fk", + "tableFrom": "members", + "tableTo": "media", + "columnsFrom": [ + "picture_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sponsors": { + "name": "sponsors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "logo_id": { + "name": "logo_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "website": { + "name": "website", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "enum_sponsors_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sponsors_logo_idx": { + "name": "sponsors_logo_idx", + "columns": [ + { + "expression": "logo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsors_updated_at_idx": { + "name": "sponsors_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsors_created_at_idx": { + "name": "sponsors_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sponsors_logo_id_media_id_fk": { + "name": "sponsors_logo_id_media_id_fk", + "tableFrom": "sponsors", + "tableTo": "media", + "columnsFrom": [ + "logo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recruitment_positions_sections": { + "name": "recruitment_positions_sections", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "section_title": { + "name": "section_title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "section_title_eng": { + "name": "section_title_eng", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "section_content": { + "name": "section_content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "section_content_eng": { + "name": "section_content_eng", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "recruitment_positions_sections_order_idx": { + "name": "recruitment_positions_sections_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recruitment_positions_sections_parent_id_idx": { + "name": "recruitment_positions_sections_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recruitment_positions_sections_parent_id_fk": { + "name": "recruitment_positions_sections_parent_id_fk", + "tableFrom": "recruitment_positions_sections", + "tableTo": "recruitment_positions", + "columnsFrom": [ + "_parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recruitment_positions": { + "name": "recruitment_positions", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "position_name": { + "name": "position_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "position_name_eng": { + "name": "position_name_eng", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "position_open": { + "name": "position_open", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "position_description": { + "name": "position_description", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "position_description_eng": { + "name": "position_description_eng", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "highlights_joining": { + "name": "highlights_joining", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "highlights_joining_eng": { + "name": "highlights_joining_eng", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "highlights_time_commitment": { + "name": "highlights_time_commitment", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "highlights_time_commitment_eng": { + "name": "highlights_time_commitment_eng", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "highlights_subsystem": { + "name": "highlights_subsystem", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "highlights_subsystem_eng": { + "name": "highlights_subsystem_eng", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "recruitment_positions_order_idx": { + "name": "recruitment_positions_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recruitment_positions_parent_id_idx": { + "name": "recruitment_positions_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recruitment_positions_parent_id_fk": { + "name": "recruitment_positions_parent_id_fk", + "tableFrom": "recruitment_positions", + "tableTo": "recruitment", + "columnsFrom": [ + "_parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recruitment": { + "name": "recruitment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "group_name": { + "name": "group_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "group_name_eng": { + "name": "group_name_eng", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "image_id": { + "name": "image_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "description_eng": { + "name": "description_eng", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "recruitment_image_idx": { + "name": "recruitment_image_idx", + "columns": [ + { + "expression": "image_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recruitment_updated_at_idx": { + "name": "recruitment_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recruitment_created_at_idx": { + "name": "recruitment_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recruitment_image_id_media_id_fk": { + "name": "recruitment_image_id_media_id_fk", + "tableFrom": "recruitment", + "tableTo": "media", + "columnsFrom": [ + "image_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_applications": { + "name": "job_applications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "phone": { + "name": "phone", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "university": { + "name": "university", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "major": { + "name": "major", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "semester": { + "name": "semester", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "position_name": { + "name": "position_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "motivation": { + "name": "motivation", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "consent": { + "name": "consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "sheet_sync_status": { + "name": "sheet_sync_status", + "type": "enum_job_applications_sheet_sync_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "sheet_sync_error": { + "name": "sheet_sync_error", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_applications_updated_at_idx": { + "name": "job_applications_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_applications_created_at_idx": { + "name": "job_applications_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.groups": { + "name": "groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name_en": { + "name": "name_en", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "description_eng": { + "name": "description_eng", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "groups_updated_at_idx": { + "name": "groups_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "groups_created_at_idx": { + "name": "groups_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.association": { + "name": "association", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "title_en": { + "name": "title_en", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "content_en": { + "name": "content_en", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "association_updated_at_idx": { + "name": "association_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "association_created_at_idx": { + "name": "association_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.support_us": { + "name": "support_us", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "title_en": { + "name": "title_en", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "button_text": { + "name": "button_text", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'Támogatom a csapatot'" + }, + "button_text_en": { + "name": "button_text_en", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'Support the team'" + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "content_en": { + "name": "content_en", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "support_us_updated_at_idx": { + "name": "support_us_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "support_us_created_at_idx": { + "name": "support_us_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contact": { + "name": "contact", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "title_en": { + "name": "title_en", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "content_en": { + "name": "content_en", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "contact_updated_at_idx": { + "name": "contact_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "contact_created_at_idx": { + "name": "contact_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.formula_student": { + "name": "formula_student", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "title_en": { + "name": "title_en", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "content_en": { + "name": "content_en", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "formula_student_updated_at_idx": { + "name": "formula_student_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "formula_student_created_at_idx": { + "name": "formula_student_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_kv": { + "name": "payload_kv", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "payload_kv_key_idx": { + "name": "payload_kv_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_locked_documents": { + "name": "payload_locked_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "global_slug": { + "name": "global_slug", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payload_locked_documents_global_slug_idx": { + "name": "payload_locked_documents_global_slug_idx", + "columns": [ + { + "expression": "global_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_updated_at_idx": { + "name": "payload_locked_documents_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_created_at_idx": { + "name": "payload_locked_documents_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_locked_documents_rels": { + "name": "payload_locked_documents_rels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "users_id": { + "name": "users_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "media_id": { + "name": "media_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "articles_id": { + "name": "articles_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "events_id": { + "name": "events_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cars_id": { + "name": "cars_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gallery_id": { + "name": "gallery_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "publications_id": { + "name": "publications_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "members_id": { + "name": "members_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sponsors_id": { + "name": "sponsors_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "recruitment_id": { + "name": "recruitment_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "job_applications_id": { + "name": "job_applications_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "groups_id": { + "name": "groups_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "association_id": { + "name": "association_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "support_us_id": { + "name": "support_us_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "contact_id": { + "name": "contact_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "formula_student_id": { + "name": "formula_student_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "payload_locked_documents_rels_order_idx": { + "name": "payload_locked_documents_rels_order_idx", + "columns": [ + { + "expression": "order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_parent_idx": { + "name": "payload_locked_documents_rels_parent_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_path_idx": { + "name": "payload_locked_documents_rels_path_idx", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_users_id_idx": { + "name": "payload_locked_documents_rels_users_id_idx", + "columns": [ + { + "expression": "users_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_media_id_idx": { + "name": "payload_locked_documents_rels_media_id_idx", + "columns": [ + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_articles_id_idx": { + "name": "payload_locked_documents_rels_articles_id_idx", + "columns": [ + { + "expression": "articles_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_events_id_idx": { + "name": "payload_locked_documents_rels_events_id_idx", + "columns": [ + { + "expression": "events_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_cars_id_idx": { + "name": "payload_locked_documents_rels_cars_id_idx", + "columns": [ + { + "expression": "cars_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_gallery_id_idx": { + "name": "payload_locked_documents_rels_gallery_id_idx", + "columns": [ + { + "expression": "gallery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_publications_id_idx": { + "name": "payload_locked_documents_rels_publications_id_idx", + "columns": [ + { + "expression": "publications_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_members_id_idx": { + "name": "payload_locked_documents_rels_members_id_idx", + "columns": [ + { + "expression": "members_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_sponsors_id_idx": { + "name": "payload_locked_documents_rels_sponsors_id_idx", + "columns": [ + { + "expression": "sponsors_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_recruitment_id_idx": { + "name": "payload_locked_documents_rels_recruitment_id_idx", + "columns": [ + { + "expression": "recruitment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_job_applications_id_idx": { + "name": "payload_locked_documents_rels_job_applications_id_idx", + "columns": [ + { + "expression": "job_applications_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_groups_id_idx": { + "name": "payload_locked_documents_rels_groups_id_idx", + "columns": [ + { + "expression": "groups_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_association_id_idx": { + "name": "payload_locked_documents_rels_association_id_idx", + "columns": [ + { + "expression": "association_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_support_us_id_idx": { + "name": "payload_locked_documents_rels_support_us_id_idx", + "columns": [ + { + "expression": "support_us_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_contact_id_idx": { + "name": "payload_locked_documents_rels_contact_id_idx", + "columns": [ + { + "expression": "contact_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_formula_student_id_idx": { + "name": "payload_locked_documents_rels_formula_student_id_idx", + "columns": [ + { + "expression": "formula_student_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "payload_locked_documents_rels_parent_fk": { + "name": "payload_locked_documents_rels_parent_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "payload_locked_documents", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_users_fk": { + "name": "payload_locked_documents_rels_users_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "users", + "columnsFrom": [ + "users_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_media_fk": { + "name": "payload_locked_documents_rels_media_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_articles_fk": { + "name": "payload_locked_documents_rels_articles_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "articles", + "columnsFrom": [ + "articles_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_events_fk": { + "name": "payload_locked_documents_rels_events_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "events", + "columnsFrom": [ + "events_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_cars_fk": { + "name": "payload_locked_documents_rels_cars_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "cars", + "columnsFrom": [ + "cars_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_gallery_fk": { + "name": "payload_locked_documents_rels_gallery_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "gallery", + "columnsFrom": [ + "gallery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_publications_fk": { + "name": "payload_locked_documents_rels_publications_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "publications", + "columnsFrom": [ + "publications_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_members_fk": { + "name": "payload_locked_documents_rels_members_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "members", + "columnsFrom": [ + "members_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_sponsors_fk": { + "name": "payload_locked_documents_rels_sponsors_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "sponsors", + "columnsFrom": [ + "sponsors_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_recruitment_fk": { + "name": "payload_locked_documents_rels_recruitment_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "recruitment", + "columnsFrom": [ + "recruitment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_job_applications_fk": { + "name": "payload_locked_documents_rels_job_applications_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "job_applications", + "columnsFrom": [ + "job_applications_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_groups_fk": { + "name": "payload_locked_documents_rels_groups_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "groups", + "columnsFrom": [ + "groups_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_association_fk": { + "name": "payload_locked_documents_rels_association_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "association", + "columnsFrom": [ + "association_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_support_us_fk": { + "name": "payload_locked_documents_rels_support_us_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "support_us", + "columnsFrom": [ + "support_us_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_contact_fk": { + "name": "payload_locked_documents_rels_contact_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "contact", + "columnsFrom": [ + "contact_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_formula_student_fk": { + "name": "payload_locked_documents_rels_formula_student_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "formula_student", + "columnsFrom": [ + "formula_student_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_preferences": { + "name": "payload_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payload_preferences_key_idx": { + "name": "payload_preferences_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_preferences_updated_at_idx": { + "name": "payload_preferences_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_preferences_created_at_idx": { + "name": "payload_preferences_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_preferences_rels": { + "name": "payload_preferences_rels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "users_id": { + "name": "users_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "payload_preferences_rels_order_idx": { + "name": "payload_preferences_rels_order_idx", + "columns": [ + { + "expression": "order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_preferences_rels_parent_idx": { + "name": "payload_preferences_rels_parent_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_preferences_rels_path_idx": { + "name": "payload_preferences_rels_path_idx", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_preferences_rels_users_id_idx": { + "name": "payload_preferences_rels_users_id_idx", + "columns": [ + { + "expression": "users_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "payload_preferences_rels_parent_fk": { + "name": "payload_preferences_rels_parent_fk", + "tableFrom": "payload_preferences_rels", + "tableTo": "payload_preferences", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_preferences_rels_users_fk": { + "name": "payload_preferences_rels_users_fk", + "tableFrom": "payload_preferences_rels", + "tableTo": "users", + "columnsFrom": [ + "users_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_migrations": { + "name": "payload_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "batch": { + "name": "batch", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payload_migrations_updated_at_idx": { + "name": "payload_migrations_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_migrations_created_at_idx": { + "name": "payload_migrations_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.site_settings": { + "name": "site_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "show_association_page": { + "name": "show_association_page", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "show_recruitment_page": { + "name": "show_recruitment_page", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.career_settings": { + "name": "career_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "intro": { + "name": "intro", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "intro_eng": { + "name": "intro_eng", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "application_mode": { + "name": "application_mode", + "type": "enum_career_settings_application_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'builtIn'" + }, + "google_form_url": { + "name": "google_form_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "applications_open": { + "name": "applications_open", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "applications_closed_text": { + "name": "applications_closed_text", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "applications_closed_text_eng": { + "name": "applications_closed_text_eng", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.enum_sponsors_tier": { + "name": "enum_sponsors_tier", + "schema": "public", + "values": [ + "diamond", + "gold", + "silver", + "copper", + "other", + "bme" + ] + }, + "public.enum_job_applications_sheet_sync_status": { + "name": "enum_job_applications_sheet_sync_status", + "schema": "public", + "values": [ + "ok", + "skipped", + "error" + ] + }, + "public.enum_career_settings_application_mode": { + "name": "enum_career_settings_application_mode", + "schema": "public", + "values": [ + "builtIn", + "googleForm" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "id": "cbfb0793-cc68-42b5-b33f-4289d9500eb8", + "prevId": "00000000-0000-0000-0000-000000000000" +} \ No newline at end of file diff --git a/src/migrations/20260729_182451_karrier_oldal.ts b/src/migrations/20260729_182451_karrier_oldal.ts new file mode 100644 index 0000000..72d28f0 --- /dev/null +++ b/src/migrations/20260729_182451_karrier_oldal.ts @@ -0,0 +1,101 @@ +import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres' + +export async function up({ db }: MigrateUpArgs): Promise { + await db.execute(sql` + CREATE TYPE "public"."enum_job_applications_sheet_sync_status" AS ENUM('ok', 'skipped', 'error'); + CREATE TYPE "public"."enum_career_settings_application_mode" AS ENUM('builtIn', 'googleForm'); + CREATE TABLE "recruitment_positions_sections" ( + "_order" integer NOT NULL, + "_parent_id" varchar NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "section_title" varchar NOT NULL, + "section_title_eng" varchar NOT NULL, + "section_content" jsonb NOT NULL, + "section_content_eng" jsonb NOT NULL + ); + + CREATE TABLE "job_applications" ( + "id" serial PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "email" varchar NOT NULL, + "phone" varchar, + "university" varchar, + "major" varchar, + "semester" varchar, + "group_name" varchar, + "position_name" varchar, + "motivation" varchar, + "consent" boolean, + "sheet_sync_status" "enum_job_applications_sheet_sync_status", + "sheet_sync_error" varchar, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL + ); + + CREATE TABLE "career_settings" ( + "id" serial PRIMARY KEY NOT NULL, + "intro" jsonb, + "intro_eng" jsonb, + "application_mode" "enum_career_settings_application_mode" DEFAULT 'builtIn', + "google_form_url" varchar, + "applications_open" boolean DEFAULT true, + "applications_closed_text" varchar, + "applications_closed_text_eng" varchar, + "updated_at" timestamp(3) with time zone, + "created_at" timestamp(3) with time zone + ); + + ALTER TABLE "recruitment_positions" ALTER COLUMN "position_description" DROP NOT NULL; + ALTER TABLE "recruitment_positions" ALTER COLUMN "position_description_eng" DROP NOT NULL; + ALTER TABLE "recruitment_positions" ALTER COLUMN "position_open" DROP DEFAULT; + ALTER TABLE "recruitment_positions" ALTER COLUMN "position_open" DROP NOT NULL; + ALTER TABLE "recruitment_positions" ADD COLUMN "highlights_joining" varchar; + ALTER TABLE "recruitment_positions" ADD COLUMN "highlights_joining_eng" varchar; + ALTER TABLE "recruitment_positions" ADD COLUMN "highlights_time_commitment" varchar; + ALTER TABLE "recruitment_positions" ADD COLUMN "highlights_time_commitment_eng" varchar; + ALTER TABLE "recruitment_positions" ADD COLUMN "highlights_subsystem" varchar; + ALTER TABLE "recruitment_positions" ADD COLUMN "highlights_subsystem_eng" varchar; + ALTER TABLE "recruitment" ADD COLUMN "image_id" integer; + ALTER TABLE "recruitment" ADD COLUMN "order" numeric; + ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "job_applications_id" integer; + ALTER TABLE "recruitment_positions_sections" ADD CONSTRAINT "recruitment_positions_sections_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."recruitment_positions"("id") ON DELETE cascade ON UPDATE no action; + CREATE INDEX "recruitment_positions_sections_order_idx" ON "recruitment_positions_sections" USING btree ("_order"); + CREATE INDEX "recruitment_positions_sections_parent_id_idx" ON "recruitment_positions_sections" USING btree ("_parent_id"); + CREATE INDEX "job_applications_updated_at_idx" ON "job_applications" USING btree ("updated_at"); + CREATE INDEX "job_applications_created_at_idx" ON "job_applications" USING btree ("created_at"); + ALTER TABLE "recruitment" ADD CONSTRAINT "recruitment_image_id_media_id_fk" FOREIGN KEY ("image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action; + ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_job_applications_fk" FOREIGN KEY ("job_applications_id") REFERENCES "public"."job_applications"("id") ON DELETE cascade ON UPDATE no action; + CREATE INDEX "recruitment_image_idx" ON "recruitment" USING btree ("image_id"); + CREATE INDEX "payload_locked_documents_rels_job_applications_id_idx" ON "payload_locked_documents_rels" USING btree ("job_applications_id");`) +} + +export async function down({ db }: MigrateDownArgs): Promise { + await db.execute(sql` + ALTER TABLE "recruitment_positions_sections" DISABLE ROW LEVEL SECURITY; + ALTER TABLE "job_applications" DISABLE ROW LEVEL SECURITY; + ALTER TABLE "career_settings" DISABLE ROW LEVEL SECURITY; + DROP TABLE "recruitment_positions_sections" CASCADE; + DROP TABLE "job_applications" CASCADE; + DROP TABLE "career_settings" CASCADE; + ALTER TABLE "recruitment" DROP CONSTRAINT "recruitment_image_id_media_id_fk"; + + ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_job_applications_fk"; + + DROP INDEX "recruitment_image_idx"; + DROP INDEX "payload_locked_documents_rels_job_applications_id_idx"; + ALTER TABLE "recruitment_positions" ALTER COLUMN "position_open" SET DEFAULT false; + ALTER TABLE "recruitment_positions" ALTER COLUMN "position_open" SET NOT NULL; + ALTER TABLE "recruitment_positions" ALTER COLUMN "position_description" SET NOT NULL; + ALTER TABLE "recruitment_positions" ALTER COLUMN "position_description_eng" SET NOT NULL; + ALTER TABLE "recruitment_positions" DROP COLUMN "highlights_joining"; + ALTER TABLE "recruitment_positions" DROP COLUMN "highlights_joining_eng"; + ALTER TABLE "recruitment_positions" DROP COLUMN "highlights_time_commitment"; + ALTER TABLE "recruitment_positions" DROP COLUMN "highlights_time_commitment_eng"; + ALTER TABLE "recruitment_positions" DROP COLUMN "highlights_subsystem"; + ALTER TABLE "recruitment_positions" DROP COLUMN "highlights_subsystem_eng"; + ALTER TABLE "recruitment" DROP COLUMN "image_id"; + ALTER TABLE "recruitment" DROP COLUMN "order"; + ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "job_applications_id"; + DROP TYPE "public"."enum_job_applications_sheet_sync_status"; + DROP TYPE "public"."enum_career_settings_application_mode";`) +} diff --git a/src/migrations/index.ts b/src/migrations/index.ts index 5e5aab6..a1480a5 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -6,6 +6,7 @@ import * as migration_20251124_120000_fix_locked_documents_relation from './2025 import * as migration_20251130_000000_init_site_settings from './20251130_000000_init_site_settings'; import * as migration_20251201_090036_publications_optional_link from './20251201_090036_publications_optional_link'; import * as migration_20251206_100136_update_payload_3_67 from './20251206_100136_update_payload_3_67'; +import * as migration_20260729_182451_karrier_oldal from './20260729_182451_karrier_oldal'; export const migrations = [ { @@ -46,6 +47,11 @@ export const migrations = [ { up: migration_20251206_100136_update_payload_3_67.up, down: migration_20251206_100136_update_payload_3_67.down, - name: '20251206_100136_update_payload_3_67' + name: '20251206_100136_update_payload_3_67', + }, + { + up: migration_20260729_182451_karrier_oldal.up, + down: migration_20260729_182451_karrier_oldal.down, + name: '20260729_182451_karrier_oldal' }, ]; diff --git a/src/payload-types.ts b/src/payload-types.ts index 547066a..73b2d91 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -77,11 +77,13 @@ export interface Config { members: Member; sponsors: Sponsor; recruitment: Recruitment; + 'job-applications': JobApplication; groups: Group; association: Association; 'support-us': SupportUs; contact: Contact; 'formula-student': FormulaStudent; + 'payload-kv': PayloadKv; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; 'payload-migrations': PayloadMigration; @@ -98,11 +100,13 @@ export interface Config { members: MembersSelect | MembersSelect; sponsors: SponsorsSelect | SponsorsSelect; recruitment: RecruitmentSelect | RecruitmentSelect; + 'job-applications': JobApplicationsSelect | JobApplicationsSelect; groups: GroupsSelect | GroupsSelect; association: AssociationSelect | AssociationSelect; 'support-us': SupportUsSelect | SupportUsSelect; contact: ContactSelect | ContactSelect; 'formula-student': FormulaStudentSelect | FormulaStudentSelect; + 'payload-kv': PayloadKvSelect | PayloadKvSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; @@ -110,16 +114,20 @@ export interface Config { db: { defaultIDType: number; }; + fallbackLocale: null; globals: { 'site-settings': SiteSetting; + 'career-settings': CareerSetting; }; globalsSelect: { 'site-settings': SiteSettingsSelect | SiteSettingsSelect; + 'career-settings': CareerSettingsSelect | CareerSettingsSelect; }; locale: null; - user: User & { - collection: 'users'; + widgets: { + collections: CollectionsWidget; }; + user: User; jobs: { tasks: unknown; workflows: unknown; @@ -160,7 +168,15 @@ export interface User { hash?: string | null; loginAttempts?: number | null; lockUntil?: string | null; + sessions?: + | { + id: string; + createdAt?: string | null; + expiresAt: string; + }[] + | null; password?: string | null; + collection: 'users'; } /** * This interface was referenced by `Config`'s JSON-Schema @@ -195,7 +211,7 @@ export interface Article { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -210,7 +226,7 @@ export interface Article { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -225,7 +241,7 @@ export interface Article { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -240,7 +256,7 @@ export interface Article { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -282,7 +298,7 @@ export interface Event { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -297,7 +313,7 @@ export interface Event { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -361,7 +377,7 @@ export interface Car { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -376,7 +392,7 @@ export interface Car { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -411,7 +427,7 @@ export interface Publication { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -426,7 +442,7 @@ export interface Publication { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -437,7 +453,7 @@ export interface Publication { }; [k: string]: unknown; }; - link: string; + link?: string | null; updatedAt: string; createdAt: string; } @@ -473,7 +489,7 @@ export interface Group { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -488,7 +504,7 @@ export interface Group { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -519,7 +535,7 @@ export interface Sponsor { createdAt: string; } /** - * Nyitott pozíciók a csoportokon belül, funkcionális területek szerint csoportosítva. + * A Karrier oldalon megjelenő csoportok és a hozzájuk tartozó pozíciók. * * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "recruitment". @@ -528,11 +544,19 @@ export interface Recruitment { id: number; groupName: string; groupNameEng: string; + /** + * A csoport sávjának bal oldalán jelenik meg. Fekvő (kb. 4:3) kép ajánlott. + */ + image?: (number | null) | Media; + /** + * A csoportok növekvő sorrendben jelennek meg. Üresen hagyva a lista végére kerül. + */ + order?: number | null; description: { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -547,7 +571,7 @@ export interface Recruitment { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -561,11 +585,18 @@ export interface Recruitment { positions: { positionName: string; positionNameEng: string; - positionDescription: { + /** + * Csak a bepipált pozíciók jelennek meg a Karrier oldalon. + */ + positionOpen?: boolean | null; + /** + * Rövid felvezető szöveg, a szekciók fölött jelenik meg. Opcionális. + */ + positionDescription?: { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -575,12 +606,12 @@ export interface Recruitment { version: number; }; [k: string]: unknown; - }; - positionDescriptionEng: { + } | null; + positionDescriptionEng?: { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -590,13 +621,95 @@ export interface Recruitment { version: number; }; [k: string]: unknown; + } | null; + /** + * A pozíció leírása szekciókra bontva, pl. Feladatok, Szükséges skillek, Miben fejlődhetsz, Előnyök. + */ + sections?: + | { + sectionTitle: string; + sectionTitleEng: string; + sectionContent: { + root: { + type: string; + children: { + type: any; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + }; + sectionContentEng: { + root: { + type: string; + children: { + type: any; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + }; + id?: string | null; + }[] + | null; + /** + * A pozíció leírása alatt, ikonokkal kiemelve jelennek meg. Az üresen hagyott mezők nem jelennek meg. + */ + highlights?: { + /** + * Pl. „2. félévtől, gépészmérnök hallgatóknak” vagy „Bármely szakos hallgatónak”. + */ + joining?: string | null; + joiningEng?: string | null; + /** + * Pl. „heti 10-15 óra”. + */ + timeCommitment?: string | null; + timeCommitmentEng?: string | null; + /** + * Pl. „Futómű”, „Akkumulátor”, „Aerodinamika”. + */ + subsystem?: string | null; + subsystemEng?: string | null; }; - positionOpen: boolean; id?: string | null; }[]; updatedAt: string; createdAt: string; } +/** + * A Karrier oldalon beérkezett jelentkezések. + * + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "job-applications". + */ +export interface JobApplication { + id: number; + name: string; + email: string; + phone?: string | null; + university?: string | null; + major?: string | null; + semester?: string | null; + groupName?: string | null; + positionName?: string | null; + motivation?: string | null; + consent?: boolean | null; + sheetSyncStatus?: ('ok' | 'skipped' | 'error') | null; + sheetSyncError?: string | null; + updatedAt: string; + createdAt: string; +} /** * Az egyesület szerkeszthető tartalmai * @@ -611,7 +724,7 @@ export interface Association { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -626,7 +739,7 @@ export interface Association { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -656,7 +769,7 @@ export interface SupportUs { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -671,7 +784,7 @@ export interface SupportUs { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -699,7 +812,7 @@ export interface Contact { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -714,7 +827,7 @@ export interface Contact { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -742,7 +855,7 @@ export interface FormulaStudent { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -757,7 +870,7 @@ export interface FormulaStudent { root: { type: string; children: { - type: string; + type: any; version: number; [k: string]: unknown; }[]; @@ -771,6 +884,23 @@ export interface FormulaStudent { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-kv". + */ +export interface PayloadKv { + id: number; + key: string; + data: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents". @@ -818,6 +948,10 @@ export interface PayloadLockedDocument { relationTo: 'recruitment'; value: number | Recruitment; } | null) + | ({ + relationTo: 'job-applications'; + value: number | JobApplication; + } | null) | ({ relationTo: 'groups'; value: number | Group; @@ -894,6 +1028,13 @@ export interface UsersSelect { hash?: T; loginAttempts?: T; lockUntil?: T; + sessions?: + | T + | { + id?: T; + createdAt?: T; + expiresAt?: T; + }; } /** * This interface was referenced by `Config`'s JSON-Schema @@ -1044,6 +1185,8 @@ export interface SponsorsSelect { export interface RecruitmentSelect { groupName?: T; groupNameEng?: T; + image?: T; + order?: T; description?: T; descriptionEng?: T; positions?: @@ -1051,14 +1194,53 @@ export interface RecruitmentSelect { | { positionName?: T; positionNameEng?: T; + positionOpen?: T; positionDescription?: T; positionDescriptionEng?: T; - positionOpen?: T; + sections?: + | T + | { + sectionTitle?: T; + sectionTitleEng?: T; + sectionContent?: T; + sectionContentEng?: T; + id?: T; + }; + highlights?: + | T + | { + joining?: T; + joiningEng?: T; + timeCommitment?: T; + timeCommitmentEng?: T; + subsystem?: T; + subsystemEng?: T; + }; id?: T; }; updatedAt?: T; createdAt?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "job-applications_select". + */ +export interface JobApplicationsSelect { + name?: T; + email?: T; + phone?: T; + university?: T; + major?: T; + semester?: T; + groupName?: T; + positionName?: T; + motivation?: T; + consent?: T; + sheetSyncStatus?: T; + sheetSyncError?: T; + updatedAt?: T; + createdAt?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "groups_select". @@ -1122,6 +1304,14 @@ export interface FormulaStudentSelect { updatedAt?: T; createdAt?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-kv_select". + */ +export interface PayloadKvSelect { + key?: T; + data?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents_select". @@ -1165,12 +1355,70 @@ export interface SiteSetting { */ showAssociationPage?: boolean | null; /** - * Controls visibility of the Recruitment page in navigation and direct access + * Controls visibility of the Careers page in navigation and direct access */ showRecruitmentPage?: boolean | null; updatedAt?: string | null; createdAt?: string | null; } +/** + * A Karrier oldal bevezetője és a jelentkezés módjának beállítása. + * + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "career-settings". + */ +export interface CareerSetting { + id: number; + /** + * Az oldal tetején, a csoportok listája fölött jelenik meg. + */ + intro?: { + root: { + type: string; + children: { + type: any; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + } | null; + introEng?: { + root: { + type: string; + children: { + type: any; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + } | null; + /** + * Beépített űrlap esetén a jelentkezések a Payload adminba érkeznek (és a Google táblázatba, ha be van állítva a webhook). + */ + applicationMode?: ('builtIn' | 'googleForm') | null; + /** + * Csak a „Külső Google űrlap” mód esetén használjuk. + */ + googleFormUrl?: string | null; + /** + * Kikapcsolva az oldal továbbra is elérhető, de a jelentkezési űrlap és a „Jelentkezz” gomb helyett egy tájékoztató szöveg jelenik meg. + */ + applicationsOpen?: boolean | null; + applicationsClosedText?: string | null; + applicationsClosedTextEng?: string | null; + updatedAt?: string | null; + createdAt?: string | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "site-settings_select". @@ -1182,6 +1430,32 @@ export interface SiteSettingsSelect { createdAt?: T; globalType?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "career-settings_select". + */ +export interface CareerSettingsSelect { + intro?: T; + introEng?: T; + applicationMode?: T; + googleFormUrl?: T; + applicationsOpen?: T; + applicationsClosedText?: T; + applicationsClosedTextEng?: T; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "collections_widget". + */ +export interface CollectionsWidget { + data?: { + [k: string]: unknown; + }; + width: 'full'; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "auth". diff --git a/src/payload.config.ts b/src/payload.config.ts index cdfc618..8ff2662 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -12,11 +12,13 @@ import { fileURLToPath } from "url"; import { Articles } from "./collections/Articles"; import Association from "./collections/Association"; import { Cars } from "./collections/Cars"; +import { CareerSettings } from "./collections/CareerSettings"; import Contact from "./collections/Contact"; import { Events } from "./collections/Events"; import FormulaStudent from "./collections/FormulaStudent"; import { Gallery } from "./collections/Gallery"; import Groups from "./collections/Groups"; +import { JobApplications } from "./collections/JobApplications"; import { Media } from "./collections/Media"; import { Members } from "./collections/Members"; import { Publications } from "./collections/Publications"; @@ -40,6 +42,7 @@ const collections = [ Members, Sponsors, Recruitment, + JobApplications, Groups, Association, SupportUs, @@ -60,7 +63,7 @@ export default buildConfig({ }, }, collections: collections, - globals: [SiteSettings], + globals: [SiteSettings, CareerSettings], editor: lexicalEditor(), secret: process.env.PAYLOAD_SECRET || "", typescript: { From 72cf6d77a8be141da33a2feb1547555de8199ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lipt=C3=A1k=20P=C3=A9ter?= Date: Thu, 30 Jul 2026 22:23:35 +0200 Subject: [PATCH 02/23] feat: add public endpoint for career page applications --- .env.example | 5 ++ src/app/api/karrier/jelentkezes/route.ts | 59 +++++++++++++++++++ src/lib/karrier-application.ts | 72 ++++++++++++++++++++++++ src/lib/rate-limit.ts | 30 ++++++++++ 4 files changed, 166 insertions(+) create mode 100644 src/app/api/karrier/jelentkezes/route.ts create mode 100644 src/lib/karrier-application.ts create mode 100644 src/lib/rate-limit.ts diff --git a/.env.example b/.env.example index 01aef04..7593857 100644 --- a/.env.example +++ b/.env.example @@ -7,3 +7,8 @@ POSTGRES_DB=frt FACEBOOK_PAGE_ID=your_page_id FACEBOOK_ACCESS_TOKEN=your_access_token + +# Karrier oldal — jelentkezések kiírása Google Sheets táblázatba (opcionális). +# Beállítás nélkül a jelentkezések csak a Payload adminba kerülnek. +GOOGLE_SHEETS_WEBHOOK_URL=https://script.google.com/macros/s/XXXX/exec +GOOGLE_SHEETS_WEBHOOK_SECRET=valami_hosszu_veletlen_titok diff --git a/src/app/api/karrier/jelentkezes/route.ts b/src/app/api/karrier/jelentkezes/route.ts new file mode 100644 index 0000000..ee58d39 --- /dev/null +++ b/src/app/api/karrier/jelentkezes/route.ts @@ -0,0 +1,59 @@ +import { parseApplicationInput } from "@/lib/karrier-application"; +import { clientIp, createRateLimiter } from "@/lib/rate-limit"; +import config from "@payload-config"; +import { NextResponse } from "next/server"; +import { getPayload } from "payload"; + +/** + * A Karrier oldal beépített jelentkezési űrlapjának végpontja. + * + * A job-applications kollekció create jogosultsága zárt, ezért a beszúrás + * innen, overrideAccess-szel történik — így a nyilvános Payload REST végponton + * nem lehet közvetlenül jelentkezést létrehozni. + */ + +const isRateLimited = createRateLimiter({ windowMs: 60_000, max: 5 }); + +const errorResponse = (error: string, status: number) => NextResponse.json({ error }, { status }); + +export async function POST(request: Request) { + if (isRateLimited(clientIp(request))) { + return errorResponse("TOO_MANY_REQUESTS", 429); + } + + let body: Record; + try { + body = await request.json(); + } catch { + return errorResponse("INVALID_BODY", 400); + } + + const parsed = parseApplicationInput(body); + + if (!parsed.ok) { + // A botnak sikeres választ adunk, hogy ne tudja kitalálni a csapdát. + return parsed.error === "HONEYPOT" + ? NextResponse.json({ ok: true }) + : errorResponse(parsed.error, 400); + } + + try { + const payload = await getPayload({ config }); + + const settings = await payload.findGlobal({ slug: "career-settings" }); + if (settings?.applicationsOpen === false) { + return errorResponse("APPLICATIONS_CLOSED", 403); + } + + await payload.create({ + collection: "job-applications", + overrideAccess: true, + data: parsed.data, + }); + + return NextResponse.json({ ok: true }); + } catch (error) { + console.error("Jelentkezés mentése sikertelen:", error); + return errorResponse("SERVER_ERROR", 500); + } +} diff --git a/src/lib/karrier-application.ts b/src/lib/karrier-application.ts new file mode 100644 index 0000000..475e8b2 --- /dev/null +++ b/src/lib/karrier-application.ts @@ -0,0 +1,72 @@ +/** + * A Karrier oldal jelentkezési űrlapjának szerveroldali validálása. + * A nyers kérés-törzsből vagy egy hibakódot, vagy egy tisztított, menthető + * adathalmazt ad vissza. + */ + +const MAX_LENGTHS = { + name: 120, + email: 200, + phone: 40, + university: 120, + major: 120, + semester: 40, + groupName: 200, + positionName: 200, + motivation: 5000, +} as const + +type Field = keyof typeof MAX_LENGTHS + +const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + +export type ApplicationInput = { + [K in Field]: K extends "name" | "email" ? string : string | null +} & { consent: true } + +export type ParseResult = + | { ok: true; data: ApplicationInput } + | { ok: false; error: "MISSING_FIELDS" | "INVALID_EMAIL" } + /** A rejtett mező kitöltve: bot. Sikeres választ adunk, de nem mentünk. */ + | { ok: false; error: "HONEYPOT" } + +function readString(value: unknown, field: Field): string | null { + if (typeof value !== "string") return null + const trimmed = value.trim() + if (!trimmed) return null + return trimmed.slice(0, MAX_LENGTHS[field]) +} + +export function parseApplicationInput(body: Record): ParseResult { + // Rejtett mező: valódi látogató sosem tölti ki, a botok viszont igen. + if (typeof body.website === "string" && body.website.trim() !== "") { + return { ok: false, error: "HONEYPOT" } + } + + const name = readString(body.name, "name") + const email = readString(body.email, "email") + + if (!name || !email || body.consent !== true) { + return { ok: false, error: "MISSING_FIELDS" } + } + + if (!EMAIL_PATTERN.test(email)) { + return { ok: false, error: "INVALID_EMAIL" } + } + + return { + ok: true, + data: { + name, + email, + phone: readString(body.phone, "phone"), + university: readString(body.university, "university"), + major: readString(body.major, "major"), + semester: readString(body.semester, "semester"), + groupName: readString(body.groupName, "groupName"), + positionName: readString(body.positionName, "positionName"), + motivation: readString(body.motivation, "motivation"), + consent: true, + }, + } +} diff --git a/src/lib/rate-limit.ts b/src/lib/rate-limit.ts new file mode 100644 index 0000000..e92577a --- /dev/null +++ b/src/lib/rate-limit.ts @@ -0,0 +1,30 @@ +/** + * Egyszerű, memóriában tartott kérés-korlátozó. Egy futó példányon belül véd a + * tömeges beküldés ellen; több példány esetén példányonként számol. + */ +export function createRateLimiter({ windowMs, max }: { windowMs: number; max: number }) { + const hits = new Map() + + return function isRateLimited(key: string): boolean { + const now = Date.now() + const recent = (hits.get(key) ?? []).filter((timestamp) => now - timestamp < windowMs) + + if (recent.length >= max) { + hits.set(key, recent) + return true + } + + recent.push(now) + hits.set(key, recent) + return false + } +} + +/** A kérést küldő kliens azonosítása a proxy fejlécei alapján. */ +export function clientIp(request: Request): string { + return ( + request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? + request.headers.get("x-real-ip") ?? + "unknown" + ) +} From 5a26cce47f9a95c3072728c56c8591fb7d8e4376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lipt=C3=A1k=20P=C3=A9ter?= Date: Thu, 30 Jul 2026 22:23:46 +0200 Subject: [PATCH 03/23] feat: add career page with collapsible groups and positions --- src/app/(app)/globals.css | 32 +++ src/app/(app)/karrier/career-config.ts | 22 ++ src/app/(app)/karrier/career-page-client.tsx | 102 +++++++++ .../karrier/components/application-form.tsx | 194 ++++++++++++++++++ .../(app)/karrier/components/apply-button.tsx | 41 ++++ .../(app)/karrier/components/career-group.tsx | 123 +++++++++++ .../karrier/components/career-position.tsx | 75 +++++++ .../karrier/components/career-sidebar.tsx | 56 +++++ .../(app)/karrier/components/form-field.tsx | 127 ++++++++++++ .../components/position-highlights.tsx | 51 +++++ .../karrier/components/position-sections.tsx | 32 +++ src/app/(app)/karrier/error.tsx | 32 +++ src/app/(app)/karrier/loading.tsx | 27 +++ src/app/(app)/karrier/page.tsx | 32 +++ src/app/(app)/karrier/translations.ts | 84 ++++++++ src/app/(app)/karrier/types.ts | 19 ++ src/app/(app)/karrier/use-career-page.ts | 72 +++++++ 17 files changed, 1121 insertions(+) create mode 100644 src/app/(app)/karrier/career-config.ts create mode 100644 src/app/(app)/karrier/career-page-client.tsx create mode 100644 src/app/(app)/karrier/components/application-form.tsx create mode 100644 src/app/(app)/karrier/components/apply-button.tsx create mode 100644 src/app/(app)/karrier/components/career-group.tsx create mode 100644 src/app/(app)/karrier/components/career-position.tsx create mode 100644 src/app/(app)/karrier/components/career-sidebar.tsx create mode 100644 src/app/(app)/karrier/components/form-field.tsx create mode 100644 src/app/(app)/karrier/components/position-highlights.tsx create mode 100644 src/app/(app)/karrier/components/position-sections.tsx create mode 100644 src/app/(app)/karrier/error.tsx create mode 100644 src/app/(app)/karrier/loading.tsx create mode 100644 src/app/(app)/karrier/page.tsx create mode 100644 src/app/(app)/karrier/translations.ts create mode 100644 src/app/(app)/karrier/types.ts create mode 100644 src/app/(app)/karrier/use-career-page.ts diff --git a/src/app/(app)/globals.css b/src/app/(app)/globals.css index a478443..235fea8 100644 --- a/src/app/(app)/globals.css +++ b/src/app/(app)/globals.css @@ -143,6 +143,38 @@ margin-bottom: 0.5em; /* Kis térköz a listaelemek között */ } +/* Karrier oldal — pozíció szekcióinak felsorolása. + A szerkesztő a CMS-ben sima felsorolást ír, a sor elején emojival: az emoji a + felsorolásjel, ezért a pöttyöt elhagyjuk, és függő behúzással igazítjuk a + tördelt sorokat a szöveg alá. */ +.karrier-section-list ul, +.karrier-section-list ol { + list-style: none; + margin-left: 0; + padding-left: 0; +} + +.karrier-section-list li { + margin-bottom: 0.5em; + padding-left: 1.6em; + text-indent: -1.6em; + /* A sor elejére írt emoji után szűk lenne a sima szóköz — épp csak megnyitjuk. */ + word-spacing: 0.12em; +} + +.karrier-section-list li:last-child { + margin-bottom: 0; +} + +/* Ha a szerkesztő felsorolás helyett bekezdéseket ír, azok se lógjanak ki. */ +.karrier-section-list p { + margin-bottom: 0.5em; +} + +.karrier-section-list p:last-child { + margin-bottom: 0; +} + /* Egyéb formázások, ha szükséges (pl. linkek, címsorok, stb.) */ .rich-text-content a { color: theme('colors.frtRed'); diff --git a/src/app/(app)/karrier/career-config.ts b/src/app/(app)/karrier/career-config.ts new file mode 100644 index 0000000..0245bb3 --- /dev/null +++ b/src/app/(app)/karrier/career-config.ts @@ -0,0 +1,22 @@ +import type { CareerSetting } from "@/payload-types" +import type { ApplicationConfig } from "./types" + +/** + * A "Karrier oldal" globális beállításból egyetlen, a nézetben közvetlenül + * használható objektumot állít elő, hogy a komponensekben ne kelljen újra és + * újra a nullable CMS mezőkkel bajlódni. + */ +export function resolveApplicationConfig( + settings: CareerSetting | null, + isEn: boolean, + closedTextFallback: string, +): ApplicationConfig { + const closedText = isEn ? settings?.applicationsClosedTextEng : settings?.applicationsClosedText + + return { + isOpen: settings?.applicationsOpen !== false, + useBuiltInForm: (settings?.applicationMode ?? "builtIn") === "builtIn", + googleFormUrl: settings?.googleFormUrl?.trim() || null, + closedText: closedText?.trim() || closedTextFallback, + } +} diff --git a/src/app/(app)/karrier/career-page-client.tsx b/src/app/(app)/karrier/career-page-client.tsx new file mode 100644 index 0000000..2e9fb39 --- /dev/null +++ b/src/app/(app)/karrier/career-page-client.tsx @@ -0,0 +1,102 @@ +"use client" + +import type { CareerSetting, Recruitment } from "@/payload-types" +import { RichText } from "@payloadcms/richtext-lexical/react" +import { resolveApplicationConfig } from "./career-config" +import ApplicationForm from "./components/application-form" +import ApplyButton from "./components/apply-button" +import CareerGroup from "./components/career-group" +import CareerSidebar from "./components/career-sidebar" +import { careerTexts, isEnglish } from "./translations" +import { useCareerPage } from "./use-career-page" + +interface CareerPageClientProps { + recruitmentData: Recruitment[] + careerSettings: CareerSetting | null + lang: string +} + +export default function CareerPageClient({ recruitmentData, careerSettings, lang }: CareerPageClientProps) { + const isEn = isEnglish(lang) + const texts = careerTexts(isEn) + const application = resolveApplicationConfig(careerSettings, isEn, texts.applicationsClosed) + const intro = isEn ? careerSettings?.introEng : careerSettings?.intro + + const { + formRef, + openGroupIds, + openPositionKeys, + selectedPosition, + toggleGroup, + togglePosition, + openGroupAndScroll, + applyForPosition, + scrollToForm, + } = useCareerPage() + + return ( +
+
+
+

{texts.title}

+ + {intro ? ( +
+ +
+ ) : ( +

{texts.subtitle}

+ )} + +
+ {recruitmentData.map((group) => ( + toggleGroup(group.id)} + onTogglePosition={togglePosition} + onApply={applyForPosition} + /> + ))} +
+ +
+ {!application.isOpen ? ( +
+

{application.closedText}

+
+ ) : application.useBuiltInForm ? ( + + ) : ( + application.googleFormUrl && ( +
+

{texts.interested}

+ +
+ ) + )} +
+
+ + +
+
+ ) +} diff --git a/src/app/(app)/karrier/components/application-form.tsx b/src/app/(app)/karrier/components/application-form.tsx new file mode 100644 index 0000000..8f2ad3e --- /dev/null +++ b/src/app/(app)/karrier/components/application-form.tsx @@ -0,0 +1,194 @@ +"use client" + +import { AlertCircle, CheckCircle2, Loader2 } from "lucide-react" +import { useEffect, useState } from "react" +import { applicationErrorText, applicationFormTexts } from "../translations" +import type { SelectedPosition } from "../types" +import { CheckboxField, HoneypotField, TextAreaField, TextField } from "./form-field" + +interface ApplicationFormProps { + lang: string + isEn: boolean + selectedPosition: SelectedPosition | null +} + +type Status = "idle" | "submitting" | "success" | "error" + +const EMPTY_FORM = { + name: "", + email: "", + phone: "", + university: "", + major: "", + semester: "", + groupName: "", + positionName: "", + motivation: "", + consent: false, + website: "", +} + +type FormState = typeof EMPTY_FORM + +export default function ApplicationForm({ lang, isEn, selectedPosition }: ApplicationFormProps) { + const [form, setForm] = useState(EMPTY_FORM) + const [status, setStatus] = useState("idle") + const [errorMessage, setErrorMessage] = useState(null) + + const texts = applicationFormTexts(isEn) + + const setField = (field: K) => + (value: FormState[K]) => setForm((previous) => ({ ...previous, [field]: value })) + + // A pozíciónál lévő „Jelentkezem erre a pozícióra" gomb tölti ki ezt a két mezőt. + useEffect(() => { + if (!selectedPosition) return + setForm((previous) => ({ + ...previous, + groupName: selectedPosition.groupName, + positionName: selectedPosition.positionName, + })) + }, [selectedPosition]) + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault() + setStatus("submitting") + setErrorMessage(null) + + try { + const response = await fetch("/api/karrier/jelentkezes", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(form), + }) + + if (!response.ok) { + const data = await response.json().catch(() => null) + setErrorMessage(applicationErrorText(data?.error ?? "SERVER_ERROR", isEn)) + setStatus("error") + return + } + + setForm(EMPTY_FORM) + setStatus("success") + } catch { + setErrorMessage(applicationErrorText("SERVER_ERROR", isEn)) + setStatus("error") + } + } + + if (status === "success") { + return ( +
+
+ ) + } + + const isSubmitting = status === "submitting" + + return ( +
+

{texts.heading}

+

{texts.lead}

+ +
+ + +
+ + + + + + + + +
+ + + + + + {status === "error" && errorMessage && ( +

+

+ )} + + + +
+ ) +} diff --git a/src/app/(app)/karrier/components/apply-button.tsx b/src/app/(app)/karrier/components/apply-button.tsx new file mode 100644 index 0000000..18d2d6a --- /dev/null +++ b/src/app/(app)/karrier/components/apply-button.tsx @@ -0,0 +1,41 @@ +"use client" + +import type { ApplicationConfig } from "../types" + +interface ApplyButtonProps { + label: string + application: ApplicationConfig + /** Csak beépített űrlap esetén hívjuk; külső űrlapnál linkként viselkedik. */ + onApply: () => void + /** Méret és szélesség a használat helye szerint. */ + className?: string +} + +const BASE_CLASS = "inline-block rounded-lg bg-frtRed font-bold !text-white transition-colors hover:bg-red-700" + +/** + * A "Jelentkezz" gomb a beállított jelentkezési mód szerint gomb vagy külső + * link. Külső űrlap módban link hiányában semmit nem rajzolunk. + */ +export default function ApplyButton({ label, application, onApply, className = "px-5 py-2.5" }: ApplyButtonProps) { + if (application.useBuiltInForm) { + return ( + + ) + } + + if (!application.googleFormUrl) return null + + return ( + + {label} + + ) +} diff --git a/src/app/(app)/karrier/components/career-group.tsx b/src/app/(app)/karrier/components/career-group.tsx new file mode 100644 index 0000000..028a33f --- /dev/null +++ b/src/app/(app)/karrier/components/career-group.tsx @@ -0,0 +1,123 @@ +"use client" + +import { RichText } from "@payloadcms/richtext-lexical/react" +import { ChevronDown } from "lucide-react" +import Image from "next/image" +import { openPositionsLabel, type CareerTexts } from "../translations" +import type { ApplicationConfig, CareerGroupData, SelectedPosition } from "../types" +import CareerPosition from "./career-position" + +interface CareerGroupProps { + group: CareerGroupData + isOpen: boolean + openPositionKeys: Set + isEn: boolean + texts: CareerTexts + application: ApplicationConfig + onToggle: () => void + onTogglePosition: (key: string) => void + onApply: (position: SelectedPosition) => void +} + +function groupImageUrl(group: CareerGroupData) { + return typeof group.image === "object" && group.image !== null && group.image.url ? group.image.url : null +} + +/** Egy csoport lekattintható sávja és a benne lévő nyitott pozíciók. */ +export default function CareerGroup({ + group, + isOpen, + openPositionKeys, + isEn, + texts, + application, + onToggle, + onTogglePosition, + onApply, +}: CareerGroupProps) { + const groupName = isEn ? group.groupNameEng : group.groupName + const openPositions = group.positions.filter((position) => position.positionOpen) + const imageUrl = groupImageUrl(group) + const panelId = `group-panel-${group.id}` + + return ( +
+
+ + + {isOpen && ( +
+ {openPositions.length > 0 ? ( +
    + {openPositions.map((position, index) => { + const key = `${group.id}-${position.id ?? index}` + const positionName = isEn ? position.positionNameEng : position.positionName + + return ( + onTogglePosition(key)} + onApply={() => onApply({ groupName, positionName })} + /> + ) + })} +
+ ) : ( +

{texts.noOpenPositions}

+ )} +
+ )} +
+
+ ) +} diff --git a/src/app/(app)/karrier/components/career-position.tsx b/src/app/(app)/karrier/components/career-position.tsx new file mode 100644 index 0000000..f15f777 --- /dev/null +++ b/src/app/(app)/karrier/components/career-position.tsx @@ -0,0 +1,75 @@ +"use client" + +import { RichText } from "@payloadcms/richtext-lexical/react" +import { ChevronDown } from "lucide-react" +import type { CareerTexts } from "../translations" +import type { ApplicationConfig, Position } from "../types" +import ApplyButton from "./apply-button" +import PositionHighlights from "./position-highlights" +import PositionSections from "./position-sections" + +interface CareerPositionProps { + position: Position + positionName: string + panelId: string + isOpen: boolean + isEn: boolean + texts: CareerTexts + application: ApplicationConfig + onToggle: () => void + onApply: () => void +} + +/** Egy nyitott pozíció lenyíló sora a csoport panelén belül. */ +export default function CareerPosition({ + position, + positionName, + panelId, + isOpen, + isEn, + texts, + application, + onToggle, + onApply, +}: CareerPositionProps) { + const description = isEn ? position.positionDescriptionEng : position.positionDescription + + return ( +
  • + + + {isOpen && ( +
    + {description && ( +
    + +
    + )} + + + + + + {application.isOpen && ( +
    + +
    + )} +
    + )} +
  • + ) +} diff --git a/src/app/(app)/karrier/components/career-sidebar.tsx b/src/app/(app)/karrier/components/career-sidebar.tsx new file mode 100644 index 0000000..9b87bd6 --- /dev/null +++ b/src/app/(app)/karrier/components/career-sidebar.tsx @@ -0,0 +1,56 @@ +"use client" + +import type { CareerTexts } from "../translations" +import type { ApplicationConfig, CareerGroupData } from "../types" +import ApplyButton from "./apply-button" + +interface CareerSidebarProps { + groups: CareerGroupData[] + isEn: boolean + texts: CareerTexts + application: ApplicationConfig + onSelectGroup: (groupId: number) => void + onApply: () => void +} + +/** Ragadós oldalsáv: csoportlista és a jelentkezés gomb. Csak nagy képernyőn. */ +export default function CareerSidebar({ + groups, + isEn, + texts, + application, + onSelectGroup, + onApply, +}: CareerSidebarProps) { + return ( +
    +
    +

    {texts.groups}

    + +
    + + {application.isOpen && ( +
    +

    {texts.interested}

    + +
    + )} +
    + ) +} diff --git a/src/app/(app)/karrier/components/form-field.tsx b/src/app/(app)/karrier/components/form-field.tsx new file mode 100644 index 0000000..d415124 --- /dev/null +++ b/src/app/(app)/karrier/components/form-field.tsx @@ -0,0 +1,127 @@ +"use client" + +const INPUT_CLASS = + "w-full rounded-lg border border-white/15 bg-black/40 px-4 py-2.5 text-white placeholder:text-gray-500 focus:border-frtRed focus:outline-none focus:ring-1 focus:ring-frtRed" +const LABEL_CLASS = "mb-1.5 block text-sm font-medium text-gray-200" + +interface LabelParts { + label: string + required?: boolean + /** Pl. „opcionális" — halványan a címke mögé kerül. */ + hint?: string +} + +interface FieldLabelProps extends LabelParts { + htmlFor: string +} + +function FieldLabel({ htmlFor, label, required, hint }: FieldLabelProps) { + return ( + + ) +} + +interface TextFieldProps extends LabelParts { + id: string + value: string + onChange: (value: string) => void + type?: "text" | "email" | "tel" + autoComplete?: string +} + +export function TextField({ id, label, value, onChange, type = "text", required, hint, autoComplete }: TextFieldProps) { + return ( +
    + + onChange(event.target.value)} + /> +
    + ) +} + +interface TextAreaFieldProps extends LabelParts { + id: string + value: string + onChange: (value: string) => void + rows?: number +} + +export function TextAreaField({ id, label, value, onChange, rows = 5, required, hint }: TextAreaFieldProps) { + return ( +
    + +