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 CREATE POLICY "tickets_all_app_users" ON public.tickets FOR ALL TO authenticated USING (is_app_user()) WITH CHECK (is_app_user()); CREATE POLICY "users_select_app_users" ON public.users FOR SELECT TO authenticated USING (is_app_user()); CREATE POLICY "users_insert_admin" ON public.users FOR INSERT TO authenticated WITH CHECK (app_role() = 'admin'); 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'); CREATE POLICY "users_delete_admin" ON public.users FOR DELETE TO authenticated USING (app_role() = 'admin'); CREATE POLICY "settings_read_all" ON public.app_settings FOR SELECT TO anon, authenticated USING (true); 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) 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;

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
  • 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

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?