Setup Database

Jalankan SQL berikut di SQL Editor Supabase. Penting: nonaktifkan juga opsi "Confirm email" di Dashboard Supabase (Authentication > Sign In / Providers > Email) agar user bisa langsung login tanpa verifikasi email.

1. Setup Awal
-- Buat tabel users CREATE TABLE IF NOT EXISTS users ( username TEXT PRIMARY KEY, password TEXT NOT NULL, name TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'staff', password_hash TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); INSERT INTO users (username, password, name, role) VALUES ('ADMIN','admin123','Administrator','admin'), ('FAZA','faza123','FAZA','staff'),('ZEFA','zefa123','ZEFA','staff'), ('AMIN','amin123','AMIN','staff'),('ERWIN','erwin123','ERWIN','staff'), ('PEBI','pebi123','PEBI','staff') ON CONFLICT (username) DO NOTHING; -- Kolom password_hash terisi otomatis (hash) saat user login pertama kali -- Buat tabel tickets (Tanpa kolom closed_at) CREATE TABLE IF NOT EXISTS tickets ( id TEXT PRIMARY KEY, connote TEXT NOT NULL, pic TEXT, complaint TEXT, reason TEXT, coding_destination TEXT, status TEXT DEFAULT 'open', remark TEXT, bukti_foto TEXT, created_by TEXT, updated_by TEXT, reopen_count INTEGER DEFAULT 0, reopen_date TIMESTAMPTZ, date_in_progress TIMESTAMPTZ, date_on_confirm TIMESTAMPTZ, date_resolved TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Buat tabel app_settings untuk Backup Mode Global CREATE TABLE IF NOT EXISTS app_settings ( id INT PRIMARY KEY DEFAULT 1, backup_mode BOOLEAN DEFAULT FALSE ); INSERT INTO app_settings (id, backup_mode) VALUES (1, FALSE) ON CONFLICT (id) DO NOTHING;
2. Migrasi & Pembersihan Kolom
-- Tambah kolom yang belum ada ALTER TABLE users ADD COLUMN IF NOT EXISTS password_hash TEXT; ALTER TABLE tickets ADD COLUMN IF NOT EXISTS date_in_progress TIMESTAMPTZ; ALTER TABLE tickets ADD COLUMN IF NOT EXISTS date_on_confirm TIMESTAMPTZ; ALTER TABLE tickets ADD COLUMN IF NOT EXISTS date_resolved TIMESTAMPTZ; ALTER TABLE tickets ADD COLUMN IF NOT EXISTS reopen_date TIMESTAMPTZ; ALTER TABLE tickets ADD COLUMN IF NOT EXISTS reopen_count INTEGER DEFAULT 0; ALTER TABLE tickets ADD COLUMN IF NOT EXISTS remark TEXT; ALTER TABLE tickets ADD COLUMN IF NOT EXISTS bukti_foto TEXT; ALTER TABLE tickets ADD COLUMN IF NOT EXISTS created_by TEXT; ALTER TABLE tickets ADD COLUMN IF NOT EXISTS updated_by TEXT; -- Salin data dari resolved_at ke date_resolved (jika kolom masih ada) UPDATE tickets SET date_resolved = COALESCE(date_resolved, resolved_at, updated_at) WHERE status='resolved' AND date_resolved IS NULL; -- Isi tanggal berdasarkan status lama yang kosong UPDATE tickets SET date_in_progress = updated_at WHERE status IN ('in_progress','on_confirm','resolved') AND date_in_progress IS NULL; UPDATE tickets SET date_on_confirm = updated_at WHERE status IN ('on_confirm','resolved') AND date_on_confirm IS NULL; UPDATE tickets SET date_resolved = updated_at WHERE status='resolved' AND date_resolved IS NULL; UPDATE tickets SET reopen_count = 0 WHERE reopen_count IS NULL; UPDATE tickets SET created_by = pic WHERE created_by IS NULL; -- Ubah semua status closed menjadi resolved (Karena status closed sudah dihapus dari sistem) UPDATE tickets SET status = 'resolved', date_resolved = COALESCE(date_resolved, closed_at, updated_at) WHERE status = 'closed'; -- HAPUS kolom yang tidak terpakai ALTER TABLE tickets DROP COLUMN IF EXISTS resolved_at; ALTER TABLE tickets DROP COLUMN IF EXISTS date_reopened; ALTER TABLE tickets DROP COLUMN IF EXISTS closed_at;
3. Keamanan: RLS + Auth Gateway
-- Ekstensi & kolom pengikat akun CREATE EXTENSION IF NOT EXISTS pgcrypto; ALTER TABLE users ADD COLUMN IF NOT EXISTS auth_uid UUID; -- Fungsi identitas CREATE OR REPLACE FUNCTION public.is_app_user() RETURNS BOOLEAN LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public, extensions AS $$ SELECT EXISTS(SELECT 1 FROM public.users u WHERE u.auth_uid = auth.uid()); $$; CREATE OR REPLACE FUNCTION public.app_role() RETURNS TEXT LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public, extensions AS $$ SELECT u.role FROM public.users u WHERE u.auth_uid = auth.uid() LIMIT 1; $$; -- Login legacy (khusus user yang belum terikat akun auth) CREATE OR REPLACE FUNCTION public.fn_legacy_login(p_username TEXT, p_password TEXT) RETURNS TABLE (o_username TEXT, o_name TEXT, o_role TEXT) LANGUAGE plpgsql SECURITY DEFINER STABLE SET search_path = public, extensions AS $$ DECLARE r users%ROWTYPE; v_ok BOOLEAN := FALSE; v_salt TEXT; v_hash TEXT; BEGIN SELECT * INTO r FROM users WHERE upper(username) = upper(p_username) LIMIT 1; IF NOT FOUND THEN RETURN; END IF; IF r.auth_uid IS NOT NULL THEN RETURN; END IF; IF COALESCE(r.password,'') <> '' AND r.password = p_password THEN v_ok := TRUE; END IF; IF NOT v_ok AND position('$' IN COALESCE(r.password_hash,'')) > 0 THEN v_salt := split_part(r.password_hash,'$',1); v_hash := split_part(r.password_hash,'$',2); IF length(v_salt)=24 AND length(v_hash)=64 AND encode(digest(v_salt || '$' || p_password,'sha256'),'hex') = v_hash THEN v_ok := TRUE; END IF; END IF; IF v_ok THEN RETURN QUERY SELECT r.username, r.name, r.role; END IF; END; $$; -- Pengikat profil ke akun auth (anti pembajakan username) CREATE OR REPLACE FUNCTION public.fn_bind_auth(p_username TEXT, p_password TEXT, p_uid UUID) RETURNS VOID LANGUAGE plpgsql SECURITY DEFINER SET search_path = public, extensions AS $$ DECLARE r users%ROWTYPE; v_ok BOOLEAN := FALSE; v_salt TEXT; v_hash TEXT; BEGIN IF auth.uid() IS NULL OR p_uid IS NULL OR auth.uid() <> p_uid THEN RETURN; END IF; SELECT * INTO r FROM users WHERE upper(username) = upper(p_username) LIMIT 1; IF NOT FOUND THEN RETURN; END IF; IF r.auth_uid IS NOT NULL THEN RETURN; END IF; IF COALESCE(r.password,'') <> '' AND r.password = p_password THEN v_ok := TRUE; END IF; IF NOT v_ok AND position('$' IN COALESCE(r.password_hash,'')) > 0 THEN v_salt := split_part(r.password_hash,'$',1); v_hash := split_part(r.password_hash,'$',2); IF length(v_salt)=24 AND length(v_hash)=64 AND encode(digest(v_salt || '$' || p_password,'sha256'),'hex') = v_hash THEN v_ok := TRUE; END IF; END IF; IF v_ok THEN UPDATE users SET auth_uid = p_uid, password = '', password_hash = NULL WHERE username = r.username; END IF; END; $$; -- Cabut seluruh akses lama (anon tidak boleh apa-apa lagi) DROP POLICY IF EXISTS "Allow anon access users" ON public.users; DROP POLICY IF EXISTS "Allow anon access tickets" ON public.tickets; DROP POLICY IF EXISTS "Allow anon access app_settings" ON public.app_settings; REVOKE ALL ON public.users FROM anon, authenticated; REVOKE ALL ON public.tickets FROM anon, authenticated; REVOKE ALL ON public.app_settings FROM anon, authenticated; ALTER TABLE public.users ENABLE ROW LEVEL SECURITY; ALTER TABLE public.tickets ENABLE ROW LEVEL SECURITY; ALTER TABLE public.app_settings ENABLE ROW LEVEL SECURITY; -- Policy baru: hanya user yang login via Supabase Auth DROP POLICY IF EXISTS "tickets_all_app_users" ON public.tickets; CREATE POLICY "tickets_all_app_users" ON public.tickets FOR ALL TO authenticated USING (is_app_user()) WITH CHECK (is_app_user()); DROP POLICY IF EXISTS "users_select_app_users" ON public.users; CREATE POLICY "users_select_app_users" ON public.users FOR SELECT TO authenticated USING (is_app_user()); DROP POLICY IF EXISTS "users_insert_admin" ON public.users; CREATE POLICY "users_insert_admin" ON public.users FOR INSERT TO authenticated WITH CHECK (app_role() = 'admin'); DROP POLICY IF EXISTS "users_update_self_or_admin" ON public.users; CREATE POLICY "users_update_self_or_admin" ON public.users FOR UPDATE TO authenticated USING (auth_uid = auth.uid() OR app_role() = 'admin') WITH CHECK (auth_uid = auth.uid() OR app_role() = 'admin'); DROP POLICY IF EXISTS "users_delete_admin" ON public.users; CREATE POLICY "users_delete_admin" ON public.users FOR DELETE TO authenticated USING (app_role() = 'admin'); DROP POLICY IF EXISTS "settings_read_all" ON public.app_settings; CREATE POLICY "settings_read_all" ON public.app_settings FOR SELECT TO anon, authenticated USING (true); DROP POLICY IF EXISTS "settings_update_admin" ON public.app_settings; CREATE POLICY "settings_update_admin" ON public.app_settings FOR UPDATE TO authenticated USING (app_role() = 'admin') WITH CHECK (app_role() = 'admin'); -- Grants (kolom password disembunyikan dari client biasa) GRANT EXECUTE ON FUNCTION public.fn_legacy_login(TEXT,TEXT) TO anon, authenticated; GRANT EXECUTE ON FUNCTION public.fn_bind_auth(TEXT,TEXT,UUID) TO authenticated; GRANT EXECUTE ON FUNCTION public.is_app_user() TO authenticated; GRANT EXECUTE ON FUNCTION public.app_role() TO authenticated; GRANT SELECT ON public.app_settings TO anon; GRANT SELECT, UPDATE ON public.app_settings TO authenticated; GRANT ALL ON public.tickets TO authenticated; GRANT SELECT (username, name, role, auth_uid) ON public.users TO authenticated; GRANT INSERT (username, name, role, auth_uid, password, password_hash) ON public.users TO authenticated; GRANT UPDATE (username, name, role, auth_uid, password, password_hash) ON public.users TO authenticated; GRANT DELETE ON public.users TO authenticated; -- ===== AUDIT LOG (anti-tamper: tidak bisa diubah/dihapus dari client) ===== CREATE TABLE IF NOT EXISTS public.audit_logs ( id BIGSERIAL PRIMARY KEY, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), actor TEXT, actor_role TEXT, action TEXT NOT NULL, entity TEXT NOT NULL, entity_id TEXT, detail TEXT ); CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON public.audit_logs (created_at DESC); CREATE INDEX IF NOT EXISTS idx_audit_logs_actor ON public.audit_logs (actor); ALTER TABLE public.audit_logs ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS "audit_insert_app_users" ON public.audit_logs; CREATE POLICY "audit_insert_app_users" ON public.audit_logs FOR INSERT TO authenticated WITH CHECK (is_app_user()); DROP POLICY IF EXISTS "audit_select_admin" ON public.audit_logs; CREATE POLICY "audit_select_admin" ON public.audit_logs FOR SELECT TO authenticated USING (app_role() = 'admin'); REVOKE ALL ON public.audit_logs FROM anon, authenticated; GRANT INSERT ON public.audit_logs TO authenticated; GRANT SELECT ON public.audit_logs TO authenticated;

