v1.0
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { users, placesOfWorship, recipients, distributions } from "./schema";
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
managedPlaces: many(placesOfWorship),
|
||||
}));
|
||||
|
||||
export const placesOfWorshipRelations = relations(placesOfWorship, ({ one, many }) => ({
|
||||
manager: one(users, {
|
||||
fields: [placesOfWorship.managerId],
|
||||
references: [users.id],
|
||||
}),
|
||||
recipients: many(recipients),
|
||||
distributions: many(distributions),
|
||||
}));
|
||||
|
||||
export const recipientsRelations = relations(recipients, ({ one, many }) => ({
|
||||
placeOfWorship: one(placesOfWorship, {
|
||||
fields: [recipients.placeOfWorshipId],
|
||||
references: [placesOfWorship.id],
|
||||
}),
|
||||
distributions: many(distributions),
|
||||
}));
|
||||
|
||||
export const distributionsRelations = relations(distributions, ({ one }) => ({
|
||||
placeOfWorship: one(placesOfWorship, {
|
||||
fields: [distributions.placeOfWorshipId],
|
||||
references: [placesOfWorship.id],
|
||||
}),
|
||||
recipient: one(recipients, {
|
||||
fields: [distributions.recipientId],
|
||||
references: [recipients.id],
|
||||
}),
|
||||
}));
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
import {
|
||||
mysqlTable,
|
||||
mysqlEnum,
|
||||
serial,
|
||||
varchar,
|
||||
text,
|
||||
timestamp,
|
||||
bigint,
|
||||
int,
|
||||
decimal,
|
||||
index,
|
||||
} from "drizzle-orm/mysql-core";
|
||||
|
||||
// ── Users (base auth table) ──────────────────────────────────
|
||||
export const users = mysqlTable("users", {
|
||||
id: serial("id").primaryKey(),
|
||||
unionId: varchar("unionId", { length: 255 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }),
|
||||
email: varchar("email", { length: 320 }),
|
||||
passwordHash: text("password_hash"),
|
||||
avatar: text("avatar"),
|
||||
role: mysqlEnum("role", ["admin", "officer", "manager"]).default("manager").notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().notNull().$onUpdate(() => new Date()),
|
||||
lastSignInAt: timestamp("lastSignInAt").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type User = typeof users.$inferSelect;
|
||||
export type InsertUser = typeof users.$inferInsert;
|
||||
|
||||
// ── Places of Worship ─────────────────────────────────────────
|
||||
export const placesOfWorship = mysqlTable(
|
||||
"places_of_worship",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
type: mysqlEnum("type", ["mosque", "church", "temple", "vihara", "other"]).notNull(),
|
||||
address: text("address"),
|
||||
latitude: decimal("latitude", { precision: 10, scale: 8 }).notNull(),
|
||||
longitude: decimal("longitude", { precision: 11, scale: 8 }).notNull(),
|
||||
radius: int("radius").default(1000).notNull(), // radius in meters
|
||||
capacity: int("capacity").default(100).notNull(), // max beneficiaries
|
||||
contactName: varchar("contact_name", { length: 255 }),
|
||||
contactPhone: varchar("contact_phone", { length: 20 }),
|
||||
managerId: bigint("manager_id", { mode: "number", unsigned: true }),
|
||||
isActive: mysqlEnum("is_active", ["yes", "no"]).default("yes").notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull().$onUpdate(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
typeIdx: index("pow_type_idx").on(table.type),
|
||||
managerIdx: index("pow_manager_idx").on(table.managerId),
|
||||
})
|
||||
);
|
||||
|
||||
export type PlaceOfWorship = typeof placesOfWorship.$inferSelect;
|
||||
export type InsertPlaceOfWorship = typeof placesOfWorship.$inferInsert;
|
||||
|
||||
// ── Recipients (beneficiaries) ─────────────────────────────────
|
||||
export const recipients = mysqlTable(
|
||||
"recipients",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
nik: varchar("nik", { length: 16 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
birthDate: varchar("birth_date", { length: 10 }),
|
||||
gender: mysqlEnum("gender", ["male", "female"]),
|
||||
address: text("address"),
|
||||
phone: varchar("phone", { length: 15 }),
|
||||
latitude: decimal("latitude", { precision: 10, scale: 8 }),
|
||||
longitude: decimal("longitude", { precision: 11, scale: 8 }),
|
||||
familyMembers: int("family_members").default(1),
|
||||
incomePerMonth: int("income_per_month").default(0),
|
||||
status: mysqlEnum("status", ["pending", "active", "rejected", "suspended"]).default("pending").notNull(),
|
||||
rejectionReason: text("rejection_reason"),
|
||||
placeOfWorshipId: bigint("place_of_worship_id", { mode: "number", unsigned: true }).notNull(),
|
||||
registeredBy: bigint("registered_by", { mode: "number", unsigned: true }),
|
||||
verifiedBy: bigint("verified_by", { mode: "number", unsigned: true }),
|
||||
verifiedAt: timestamp("verified_at"),
|
||||
notes: text("notes"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull().$onUpdate(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
nikIdx: index("rec_nik_idx").on(table.nik),
|
||||
statusIdx: index("rec_status_idx").on(table.status),
|
||||
powIdx: index("rec_pow_idx").on(table.placeOfWorshipId),
|
||||
})
|
||||
);
|
||||
|
||||
export type Recipient = typeof recipients.$inferSelect;
|
||||
export type InsertRecipient = typeof recipients.$inferInsert;
|
||||
|
||||
// ── Distributions ──────────────────────────────────────────────
|
||||
export const distributions = mysqlTable(
|
||||
"distributions",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
placeOfWorshipId: bigint("place_of_worship_id", { mode: "number", unsigned: true }).notNull(),
|
||||
recipientId: bigint("recipient_id", { mode: "number", unsigned: true }).notNull(),
|
||||
distributedBy: bigint("distributed_by", { mode: "number", unsigned: true }),
|
||||
aidType: varchar("aid_type", { length: 100 }).notNull(),
|
||||
amount: decimal("amount", { precision: 12, scale: 2 }).default("0.00"),
|
||||
quantity: int("quantity").default(1),
|
||||
unit: varchar("unit", { length: 50 }).default("package"),
|
||||
distributionDate: timestamp("distribution_date").defaultNow().notNull(),
|
||||
notes: text("notes"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
powIdx: index("dist_pow_idx").on(table.placeOfWorshipId),
|
||||
recIdx: index("dist_rec_idx").on(table.recipientId),
|
||||
})
|
||||
);
|
||||
|
||||
export type Distribution = typeof distributions.$inferSelect;
|
||||
export type InsertDistribution = typeof distributions.$inferInsert;
|
||||
|
||||
// ── Activity Logs ──────────────────────────────────────────────
|
||||
export const activityLogs = mysqlTable(
|
||||
"activity_logs",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
userId: bigint("user_id", { mode: "number", unsigned: true }),
|
||||
action: varchar("action", { length: 50 }).notNull(), // e.g. "CREATE", "UPDATE", "DELETE", "VERIFY"
|
||||
entityType: varchar("entity_type", { length: 50 }).notNull(), // "recipient", "place_of_worship", "distribution"
|
||||
entityId: bigint("entity_id", { mode: "number", unsigned: true }),
|
||||
details: text("details"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
userIdx: index("act_user_idx").on(table.userId),
|
||||
entityIdx: index("act_entity_idx").on(table.entityType, table.entityId),
|
||||
})
|
||||
);
|
||||
|
||||
export type ActivityLog = typeof activityLogs.$inferSelect;
|
||||
export type InsertActivityLog = typeof activityLogs.$inferInsert;
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
import { getDb } from "../api/queries/connection";
|
||||
import { placesOfWorship, recipients, distributions, activityLogs } from "./schema";
|
||||
import { seedLocalUserIfMissing } from "../api/auth";
|
||||
|
||||
async function seed() {
|
||||
const db = getDb();
|
||||
await seedLocalUserIfMissing();
|
||||
console.log("Seeding database...");
|
||||
|
||||
// Seed Places of Worship
|
||||
const places = await db.insert(placesOfWorship).values([
|
||||
{
|
||||
name: "Masjid Al-Ikhlas",
|
||||
type: "mosque" as const,
|
||||
address: "Jl. Pahlawan No. 12, Kelurahan Cempaka, Kecamatan Cempaka Putih",
|
||||
latitude: "-6.1754",
|
||||
longitude: "106.8650",
|
||||
radius: 1500,
|
||||
capacity: 200,
|
||||
contactName: "Bapak Ahmad Surya",
|
||||
contactPhone: "081234567890",
|
||||
isActive: "yes",
|
||||
},
|
||||
{
|
||||
name: "Gereja Kristen Jawa",
|
||||
type: "church" as const,
|
||||
address: "Jl. Diponegoro No. 45, Kelurahan Menteng, Kecamatan Menteng",
|
||||
latitude: "-6.2008",
|
||||
longitude: "106.8320",
|
||||
radius: 1200,
|
||||
capacity: 150,
|
||||
contactName: "Ibu Maria Tan",
|
||||
contactPhone: "081987654321",
|
||||
isActive: "yes",
|
||||
},
|
||||
{
|
||||
name: "Pura Agung Jagatnatha",
|
||||
type: "temple" as const,
|
||||
address: "Jl. Nusantara No. 78, Kelurahan Denpasar Timur",
|
||||
latitude: "-8.6580",
|
||||
longitude: "115.2200",
|
||||
radius: 2000,
|
||||
capacity: 100,
|
||||
contactName: "I Made Wirata",
|
||||
contactPhone: "082233445566",
|
||||
isActive: "yes",
|
||||
},
|
||||
{
|
||||
name: "Vihara Dharma Bhakti",
|
||||
type: "vihara" as const,
|
||||
address: "Jl. Gajah Mada No. 88, Kelurahan Glodok, Jakarta Barat",
|
||||
latitude: "-6.1477",
|
||||
longitude: "106.8133",
|
||||
radius: 1000,
|
||||
capacity: 80,
|
||||
contactName: "Bapak Lim Swie Hian",
|
||||
contactPhone: "083344556677",
|
||||
isActive: "yes",
|
||||
},
|
||||
{
|
||||
name: "Masjid Nurul Huda",
|
||||
type: "mosque" as const,
|
||||
address: "Jl. Sudirman No. 25, Kelurahan Kuningan, Jakarta Selatan",
|
||||
latitude: "-6.2088",
|
||||
longitude: "106.8456",
|
||||
radius: 1800,
|
||||
capacity: 300,
|
||||
contactName: "Bapak Hadi Pranoto",
|
||||
contactPhone: "084455667788",
|
||||
isActive: "yes",
|
||||
},
|
||||
{
|
||||
name: "Gereja Katolik Santo Antonius",
|
||||
type: "church" as const,
|
||||
address: "Jl. Kartini No. 15, Kelurahan Yogyakarta Kota",
|
||||
latitude: "-7.8014",
|
||||
longitude: "110.3648",
|
||||
radius: 1300,
|
||||
capacity: 120,
|
||||
contactName: "Romo Yulianto",
|
||||
contactPhone: "085566778899",
|
||||
isActive: "yes",
|
||||
},
|
||||
]).$returningId();
|
||||
|
||||
console.log(`Seeded ${places.length} places of worship`);
|
||||
|
||||
// Seed Recipients
|
||||
const recs = await db.insert(recipients).values([
|
||||
{
|
||||
nik: "3175012345678901",
|
||||
name: "Budi Santoso",
|
||||
birthDate: "1975-05-15",
|
||||
gender: "male" as const,
|
||||
address: "Jl. Melati No. 3, RT 02/RW 05, Cempaka Putih",
|
||||
phone: "081111111111",
|
||||
latitude: "-6.1760",
|
||||
longitude: "106.8645",
|
||||
familyMembers: 4,
|
||||
incomePerMonth: 800000,
|
||||
status: "active" as const,
|
||||
placeOfWorshipId: 1,
|
||||
notes: "Pekerja serabutan, istri tidak bekerja",
|
||||
},
|
||||
{
|
||||
nik: "3175012345678902",
|
||||
name: "Siti Aminah",
|
||||
birthDate: "1980-12-20",
|
||||
gender: "female" as const,
|
||||
address: "Jl. Anggrek No. 7, RT 03/RW 02, Cempaka Putih",
|
||||
phone: "081222222222",
|
||||
latitude: "-6.1748",
|
||||
longitude: "106.8660",
|
||||
familyMembers: 3,
|
||||
incomePerMonth: 600000,
|
||||
status: "active" as const,
|
||||
placeOfWorshipId: 1,
|
||||
notes: "Janda dengan 2 anak sekolah",
|
||||
},
|
||||
{
|
||||
nik: "3175012345678903",
|
||||
name: "Ahmad Fauzi",
|
||||
birthDate: "1968-03-10",
|
||||
gender: "male" as const,
|
||||
address: "Jl. Mawar No. 12, RT 01/RW 08, Menteng",
|
||||
phone: "081333333333",
|
||||
latitude: "-6.2012",
|
||||
longitude: "106.8315",
|
||||
familyMembers: 5,
|
||||
incomePerMonth: 1000000,
|
||||
status: "active" as const,
|
||||
placeOfWorshipId: 2,
|
||||
notes: "Penjual nasi keliling",
|
||||
},
|
||||
{
|
||||
nik: "3175012345678904",
|
||||
name: "Dewi Kusuma",
|
||||
birthDate: "1990-08-25",
|
||||
gender: "female" as const,
|
||||
address: "Jl. Cemara No. 9, RT 04/RW 01, Menteng",
|
||||
phone: "081444444444",
|
||||
latitude: "-6.2002",
|
||||
longitude: "106.8330",
|
||||
familyMembers: 2,
|
||||
incomePerMonth: 700000,
|
||||
status: "pending" as const,
|
||||
placeOfWorshipId: 2,
|
||||
notes: "Pekerja cleaning service",
|
||||
},
|
||||
{
|
||||
nik: "5175012345678905",
|
||||
name: "I Wayan Suardana",
|
||||
birthDate: "1972-11-05",
|
||||
gender: "male" as const,
|
||||
address: "Jl. Raya Sanur No. 21, Denpasar Timur",
|
||||
phone: "082555555555",
|
||||
latitude: "-8.6575",
|
||||
longitude: "115.2195",
|
||||
familyMembers: 6,
|
||||
incomePerMonth: 1200000,
|
||||
status: "active" as const,
|
||||
placeOfWorshipId: 3,
|
||||
notes: "Petani subsisten",
|
||||
},
|
||||
{
|
||||
nik: "3175012345678906",
|
||||
name: "Rini Sulastri",
|
||||
birthDate: "1985-04-18",
|
||||
gender: "female" as const,
|
||||
address: "Jl. Pekojan No. 14, RT 02/RW 06, Glodok",
|
||||
phone: "083666666666",
|
||||
latitude: "-6.1472",
|
||||
longitude: "106.8140",
|
||||
familyMembers: 3,
|
||||
incomePerMonth: 500000,
|
||||
status: "pending" as const,
|
||||
placeOfWorshipId: 4,
|
||||
notes: "Penjual kue tradisional",
|
||||
},
|
||||
{
|
||||
nik: "3175012345678907",
|
||||
name: "Hendra Wijaya",
|
||||
birthDate: "1978-07-30",
|
||||
gender: "male" as const,
|
||||
address: "Jl. Karet No. 8, RT 03/RW 04, Kuningan",
|
||||
phone: "084777777777",
|
||||
latitude: "-6.2095",
|
||||
longitude: "106.8450",
|
||||
familyMembers: 4,
|
||||
incomePerMonth: 900000,
|
||||
status: "active" as const,
|
||||
placeOfWorshipId: 5,
|
||||
notes: "Sopir angkot",
|
||||
},
|
||||
{
|
||||
nik: "3475012345678908",
|
||||
name: "Yuliani",
|
||||
birthDate: "1982-09-12",
|
||||
gender: "female" as const,
|
||||
address: "Jl. Malioboro No. 33, RT 01/RW 02, Yogyakarta",
|
||||
phone: "085888888888",
|
||||
latitude: "-7.8020",
|
||||
longitude: "110.3655",
|
||||
familyMembers: 2,
|
||||
incomePerMonth: 650000,
|
||||
status: "active" as const,
|
||||
placeOfWorshipId: 6,
|
||||
notes: "Penjual oleh-oleh",
|
||||
},
|
||||
{
|
||||
nik: "3175012345678909",
|
||||
name: "Agus Priyanto",
|
||||
birthDate: "1965-01-22",
|
||||
gender: "male" as const,
|
||||
address: "Jl. Kenanga No. 5, RT 02/RW 07, Cempaka Putih",
|
||||
phone: "086999999999",
|
||||
latitude: "-6.1750",
|
||||
longitude: "106.8655",
|
||||
familyMembers: 3,
|
||||
incomePerMonth: 750000,
|
||||
status: "rejected" as const,
|
||||
placeOfWorshipId: 1,
|
||||
rejectionReason: "Memiliki usaha warung yang cukup stabil",
|
||||
notes: "Pemilik warung kecil",
|
||||
},
|
||||
{
|
||||
nik: "3175012345678910",
|
||||
name: "Maya Indah",
|
||||
birthDate: "1992-06-08",
|
||||
gender: "female" as const,
|
||||
address: "Jl. Flamboyan No. 11, RT 05/RW 03, Kuningan",
|
||||
phone: "087000000001",
|
||||
latitude: "-6.2078",
|
||||
longitude: "106.8465",
|
||||
familyMembers: 1,
|
||||
incomePerMonth: 850000,
|
||||
status: "pending" as const,
|
||||
placeOfWorshipId: 5,
|
||||
notes: "Mahasiswa yang tinggal sendiri",
|
||||
},
|
||||
]).$returningId();
|
||||
|
||||
console.log(`Seeded ${recs.length} recipients`);
|
||||
|
||||
// Seed Distributions
|
||||
const dists = await db.insert(distributions).values([
|
||||
{
|
||||
placeOfWorshipId: 1,
|
||||
recipientId: 1,
|
||||
aidType: "Sembako",
|
||||
amount: "350000",
|
||||
quantity: 2,
|
||||
unit: "paket",
|
||||
notes: "Paket beras, minyak goreng, gula, teh",
|
||||
},
|
||||
{
|
||||
placeOfWorshipId: 1,
|
||||
recipientId: 2,
|
||||
aidType: "Sembako",
|
||||
amount: "350000",
|
||||
quantity: 1,
|
||||
unit: "paket",
|
||||
notes: "Paket beras, minyak goreng, gula",
|
||||
},
|
||||
{
|
||||
placeOfWorshipId: 2,
|
||||
recipientId: 3,
|
||||
aidType: "Bantuan Tunai",
|
||||
amount: "500000",
|
||||
quantity: 1,
|
||||
unit: "kali",
|
||||
notes: "Bantuan langsung tunai",
|
||||
},
|
||||
{
|
||||
placeOfWorshipId: 3,
|
||||
recipientId: 5,
|
||||
aidType: "Sembako",
|
||||
amount: "400000",
|
||||
quantity: 2,
|
||||
unit: "paket",
|
||||
notes: "Paket besar untuk keluarga 6 orang",
|
||||
},
|
||||
{
|
||||
placeOfWorshipId: 4,
|
||||
recipientId: 6,
|
||||
aidType: "Modal Usaha",
|
||||
amount: "1000000",
|
||||
quantity: 1,
|
||||
unit: "kali",
|
||||
notes: "Modal untuk pengembangan usaha kue",
|
||||
},
|
||||
{
|
||||
placeOfWorshipId: 5,
|
||||
recipientId: 7,
|
||||
aidType: "Sembako",
|
||||
amount: "350000",
|
||||
quantity: 1,
|
||||
unit: "paket",
|
||||
notes: "Paket standar",
|
||||
},
|
||||
{
|
||||
placeOfWorshipId: 6,
|
||||
recipientId: 8,
|
||||
aidType: "Bantuan Pendidikan",
|
||||
amount: "750000",
|
||||
quantity: 1,
|
||||
unit: "kali",
|
||||
notes: "Bantuan biaya sekolah anak",
|
||||
},
|
||||
{
|
||||
placeOfWorshipId: 1,
|
||||
recipientId: 1,
|
||||
aidType: "Bantuan Tunai",
|
||||
amount: "600000",
|
||||
quantity: 1,
|
||||
unit: "kali",
|
||||
notes: "Bantuan bulanan kedua",
|
||||
},
|
||||
]).$returningId();
|
||||
|
||||
console.log(`Seeded ${dists.length} distributions`);
|
||||
|
||||
// Seed Activity Logs
|
||||
await db.insert(activityLogs).values([
|
||||
{ action: "CREATE", entityType: "recipient", entityId: 1, details: "Mendaftarkan Budi Santoso" },
|
||||
{ action: "CREATE", entityType: "recipient", entityId: 2, details: "Mendaftarkan Siti Aminah" },
|
||||
{ action: "VERIFY", entityType: "recipient", entityId: 1, details: "Memverifikasi Budi Santoso - Diterima" },
|
||||
{ action: "VERIFY", entityType: "recipient", entityId: 2, details: "Memverifikasi Siti Aminah - Diterima" },
|
||||
{ action: "CREATE", entityType: "distribution", entityId: 1, details: "Distribusi sembako ke Budi Santoso" },
|
||||
{ action: "CREATE", entityType: "recipient", entityId: 4, details: "Mendaftarkan Dewi Kusuma" },
|
||||
{ action: "CREATE", entityType: "recipient", entityId: 9, details: "Mendaftarkan Agus Priyanto" },
|
||||
{ action: "VERIFY", entityType: "recipient", entityId: 9, details: "Memverifikasi Agus Priyanto - Ditolak" },
|
||||
{ action: "CREATE", entityType: "place_of_worship", entityId: 5, details: "Menambahkan Masjid Nurul Huda" },
|
||||
{ action: "CREATE", entityType: "recipient", entityId: 10, details: "Mendaftarkan Maya Indah" },
|
||||
]);
|
||||
|
||||
console.log("Seeded activity logs");
|
||||
console.log("Seed complete!");
|
||||
}
|
||||
|
||||
seed().catch(console.error);
|
||||
Reference in New Issue
Block a user