EventSika adalah platform pusat informasi kegiatan kemahasiswaan (event) terpadu yang dirancang khusus untuk civitas akademika Universitas Singaperbangsa Karawang (UNSIKA). Platform ini mengintegrasikan Sistem Rekomendasi Hibrida berbasis Artificial Intelligence (AI) untuk mencocokkan profil akademik (Program Studi) dan preferensi minat mahasiswa dengan metadata event guna menghasilkan rekomendasi kegiatan yang personal dan relevan.
Aplikasi telah direfaktor sepenuhnya (V2.0) dari basis kode MVP menjadi struktur modular berbasis domain (Domain-Driven layout) dengan keamanan ketat berbasis Row Level Security (RLS) di PostgreSQL Supabase, integrasi model NLP Hugging Face 768-dimensi, dan fungsionalitas pengujian algoritma rekomendasi.
- Tentang Proyek
- Fitur Utama
- Arsitektur & Tumpukan Teknologi
- Struktur Direktori Proyek
- Sistem Rekomendasi Hybrid AI
- Panduan Instalasi Lokal
- Skema Database & RPC Supabase
- Panduan Pengujian Algoritma
EventSika dikembangkan untuk mengatasi fragmentasi penyebaran informasi kegiatan mahasiswa di lingkungan kampus UNSIKA. Sebelumnya, informasi kegiatan kemahasiswaan tersebar di berbagai akun media sosial organisasi mahasiswa, menyulitkan mahasiswa dalam menemukan kegiatan yang relevan dengan minat mereka dan menyebabkan panitia kesulitan menjangkau target peserta yang tepat.
Dengan EventSika, semua kegiatan kemahasiswaan disatukan dalam satu wadah digital yang dinamis, dilengkapi dengan filter berbasis jurusan/fakultas, pencarian teks penuh, serta sistem feed pintar terpersonalisasi yang ditenagai oleh pencarian vektor kosinus.
- Feed Rekomendasi Terpersonalisasi: Feed utama menampilkan daftar event yang dicocokkan secara pintar berdasarkan program studi dan topik minat yang dipilih pengguna saat onboarding.
- Pencarian Cerdas & Filter Terstruktur: Pencarian teks penuh (full-text search) di kolom judul, deskripsi, dan nama organizer, dikombinasikan dengan kategori event.
- Autentikasi & Onboarding: Registrasi aman menggunakan Supabase Auth terintegrasi cookie server, dilengkapi dengan alur onboarding pemilihan program studi UNSIKA dan topik minat (Teknologi, Bisnis, Seni, Olahraga, dll.).
- Manajemen Event (Lifecycle):
- Organizer: Mengajukan event baru dengan mengunggah banner poster, mendefinisikan detail tanggal mulai/selesai, target jurusan, kategori utama, dan tag spesifik. Status event bergerak melalui alur:
Draft➔Pending➔Published(jika disetujui Admin) atauRejected. - Admin: Menyetujui atau menolak event pengajuan serta memasukkan alasan penolakan.
- Organizer: Mengajukan event baru dengan mengunggah banner poster, mendefinisikan detail tanggal mulai/selesai, target jurusan, kategori utama, dan tag spesifik. Status event bergerak melalui alur:
- Pengajuan Role Organizer: Pengguna biasa dapat mengajukan permohonan menjadi Organizer dengan mengunggah bukti kelayakan. Aplikasi ditinjau langsung oleh Admin.
- Bookmark Event: Fitur penyimpanan event menarik (Save Event) untuk ditonton kembali di kemudian hari.
- Panel Dashboard Khusus:
- Admin Dashboard: Menampilkan ringkasan statistik platform, peninjauan permohonan organizer, dan peninjauan event masuk.
- Organizer Dashboard: Menyajikan statistik performa event buatan organizer (draft, pending, published, rejected) dan alat pembuatan event.
- Suite Pengujian Algoritma Rekomendasi: Panel eksperimen bagi pengembang untuk mengevaluasi metrik (Precision@K, Recall@K, F1@K, MAP) secara langsung dan melakukan ekspor CSV massal (Bulk Export) rekomendasi di bawah skenario pembobotan S1-S5.
Aplikasi ini menggunakan arsitektur Serverless Fullstack modern yang dioptimalkan untuk kecepatan muat (RSC) dan integrasi database vektor:
- Framework: Next.js 16.1.6 (App Router) & React 19.2.4
- Bahasa: TypeScript (Strict Mode, bebas
any) - Desain & Gaya: Tailwind CSS 3.4.17, Shadcn UI (Radix UI), Framer Motion untuk mikro-animasi, dan Sonner untuk notifikasi.
- State Management & Caching: Zustand 5.0.8 dan TanStack Query v5 untuk caching data klien.
- Formulir & Validasi: React Hook Form & Zod untuk validasi schema klien-server.
- Backend & Database: Next.js Server Actions, Supabase SSR 0.7.0 untuk auth berbasis cookie server, PostgreSQL, dan ekstensi
pgvectoruntuk database vektor. - AI NLP Model: Hugging Face Inference API menggunakan model
sentence-transformers/paraphrase-multilingual-mpnet-base-v2. Menghasilkan embedding vektor sebesar 768 dimensi. - Package Manager:
pnpm(sangat direkomendasikan dan dikonfigurasi di repositori ini).
Aplikasi dirancang dengan struktur modular yang menempatkan fungsionalitas bisnis berdasarkan domain bisnis di dalam direktori modules/ untuk skalabilitas yang baik:
eventsika/
├── app/ # App Router (Pages, Layouts, Routing)
│ ├── (auth)/ # Halaman login, register, onboarding
│ ├── (dashboard)/ # Dashboard admin & organizer (events, analytics, dll)
│ ├── (main)/ # Halaman publik (home, event detail, profile)
│ ├── (types)/ # Tipe data spesifik routing
│ ├── action.ts # V2.0 Re-export Hub (backward compatibility)
│ ├── error.tsx # Error Boundary global
│ └── layout.tsx # Shell tata letak global & penyedia provider
├── components/ # Komponen UI global reusable (Shared, UI, Skeletons)
├── docs/ # Dokumentasi proyek (PRD, DESIGN, ARCHITECTURE, PROJECT_CONTEXT)
├── lib/ # Utilitas lintas-domain & konfigurasi global
│ ├── supabase/ # Inisialisasi client & server Supabase (client.ts, server.ts)
│ ├── embedding.ts # Integrasi model AI Hugging Face (pembangkitan vektor 768-dim)
│ ├── taxonomy.ts # Hierarki topik & logika taxonomy matching
│ ├── metrics.ts # Kalkulasi metrik evaluasi rekomendasi (MAP, Precision@K)
│ └── queries.ts # Global database queries (tanpa manipulasi/mutasi)
├── modules/ # Domain-specific modules (Modular Business Logic)
│ ├── admin/ # Logic & actions persetujuan admin (actions.ts)
│ ├── auth/ # Logic & actions otentikasi & onboarding (actions.ts)
│ ├── bookmark/ # Logic & actions bookmark event (actions.ts)
│ ├── events/ # Logic & actions manajemen & kueri event (actions.ts, queries.ts)
│ ├── profile/ # Logic & actions manajemen profil & interest (actions.ts)
│ └── recommendation/ # Logic & service sistem rekomendasi AI (ai-service.ts)
├── public/ # File statis (gambar, ikon, font)
├── types/ # Core domain types
│ └── index.ts # Unified TypeScript interfaces (UserProfile, Event, dll)
Sistem rekomendasi EventSika mengadopsi pendekatan hibrida (hybrid recommendation) dengan arsitektur 4-Tier Fallback guna memastikan toleransi kesalahan yang tinggi (graceful degradation):
- Tier 1 (AI Vector Search): Sistem mengambil minat kategori pengguna (parent interests), memperluasnya menggunakan taksonomi topik (taxonomy expansion) ke dalam tag spesifik (child tags), lalu mengirimkannya ke Hugging Face Inference API untuk membuat vektor representasi minat pengguna (interest vector 768 dimensi). Vektor ini dicocokkan dengan vektor embedding event di database Supabase melalui Cosine Similarity (
<=>) dikombinasikan dengan aturan kecocokan program studi (Bobot: 80% semantik, 20% program studi). - Tier 2 (Category Match): Jika Hugging Face API mengalami kegagalan/timeout (misal karena cold start), sistem secara otomatis melakukan pemindaian konvensional berdasarkan kesesuaian kategori minat pengguna.
- Tier 3 (Latest Fallback): Jika data kategori event tidak memadai, sistem menyajikan daftar event terbitan terbaru secara kronologis.
- Tier 4 (Cold Start): Jika pengguna adalah mahasiswa baru yang belum mengisi data minat pada profilnya, sistem mengarahkan mereka untuk melengkapi profil sambil menyajikan feed event terpopuler/terbaru.
Ikuti langkah-langkah di bawah ini untuk menjalankan EventSika di mesin lokal Anda.
- Node.js (versi 18.x atau lebih tinggi)
- pnpm (direkomendasikan, versi 8.x atau lebih tinggi)
-
Clone Repositori
git clone https://github.com/zidankhainur2/eventsika.git cd eventsika -
Instal Dependensi Gunakan
pnpmuntuk memasang semua modul proyek:pnpm install
-
Konfigurasi Environment Variables Buat file bernama
.env.localdi root direktori proyek Anda dan isi variabel berikut:NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key HF_API_TOKEN=your-huggingface-api-token
-
Jalankan Server Pengembangan Jalankan server lokal Next.js dengan turbopack:
pnpm dev
Buka http://localhost:3000 di browser Anda untuk melihat jalannya aplikasi.
Jalankan skrip SQL berikut pada menu SQL Editor di dashboard Supabase Anda untuk menyiapkan tabel, relasi, tipe enum, ekstensi pgvector, serta fungsi database yang diperlukan.
-- 1. Aktifkan Ekstensi pgvector
CREATE EXTENSION IF NOT EXISTS vector;
-- 2. Tipe Enum
CREATE TYPE user_role AS ENUM ('student', 'organizer', 'admin');
CREATE TYPE event_status AS ENUM ('draft', 'pending', 'published', 'rejected');
CREATE TYPE application_status AS ENUM ('pending', 'approved', 'rejected');
-- 3. Tabel Profiles (User Profile)
CREATE TABLE public.profiles (
id uuid REFERENCES auth.users ON DELETE CASCADE PRIMARY KEY,
full_name text,
avatar_url text,
role user_role DEFAULT 'student'::user_role NOT NULL,
student_id text, -- NIM
faculty text,
major text,
academic_year text, -- Angkatan
interests text, -- Minat kategori dipisahkan koma
interest_vector vector(768), -- Vektor representasi minat semantik
interests_updated_at timestamp with time zone,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now()
);
-- 4. Tabel Events
CREATE TABLE public.events (
id uuid DEFAULT gen_random_uuid() NOT NULL PRIMARY KEY,
title text NOT NULL,
slug text UNIQUE NOT NULL,
description text,
organizer_id uuid REFERENCES public.profiles(id) ON DELETE SET NULL,
organizer_name text NOT NULL,
category text NOT NULL,
tags text[] DEFAULT '{}'::text[],
target_majors text[] DEFAULT '{}'::text[],
start_date timestamp with time zone NOT NULL,
end_date timestamp with time zone NOT NULL,
image_url text NOT NULL,
status event_status DEFAULT 'draft'::event_status NOT NULL,
rejection_reason text,
embedding vector(768), -- Vektor semantik judul + deskripsi + tag
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now()
);
-- 5. Tabel Organizer Applications
CREATE TABLE public.organizer_applications (
id uuid DEFAULT gen_random_uuid() NOT NULL PRIMARY KEY,
user_id uuid REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
organization_name text NOT NULL,
contact_person text NOT NULL,
phone text,
description text,
status application_status DEFAULT 'pending'::application_status NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now()
);
-- 6. Tabel Saved Events (Bookmarks)
CREATE TABLE public.saved_events (
id uuid DEFAULT gen_random_uuid() NOT NULL PRIMARY KEY,
user_id uuid REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
event_id uuid REFERENCES public.events(id) ON DELETE CASCADE NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT unique_user_event_bookmark UNIQUE (user_id, event_id)
);
-- 7. Hidupkan Row Level Security (RLS)
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.events ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.organizer_applications ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.saved_events ENABLE ROW LEVEL SECURITY;
-- 8. Tambahkan Indeks Pencarian & Vektor (HNSW)
CREATE INDEX idx_events_status ON public.events(status);
CREATE INDEX idx_events_end_date ON public.events(end_date);
CREATE INDEX idx_events_vector ON public.events USING hnsw (embedding vector_cosine_ops);
-- 9. RPC: Mengupdate Interest Vector Profil
CREATE OR REPLACE FUNCTION public.update_user_interest_vector(
p_user_id uuid,
p_interest_vec vector(768)
)
RETURNS void AS $$
BEGIN
UPDATE public.profiles
SET
interest_vector = p_interest_vec,
interests_updated_at = now()
WHERE id = p_user_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 10. RPC: get_hybrid_recommendations
CREATE OR REPLACE FUNCTION public.get_hybrid_recommendations(
query_embedding vector(768),
p_user_id uuid DEFAULT NULL,
p_user_major text DEFAULT '',
p_user_interests text[] DEFAULT '{}'::text[],
p_weight_semantic double precision DEFAULT 0.8,
p_weight_rule double precision DEFAULT 0.2,
p_threshold double precision DEFAULT 0.5,
match_count integer DEFAULT 10
)
RETURNS TABLE(
id uuid,
title text,
slug text,
description text,
organizer_id uuid,
organizer_name text,
category text,
tags text[],
target_majors text[],
start_date timestamp with time zone,
end_date timestamp with time zone,
image_url text,
status event_status,
created_at timestamp with time zone,
vector_score double precision,
major_score double precision,
tag_score double precision,
rule_score double precision,
total_score double precision
) AS $$
BEGIN
RETURN QUERY
WITH scored_events AS (
SELECT
e.id AS ev_id,
e.title AS ev_title,
e.slug AS ev_slug,
e.description AS ev_desc,
e.organizer_id AS ev_org_id,
e.organizer_name AS ev_org_name,
e.category AS ev_cat,
e.tags AS ev_tags,
e.target_majors AS ev_majors,
e.start_date AS ev_start,
e.end_date AS ev_end,
e.image_url AS ev_img,
e.status AS ev_status,
e.created_at AS ev_created,
-- 1) Skor Vektor Semantik (Cosine Similarity)
-- 1 - (embedding <=> query_embedding) mengubah cosine distance menjadi cosine similarity [0, 1]
(1 - (e.embedding <=> query_embedding))::double precision AS v_score,
-- 2) Skor Jurusan (Rule 1)
-- Bernilai 1.0 jika major user ada di list target_majors, atau list target_majors berisi 'Umum', selain itu 0.0
(CASE
WHEN p_user_major = '' THEN 0.0
WHEN 'Umum' = ANY(e.target_majors) THEN 1.0
WHEN p_user_major = ANY(e.target_majors) THEN 1.0
ELSE 0.0
END)::double precision AS m_score,
-- 3) Skor Tag (Rule 2)
-- Menghitung irisan antara tags event dengan tag minat user (taxonomy child), di-normalisasi
(CASE
WHEN array_length(e.tags, 1) IS NULL OR array_length(p_user_interests, 1) IS NULL THEN 0.0
ELSE LEAST(
(SELECT count(*)::double precision
FROM unnest(e.tags) x
WHERE x = ANY(p_user_interests)) / 3.0,
1.0
)
END)::double precision AS t_score
FROM public.events e
WHERE e.status = 'published'::event_status
AND e.end_date >= now()
)
SELECT
ev_id,
ev_title,
ev_slug,
ev_desc,
ev_org_id,
ev_org_name,
ev_cat,
ev_tags,
ev_majors,
ev_start,
ev_end,
ev_img,
ev_status,
ev_created,
v_score AS vector_score,
m_score AS major_score,
t_score AS tag_score,
-- Rule Score merupakan rata-rata dari major_score dan tag_score
(m_score * 0.5 + t_score * 0.5) AS rule_score,
-- Total Score = (Semantic * Alpha) + (Rule * Beta)
(v_score * p_weight_semantic + (m_score * 0.5 + t_score * 0.5) * p_weight_rule) AS total_score
FROM scored_events
WHERE v_score >= p_threshold
ORDER BY total_score DESC, ev_created DESC
LIMIT match_count;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 11. RPC: evaluate_recommendations (Untuk Halaman Uji Algoritma)
-- Cara kerja mirip dengan get_hybrid_recommendations tetapi menonaktifkan threshold (threshold = 0.0)
-- dan mengambil data untuk evaluasi metrik Precision/Recall yang tidak terbatasi filter ketat.
CREATE OR REPLACE FUNCTION public.evaluate_recommendations(
query_embedding vector(768),
p_user_id uuid,
p_user_major text,
p_user_interests text[],
p_weight_semantic double precision,
p_weight_rule double precision,
p_threshold double precision DEFAULT 0.0,
match_count integer DEFAULT 50
)
RETURNS TABLE(
id uuid,
title text,
category text,
tags text[],
target_majors text[],
start_date timestamp with time zone,
end_date timestamp with time zone,
vector_score double precision,
major_score double precision,
tag_score double precision,
rule_score double precision,
total_score double precision
) AS $$
BEGIN
RETURN QUERY
WITH scored_events AS (
SELECT
e.id AS ev_id,
e.title AS ev_title,
e.category AS ev_cat,
e.tags AS ev_tags,
e.target_majors AS ev_majors,
e.start_date AS ev_start,
e.end_date AS ev_end,
(1 - (e.embedding <=> query_embedding))::double precision AS v_score,
(CASE
WHEN p_user_major = '' THEN 0.0
WHEN 'Umum' = ANY(e.target_majors) THEN 1.0
WHEN p_user_major = ANY(e.target_majors) THEN 1.0
ELSE 0.0
END)::double precision AS m_score,
(CASE
WHEN array_length(e.tags, 1) IS NULL OR array_length(p_user_interests, 1) IS NULL THEN 0.0
ELSE LEAST(
(SELECT count(*)::double precision
FROM unnest(e.tags) x
WHERE x = ANY(p_user_interests)) / 3.0,
1.0
)
END)::double precision AS t_score
FROM public.events e
WHERE e.status = 'published'::event_status
AND e.end_date >= now()
)
SELECT
ev_id,
ev_title,
ev_cat,
ev_tags,
ev_majors,
ev_start,
ev_end,
v_score AS vector_score,
m_score AS major_score,
t_score AS tag_score,
(m_score * 0.5 + t_score * 0.5) AS rule_score,
(v_score * p_weight_semantic + (m_score * 0.5 + t_score * 0.5) * p_weight_rule) AS total_score
FROM scored_events
WHERE v_score >= p_threshold
ORDER BY total_score DESC, ev_start ASC
LIMIT match_count;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;EventSika menyertakan fungsionalitas pengujian internal untuk membantu pengembang dan akademisi mengevaluasi akurasi sistem rekomendasi hybrid.
Gunakan fungsi runRecommendationTest(userId, alpha, beta) di Server Action untuk mensimulasikan hasil rekomendasi bagi pengguna tertentu dengan parameter bobot khusus. Anda dapat memicu ini melalui UI panel Admin/Pengujian.
Panggil fungsi bulkExportRecommendations() untuk:
- Memindai seluruh profil mahasiswa aktif di sistem.
- Menghitung representasi vektor minat (interest vector) mereka menggunakan Hugging Face.
- Mengeksekusi pencarian rekomendasi hybrid untuk 5 skenario pembobotan (S1 hingga S5):
- S1 (Alpha 1.0 : Beta 0.0): Murni Semantik AI.
- S2 (Alpha 0.8 : Beta 0.2): Dominan Semantik AI.
- S3 (Alpha 0.5 : Beta 0.5): Seimbang (50% Semantik, 50% Aturan).
- S4 (Alpha 0.2 : Beta 0.8): Dominan Aturan Heuristik.
- S5 (Alpha 0.0 : Beta 1.0): Murni Aturan Heuristik (Tanpa AI).
- Menyusun baris rekomendasi ke format CSV terstruktur yang siap diunduh untuk kalkulasi metrik Precision, Recall, F1, dan MAP secara eksternal.