ConnoteTrackingCCC

Monitoring & Ticketing System

SYSTEM BACKUP MODE ACTIVE
Sistem sedang dalam proses Backup. User non-admin tidak dapat login sementara.
ConnoteTrackingMonitoring & Ticketing System
  • Menu Utama
  • Menu Ticketing
  • Daftar User
  • Audit Log
  • Edit Password
A
Admin
Administrator
ConnoteTracker

Selamat Datang, Admin

MODE BACKUP AKTIF: Akses user lain sementara diblokir.
Peringatan Backup DataJumlah ticket mendekati batas.
0

0

Open

0

In Progress

0

On Confirm

0

Resolved

0

Overdue (>7 hari)

Reminder: Ticket > 1 hari belum di-update.  |||  Total Ticket : 0

Tiket Open

Memuat...

Total Tickets Created

Top 5 Complaint

Ticket Overview - Last 7 Days

Daftar User Login

Kelola akun pengguna.

NoUsernameNamaRoleAksi

Belum Ada User

Audit Log

Riwayat aktivitas pengguna. Catatan ini tidak dapat diubah maupun dihapus dari aplikasi.

WaktuUserAksiObjekDetail

Belum Ada Aktivitas Tercatat

Edit Password User

Ubah password akun pengguna.

Menu Ticketing

Kelola semua ticket complaint dan monitoring.

MODE BACKUP AKTIF: Hapus data resolved untuk mengosongkan database.
Peringatan Backup DataJumlah ticket mendekati batas.
0
Reminder: Ticket > 1 hari belum di-update.  |||  Total Ticket : 0

0

Open

0

In Progress

0

On Confirm

0

Resolved

0

Overdue (>7 hari)

NoConnotePICComplaintDestinationStatusAgeCreatedUpdatedAksi

Tidak Ada Ticket

Memuat data ticket...

Release 2.1 · Built with ☕ & "99% Coding, 1% Keajaiban."

Input Ticket Baru

Detail Ticket

Tambah User Baru

Export Data

File akan disimpan sebagai .csv

Detail Ticket Reminder

Peringatan Kapasitas Database

Konfirmasi
Apakah Anda yakin?