auth init
This commit is contained in:
parent
3fd769a36f
commit
51bfb1513a
29 changed files with 1606 additions and 50 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/bcryptjs": "^3.0.0",
|
||||||
"@types/bun": "latest"
|
"@types/bun": "latest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
@ -14,8 +15,10 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@trpc/server": "^11.6.0",
|
"@trpc/server": "^11.6.0",
|
||||||
|
"bcryptjs": "^3.0.3",
|
||||||
"data-manager-sqlite": "*",
|
"data-manager-sqlite": "*",
|
||||||
"hono": "^4.12.18",
|
"hono": "^4.12.18",
|
||||||
|
"jose": "^6.2.3",
|
||||||
"zod": "^3.25.0"
|
"zod": "^3.25.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,51 @@
|
||||||
import { Hono } from 'hono'
|
import { Hono } from "hono";
|
||||||
import { cors } from 'hono/cors'
|
import { cors } from "hono/cors";
|
||||||
import { env } from './lib/env-exporter'
|
import { env } from "./lib/env-exporter";
|
||||||
import { appRouter } from './trpc/router'
|
import { appRouter } from "./trpc/router";
|
||||||
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
|
import { createContext } from "./trpc/context";
|
||||||
|
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||||
|
|
||||||
const app = new Hono()
|
const app = new Hono();
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
'*',
|
"*",
|
||||||
cors({
|
cors({
|
||||||
origin: (origin) => {
|
origin: (origin) => {
|
||||||
// Allow local dev UIs.
|
if (origin === "http://localhost:3000") return origin;
|
||||||
if (origin === 'http://localhost:3000') return origin
|
if (origin === "http://localhost:3001") return origin;
|
||||||
if (origin === 'http://localhost:3001') return origin
|
if (!origin) return "*";
|
||||||
// Non-browser clients (no Origin header)
|
return null;
|
||||||
if (!origin) return '*'
|
},
|
||||||
return null
|
allowHeaders: ["content-type"],
|
||||||
},
|
allowMethods: ["GET", "POST", "OPTIONS"],
|
||||||
allowHeaders: ['content-type'],
|
credentials: true,
|
||||||
allowMethods: ['GET', 'POST', 'OPTIONS'],
|
}),
|
||||||
}),
|
);
|
||||||
)
|
|
||||||
|
|
||||||
app.get('/health', (c) => c.json({ ok: true }))
|
app.get("/health", (c) => c.json({ ok: true }));
|
||||||
|
|
||||||
app.all('/trpc/*', (c) =>
|
app.all("/trpc/*", async (c) => {
|
||||||
fetchRequestHandler({
|
const resHeaders = new Headers();
|
||||||
endpoint: '/trpc',
|
const response = await fetchRequestHandler({
|
||||||
req: c.req.raw,
|
endpoint: "/trpc",
|
||||||
router: appRouter,
|
req: c.req.raw,
|
||||||
}),
|
router: appRouter,
|
||||||
)
|
createContext: () => createContext({ req: c.req.raw, resHeaders }),
|
||||||
|
});
|
||||||
|
|
||||||
export { app }
|
resHeaders.forEach((value, key) => {
|
||||||
|
response.headers.set(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
const port = Number(env.PORT || 4004)
|
return response;
|
||||||
|
});
|
||||||
|
|
||||||
|
export { app };
|
||||||
|
|
||||||
|
const port = Number(env.PORT || 4004);
|
||||||
Bun.serve({
|
Bun.serve({
|
||||||
port,
|
port,
|
||||||
fetch: app.fetch,
|
fetch: app.fetch,
|
||||||
})
|
});
|
||||||
|
|
||||||
console.log(`Backend listening on http://localhost:${port}`)
|
console.log(`Backend listening on http://localhost:${port}`);
|
||||||
|
|
|
||||||
31
apps/backend/src/lib/auth.ts
Normal file
31
apps/backend/src/lib/auth.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { SignJWT, jwtVerify } from "jose";
|
||||||
|
|
||||||
|
const JWT_SECRET = new TextEncoder().encode(
|
||||||
|
process.env.JWT_SECRET || "pharmanager-dev-secret-change-me",
|
||||||
|
);
|
||||||
|
|
||||||
|
const COOKIE_NAME = "pharmanager_token";
|
||||||
|
const EXPIRY = "24h";
|
||||||
|
|
||||||
|
export interface JWTPayload {
|
||||||
|
staffId: number;
|
||||||
|
enterpriseId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signJWT(payload: JWTPayload): Promise<string> {
|
||||||
|
return new SignJWT({ ...payload })
|
||||||
|
.setProtectedHeader({ alg: "HS256" })
|
||||||
|
.setExpirationTime(EXPIRY)
|
||||||
|
.sign(JWT_SECRET);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifyJWT(token: string): Promise<JWTPayload | null> {
|
||||||
|
try {
|
||||||
|
const { payload } = await jwtVerify(token, JWT_SECRET);
|
||||||
|
return payload as unknown as JWTPayload;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { COOKIE_NAME };
|
||||||
|
|
@ -5,12 +5,18 @@ import {
|
||||||
createDrugInfoRepo,
|
createDrugInfoRepo,
|
||||||
createUnitsRepo,
|
createUnitsRepo,
|
||||||
createStockBatchesRepo,
|
createStockBatchesRepo,
|
||||||
|
createEnterpriseRepo,
|
||||||
|
createStaffRepo,
|
||||||
|
createEnterpriseStaffRepo,
|
||||||
type StorageSpacesRepo,
|
type StorageSpacesRepo,
|
||||||
type DistributorsRepo,
|
type DistributorsRepo,
|
||||||
type ProductsRepo,
|
type ProductsRepo,
|
||||||
type DrugInfoRepo,
|
type DrugInfoRepo,
|
||||||
type UnitsRepo,
|
type UnitsRepo,
|
||||||
type StockBatchesRepo,
|
type StockBatchesRepo,
|
||||||
|
type EnterpriseRepo,
|
||||||
|
type StaffRepo,
|
||||||
|
type EnterpriseStaffRepo,
|
||||||
} from "data-manager-sqlite";
|
} from "data-manager-sqlite";
|
||||||
|
|
||||||
export class DataManager {
|
export class DataManager {
|
||||||
|
|
@ -20,6 +26,9 @@ export class DataManager {
|
||||||
readonly drugInfo: DrugInfoRepo;
|
readonly drugInfo: DrugInfoRepo;
|
||||||
readonly units: UnitsRepo;
|
readonly units: UnitsRepo;
|
||||||
readonly stockBatches: StockBatchesRepo;
|
readonly stockBatches: StockBatchesRepo;
|
||||||
|
readonly enterprises: EnterpriseRepo;
|
||||||
|
readonly staff: StaffRepo;
|
||||||
|
readonly enterpriseStaff: EnterpriseStaffRepo;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const { repo: storageSpacesRepo } = createStorageSpacesRepo();
|
const { repo: storageSpacesRepo } = createStorageSpacesRepo();
|
||||||
|
|
@ -28,6 +37,9 @@ export class DataManager {
|
||||||
const { repo: drugInfoRepo } = createDrugInfoRepo();
|
const { repo: drugInfoRepo } = createDrugInfoRepo();
|
||||||
const { repo: unitsRepo } = createUnitsRepo();
|
const { repo: unitsRepo } = createUnitsRepo();
|
||||||
const { repo: stockBatchesRepo } = createStockBatchesRepo();
|
const { repo: stockBatchesRepo } = createStockBatchesRepo();
|
||||||
|
const { repo: enterpriseRepo } = createEnterpriseRepo();
|
||||||
|
const { repo: staffRepo } = createStaffRepo();
|
||||||
|
const { repo: enterpriseStaffRepo } = createEnterpriseStaffRepo();
|
||||||
|
|
||||||
this.storageSpaces = storageSpacesRepo;
|
this.storageSpaces = storageSpacesRepo;
|
||||||
this.distributors = distributorsRepo;
|
this.distributors = distributorsRepo;
|
||||||
|
|
@ -35,5 +47,8 @@ export class DataManager {
|
||||||
this.drugInfo = drugInfoRepo;
|
this.drugInfo = drugInfoRepo;
|
||||||
this.units = unitsRepo;
|
this.units = unitsRepo;
|
||||||
this.stockBatches = stockBatchesRepo;
|
this.stockBatches = stockBatchesRepo;
|
||||||
|
this.enterprises = enterpriseRepo;
|
||||||
|
this.staff = staffRepo;
|
||||||
|
this.enterpriseStaff = enterpriseStaffRepo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
apps/backend/src/trpc/context.ts
Normal file
26
apps/backend/src/trpc/context.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { COOKIE_NAME, verifyJWT, type JWTPayload } from "../lib/auth";
|
||||||
|
|
||||||
|
export interface Context {
|
||||||
|
req: Request;
|
||||||
|
resHeaders: Headers;
|
||||||
|
staff?: JWTPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createContext(opts: {
|
||||||
|
req: Request;
|
||||||
|
resHeaders: Headers;
|
||||||
|
}): Promise<Context> {
|
||||||
|
const cookieHeader = opts.req.headers.get("cookie") || "";
|
||||||
|
const token = cookieHeader
|
||||||
|
.split("; ")
|
||||||
|
.find((c) => c.startsWith(`${COOKIE_NAME}=`))
|
||||||
|
?.split("=")[1];
|
||||||
|
|
||||||
|
const staff = token ? (await verifyJWT(token)) ?? undefined : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
req: opts.req,
|
||||||
|
resHeaders: opts.resHeaders,
|
||||||
|
staff,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,16 @@
|
||||||
import { initTRPC } from "@trpc/server";
|
import { initTRPC, TRPCError } from "@trpc/server";
|
||||||
|
import type { Context } from "./context";
|
||||||
|
|
||||||
|
const t = initTRPC.context<Context>().create();
|
||||||
|
|
||||||
|
export const router = t.router;
|
||||||
|
export const publicProcedure = t.procedure;
|
||||||
|
|
||||||
|
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
|
||||||
|
if (!ctx.staff) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
return next({ ctx: ctx as Context & { staff: NonNullable<Context["staff"]> } });
|
||||||
|
});
|
||||||
|
|
||||||
const t = initTRPC.create();
|
|
||||||
export { t };
|
export { t };
|
||||||
|
|
|
||||||
75
apps/backend/src/trpc/pharmanager/v1/auth.ts
Normal file
75
apps/backend/src/trpc/pharmanager/v1/auth.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
import { COOKIE_NAME, signJWT } from "../../../lib/auth";
|
||||||
|
import { dataManager } from "../../../lib/data-manager-instance";
|
||||||
|
import { publicProcedure, protectedProcedure, router } from "../../init";
|
||||||
|
|
||||||
|
export const authRouter = router({
|
||||||
|
login: publicProcedure
|
||||||
|
.input(z.object({ username: z.string(), password: z.string() }))
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const staff = await dataManager.staff.getStaffByUsername(
|
||||||
|
input.username,
|
||||||
|
);
|
||||||
|
if (!staff) {
|
||||||
|
throw new Error("Invalid username or password");
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = await dataManager.staff.verifyPassword(
|
||||||
|
staff,
|
||||||
|
input.password,
|
||||||
|
);
|
||||||
|
if (!valid) {
|
||||||
|
throw new Error("Invalid username or password");
|
||||||
|
}
|
||||||
|
|
||||||
|
const enterpriseStaff = dataManager.enterpriseStaff.getByStaffId(
|
||||||
|
staff.id,
|
||||||
|
);
|
||||||
|
if (!enterpriseStaff) {
|
||||||
|
throw new Error("No enterprise assigned");
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await signJWT({
|
||||||
|
staffId: staff.id,
|
||||||
|
enterpriseId: enterpriseStaff.enterprise_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.resHeaders.set(
|
||||||
|
"Set-Cookie",
|
||||||
|
`${COOKIE_NAME}=${token}; HttpOnly; Path=/; Max-Age=86400; SameSite=Lax`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
staff: {
|
||||||
|
id: staff.id,
|
||||||
|
name: staff.name,
|
||||||
|
username: staff.username,
|
||||||
|
is_password_reset_needed: staff.is_password_reset_needed,
|
||||||
|
},
|
||||||
|
enterprise_id: enterpriseStaff.enterprise_id,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
whoAmI: protectedProcedure.query(async ({ ctx }) => {
|
||||||
|
const staff = await dataManager.staff.getStaffById(ctx.staff.staffId);
|
||||||
|
if (!staff) {
|
||||||
|
throw new Error("Staff not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const enterprise = await dataManager.enterprises.getEnterpriseById(
|
||||||
|
ctx.staff.enterpriseId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
staff: {
|
||||||
|
id: staff.id,
|
||||||
|
name: staff.name,
|
||||||
|
username: staff.username,
|
||||||
|
email: staff.email,
|
||||||
|
mobile: staff.mobile,
|
||||||
|
is_password_reset_needed: staff.is_password_reset_needed,
|
||||||
|
},
|
||||||
|
enterprise,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
import { t } from "./init";
|
import { router } from "./init";
|
||||||
import { storageRouter } from "./pharmanager/v1/storage";
|
import { storageRouter } from "./pharmanager/v1/storage";
|
||||||
import { distributorRouter } from "./pharmanager/v1/distributor";
|
import { distributorRouter } from "./pharmanager/v1/distributor";
|
||||||
import { productRouter } from "./pharmanager/v1/product";
|
import { productRouter } from "./pharmanager/v1/product";
|
||||||
import { drugInfoRouter } from "./pharmanager/v1/drugInfo";
|
import { drugInfoRouter } from "./pharmanager/v1/drugInfo";
|
||||||
import { unitsRouter } from "./pharmanager/v1/units";
|
import { unitsRouter } from "./pharmanager/v1/units";
|
||||||
import { stockRouter } from "./pharmanager/v1/stock";
|
import { stockRouter } from "./pharmanager/v1/stock";
|
||||||
|
import { authRouter } from "./pharmanager/v1/auth";
|
||||||
|
|
||||||
export const appRouter = t.router({
|
export const appRouter = router({
|
||||||
storage: storageRouter,
|
storage: storageRouter,
|
||||||
distributor: distributorRouter,
|
distributor: distributorRouter,
|
||||||
product: productRouter,
|
product: productRouter,
|
||||||
drugInfo: drugInfoRouter,
|
drugInfo: drugInfoRouter,
|
||||||
units: unitsRouter,
|
units: unitsRouter,
|
||||||
stock: stockRouter,
|
stock: stockRouter,
|
||||||
|
auth: authRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter;
|
export type AppRouter = typeof appRouter;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
CREATE TABLE `enterprises` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`type` text NOT NULL,
|
||||||
|
`owner_name` text NOT NULL,
|
||||||
|
`mobile` text NOT NULL,
|
||||||
|
`address` text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `staff` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`username` text NOT NULL,
|
||||||
|
`email` text,
|
||||||
|
`mobile` text,
|
||||||
|
`added_on` text NOT NULL,
|
||||||
|
`password` text NOT NULL,
|
||||||
|
`is_password_reset_needed` integer DEFAULT true NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `staff_username_unique` ON `staff` (`username`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `roles` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`name` text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `roles_name_unique` ON `roles` (`name`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `permissions` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`name` text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `permissions_name_unique` ON `permissions` (`name`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `role_permissions` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`role_id` integer NOT NULL,
|
||||||
|
`permission_id` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
|
FOREIGN KEY (`permission_id`) REFERENCES `permissions`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `staff_roles` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`staff_id` integer NOT NULL,
|
||||||
|
`role_id` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`staff_id`) REFERENCES `staff`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
|
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `enterprise_staff` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`staff_id` integer NOT NULL,
|
||||||
|
`enterprise_id` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`staff_id`) REFERENCES `staff`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
|
FOREIGN KEY (`enterprise_id`) REFERENCES `enterprises`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE `storage_spaces` ADD `enterprise_id` integer REFERENCES enterprises(id);--> statement-breakpoint
|
||||||
|
CREATE INDEX `storage_spaces_enterprise_idx` ON `storage_spaces` (`enterprise_id`);--> statement-breakpoint
|
||||||
|
ALTER TABLE `distributors` ADD `enterprise_id` integer REFERENCES enterprises(id);--> statement-breakpoint
|
||||||
|
CREATE INDEX `distributors_enterprise_idx` ON `distributors` (`enterprise_id`);--> statement-breakpoint
|
||||||
|
ALTER TABLE `products` ADD `enterprise_id` integer REFERENCES enterprises(id);--> statement-breakpoint
|
||||||
|
CREATE INDEX `products_enterprise_idx` ON `products` (`enterprise_id`);--> statement-breakpoint
|
||||||
|
ALTER TABLE `stock_batches` ADD `enterprise_id` integer REFERENCES enterprises(id);--> statement-breakpoint
|
||||||
|
CREATE INDEX `stock_batches_enterprise_idx` ON `stock_batches` (`enterprise_id`);
|
||||||
999
packages/data-manager-sqlite/drizzle/meta/0005_snapshot.json
Normal file
999
packages/data-manager-sqlite/drizzle/meta/0005_snapshot.json
Normal file
|
|
@ -0,0 +1,999 @@
|
||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "4ebc4f18-552b-459a-abef-dc2dfe600cd2",
|
||||||
|
"prevId": "dfa18399-c3f0-4ef0-896b-632b38f67c1f",
|
||||||
|
"tables": {
|
||||||
|
"storage_spaces": {
|
||||||
|
"name": "storage_spaces",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"name": "aliases",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"image_urls": {
|
||||||
|
"name": "image_urls",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"enterprise_id": {
|
||||||
|
"name": "enterprise_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"storage_spaces_enterprise_idx": {
|
||||||
|
"name": "storage_spaces_enterprise_idx",
|
||||||
|
"columns": [
|
||||||
|
"enterprise_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"storage_spaces_enterprise_id_enterprises_id_fk": {
|
||||||
|
"name": "storage_spaces_enterprise_id_enterprises_id_fk",
|
||||||
|
"tableFrom": "storage_spaces",
|
||||||
|
"tableTo": "enterprises",
|
||||||
|
"columnsFrom": [
|
||||||
|
"enterprise_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"distributors": {
|
||||||
|
"name": "distributors",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"agency": {
|
||||||
|
"name": "agency",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"name": "contact",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"mobile": {
|
||||||
|
"name": "mobile",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"name": "address",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enterprise_id": {
|
||||||
|
"name": "enterprise_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"distributors_enterprise_idx": {
|
||||||
|
"name": "distributors_enterprise_idx",
|
||||||
|
"columns": [
|
||||||
|
"enterprise_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"distributors_enterprise_id_enterprises_id_fk": {
|
||||||
|
"name": "distributors_enterprise_id_enterprises_id_fk",
|
||||||
|
"tableFrom": "distributors",
|
||||||
|
"tableTo": "enterprises",
|
||||||
|
"columnsFrom": [
|
||||||
|
"enterprise_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"products": {
|
||||||
|
"name": "products",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"brand": {
|
||||||
|
"name": "brand",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"name": "category",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"distributor_id": {
|
||||||
|
"name": "distributor_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"unit_id": {
|
||||||
|
"name": "unit_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"procured_price": {
|
||||||
|
"name": "procured_price",
|
||||||
|
"type": "real",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"mrp": {
|
||||||
|
"name": "mrp",
|
||||||
|
"type": "real",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"selling_price": {
|
||||||
|
"name": "selling_price",
|
||||||
|
"type": "real",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"name": "size",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"reorder_level": {
|
||||||
|
"name": "reorder_level",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"units_per_strip": {
|
||||||
|
"name": "units_per_strip",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"hide_product_from_public": {
|
||||||
|
"name": "hide_product_from_public",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"hide_price_from_public": {
|
||||||
|
"name": "hide_price_from_public",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"enterprise_id": {
|
||||||
|
"name": "enterprise_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"products_enterprise_idx": {
|
||||||
|
"name": "products_enterprise_idx",
|
||||||
|
"columns": [
|
||||||
|
"enterprise_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"products_distributor_id_distributors_id_fk": {
|
||||||
|
"name": "products_distributor_id_distributors_id_fk",
|
||||||
|
"tableFrom": "products",
|
||||||
|
"tableTo": "distributors",
|
||||||
|
"columnsFrom": [
|
||||||
|
"distributor_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"products_unit_id_units_id_fk": {
|
||||||
|
"name": "products_unit_id_units_id_fk",
|
||||||
|
"tableFrom": "products",
|
||||||
|
"tableTo": "units",
|
||||||
|
"columnsFrom": [
|
||||||
|
"unit_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"products_enterprise_id_enterprises_id_fk": {
|
||||||
|
"name": "products_enterprise_id_enterprises_id_fk",
|
||||||
|
"tableFrom": "products",
|
||||||
|
"tableTo": "enterprises",
|
||||||
|
"columnsFrom": [
|
||||||
|
"enterprise_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"drug_info": {
|
||||||
|
"name": "drug_info",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"drug_info_name_unique": {
|
||||||
|
"name": "drug_info_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"units": {
|
||||||
|
"name": "units",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"units_name_unique": {
|
||||||
|
"name": "units_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"product_compositions": {
|
||||||
|
"name": "product_compositions",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"product_id": {
|
||||||
|
"name": "product_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"drug_info_id": {
|
||||||
|
"name": "drug_info_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"name": "quantity",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"unit_id": {
|
||||||
|
"name": "unit_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"product_compositions_product_id_products_id_fk": {
|
||||||
|
"name": "product_compositions_product_id_products_id_fk",
|
||||||
|
"tableFrom": "product_compositions",
|
||||||
|
"tableTo": "products",
|
||||||
|
"columnsFrom": [
|
||||||
|
"product_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"product_compositions_drug_info_id_drug_info_id_fk": {
|
||||||
|
"name": "product_compositions_drug_info_id_drug_info_id_fk",
|
||||||
|
"tableFrom": "product_compositions",
|
||||||
|
"tableTo": "drug_info",
|
||||||
|
"columnsFrom": [
|
||||||
|
"drug_info_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"product_compositions_unit_id_units_id_fk": {
|
||||||
|
"name": "product_compositions_unit_id_units_id_fk",
|
||||||
|
"tableFrom": "product_compositions",
|
||||||
|
"tableTo": "units",
|
||||||
|
"columnsFrom": [
|
||||||
|
"unit_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"stock_batches": {
|
||||||
|
"name": "stock_batches",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"product_id": {
|
||||||
|
"name": "product_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"arrived": {
|
||||||
|
"name": "arrived",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"batch_no": {
|
||||||
|
"name": "batch_no",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"mfg": {
|
||||||
|
"name": "mfg",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expiry": {
|
||||||
|
"name": "expiry",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"rack_id": {
|
||||||
|
"name": "rack_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"distributor_id": {
|
||||||
|
"name": "distributor_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"name": "quantity",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"is_default": {
|
||||||
|
"name": "is_default",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"enterprise_id": {
|
||||||
|
"name": "enterprise_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"stock_batches_enterprise_idx": {
|
||||||
|
"name": "stock_batches_enterprise_idx",
|
||||||
|
"columns": [
|
||||||
|
"enterprise_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"stock_batches_product_id_products_id_fk": {
|
||||||
|
"name": "stock_batches_product_id_products_id_fk",
|
||||||
|
"tableFrom": "stock_batches",
|
||||||
|
"tableTo": "products",
|
||||||
|
"columnsFrom": [
|
||||||
|
"product_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"stock_batches_rack_id_storage_spaces_id_fk": {
|
||||||
|
"name": "stock_batches_rack_id_storage_spaces_id_fk",
|
||||||
|
"tableFrom": "stock_batches",
|
||||||
|
"tableTo": "storage_spaces",
|
||||||
|
"columnsFrom": [
|
||||||
|
"rack_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"stock_batches_distributor_id_distributors_id_fk": {
|
||||||
|
"name": "stock_batches_distributor_id_distributors_id_fk",
|
||||||
|
"tableFrom": "stock_batches",
|
||||||
|
"tableTo": "distributors",
|
||||||
|
"columnsFrom": [
|
||||||
|
"distributor_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"stock_batches_enterprise_id_enterprises_id_fk": {
|
||||||
|
"name": "stock_batches_enterprise_id_enterprises_id_fk",
|
||||||
|
"tableFrom": "stock_batches",
|
||||||
|
"tableTo": "enterprises",
|
||||||
|
"columnsFrom": [
|
||||||
|
"enterprise_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"enterprises": {
|
||||||
|
"name": "enterprises",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"owner_name": {
|
||||||
|
"name": "owner_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"mobile": {
|
||||||
|
"name": "mobile",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"name": "address",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"staff": {
|
||||||
|
"name": "staff",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"mobile": {
|
||||||
|
"name": "mobile",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"added_on": {
|
||||||
|
"name": "added_on",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "password",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"is_password_reset_needed": {
|
||||||
|
"name": "is_password_reset_needed",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"staff_username_unique": {
|
||||||
|
"name": "staff_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"name": "roles",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"roles_name_unique": {
|
||||||
|
"name": "roles_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"name": "permissions",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"permissions_name_unique": {
|
||||||
|
"name": "permissions_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"role_permissions": {
|
||||||
|
"name": "role_permissions",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"role_id": {
|
||||||
|
"name": "role_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"permission_id": {
|
||||||
|
"name": "permission_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"role_permissions_role_id_roles_id_fk": {
|
||||||
|
"name": "role_permissions_role_id_roles_id_fk",
|
||||||
|
"tableFrom": "role_permissions",
|
||||||
|
"tableTo": "roles",
|
||||||
|
"columnsFrom": [
|
||||||
|
"role_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"role_permissions_permission_id_permissions_id_fk": {
|
||||||
|
"name": "role_permissions_permission_id_permissions_id_fk",
|
||||||
|
"tableFrom": "role_permissions",
|
||||||
|
"tableTo": "permissions",
|
||||||
|
"columnsFrom": [
|
||||||
|
"permission_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"staff_roles": {
|
||||||
|
"name": "staff_roles",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"staff_id": {
|
||||||
|
"name": "staff_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"role_id": {
|
||||||
|
"name": "role_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"staff_roles_staff_id_staff_id_fk": {
|
||||||
|
"name": "staff_roles_staff_id_staff_id_fk",
|
||||||
|
"tableFrom": "staff_roles",
|
||||||
|
"tableTo": "staff",
|
||||||
|
"columnsFrom": [
|
||||||
|
"staff_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"staff_roles_role_id_roles_id_fk": {
|
||||||
|
"name": "staff_roles_role_id_roles_id_fk",
|
||||||
|
"tableFrom": "staff_roles",
|
||||||
|
"tableTo": "roles",
|
||||||
|
"columnsFrom": [
|
||||||
|
"role_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"enterprise_staff": {
|
||||||
|
"name": "enterprise_staff",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"staff_id": {
|
||||||
|
"name": "staff_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enterprise_id": {
|
||||||
|
"name": "enterprise_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"enterprise_staff_staff_id_staff_id_fk": {
|
||||||
|
"name": "enterprise_staff_staff_id_staff_id_fk",
|
||||||
|
"tableFrom": "enterprise_staff",
|
||||||
|
"tableTo": "staff",
|
||||||
|
"columnsFrom": [
|
||||||
|
"staff_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"enterprise_staff_enterprise_id_enterprises_id_fk": {
|
||||||
|
"name": "enterprise_staff_enterprise_id_enterprises_id_fk",
|
||||||
|
"tableFrom": "enterprise_staff",
|
||||||
|
"tableTo": "enterprises",
|
||||||
|
"columnsFrom": [
|
||||||
|
"enterprise_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,13 @@
|
||||||
"when": 1779533139096,
|
"when": 1779533139096,
|
||||||
"tag": "0004_ambiguous_captain_america",
|
"tag": "0004_ambiguous_captain_america",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1779546674621,
|
||||||
|
"tag": "0005_yielding_silver_fox",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -11,12 +11,14 @@
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bcryptjs": "^3.0.3",
|
||||||
"drizzle-orm": "^0.44.5"
|
"drizzle-orm": "^0.44.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"drizzle-kit": "^0.31.4",
|
"@types/bcryptjs": "^3.0.0",
|
||||||
"@types/node": "^22.10.2",
|
|
||||||
"@types/bun": "^1.3.14",
|
"@types/bun": "^1.3.14",
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"drizzle-kit": "^0.31.4",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,56 @@
|
||||||
|
import bcrypt from 'bcryptjs'
|
||||||
import { createDb } from './db'
|
import { createDb } from './db'
|
||||||
import { runMigrations } from './migrate'
|
import { runMigrations } from './migrate'
|
||||||
|
|
||||||
const { db, sqlite } = createDb()
|
const { db, sqlite } = createDb()
|
||||||
runMigrations(sqlite)
|
runMigrations(sqlite)
|
||||||
|
|
||||||
// Seed reference data
|
// Seed reference data — units
|
||||||
sqlite.run("INSERT OR IGNORE INTO units (name) VALUES ('mg')")
|
sqlite.run("INSERT OR IGNORE INTO units (name) VALUES ('mg')")
|
||||||
sqlite.run("INSERT OR IGNORE INTO units (name) VALUES ('gm')")
|
sqlite.run("INSERT OR IGNORE INTO units (name) VALUES ('gm')")
|
||||||
sqlite.run("INSERT OR IGNORE INTO units (name) VALUES ('ml')")
|
sqlite.run("INSERT OR IGNORE INTO units (name) VALUES ('ml')")
|
||||||
sqlite.run("INSERT OR IGNORE INTO units (name) VALUES ('piece')")
|
sqlite.run("INSERT OR IGNORE INTO units (name) VALUES ('piece')")
|
||||||
|
|
||||||
|
// Seed enterprise
|
||||||
|
sqlite.run("INSERT OR IGNORE INTO enterprises (id, name, type, owner_name, mobile) VALUES (1, 'Main Pharmacy', 'Pharmacy', 'Admin User', '0000000000')")
|
||||||
|
|
||||||
|
// Seed staff (admin user)
|
||||||
|
const today = new Date().toISOString().split('T')[0]
|
||||||
|
const adminHash = bcrypt.hashSync('admin123', 10)
|
||||||
|
sqlite.run(
|
||||||
|
"INSERT OR IGNORE INTO staff (id, name, username, password, added_on, is_password_reset_needed) VALUES (1, 'Admin', 'admin', ?, ?, 1)",
|
||||||
|
[adminHash, today],
|
||||||
|
)
|
||||||
|
|
||||||
|
// Link staff to enterprise
|
||||||
|
sqlite.run("INSERT OR IGNORE INTO enterprise_staff (id, staff_id, enterprise_id) VALUES (1, 1, 1)")
|
||||||
|
|
||||||
|
// Seed roles
|
||||||
|
sqlite.run("INSERT OR IGNORE INTO roles (id, name) VALUES (1, 'Admin')")
|
||||||
|
sqlite.run("INSERT OR IGNORE INTO roles (id, name) VALUES (2, 'Pharmacist')")
|
||||||
|
sqlite.run("INSERT OR IGNORE INTO roles (id, name) VALUES (3, 'Manager')")
|
||||||
|
sqlite.run("INSERT OR IGNORE INTO roles (id, name) VALUES (4, 'Cashier')")
|
||||||
|
|
||||||
|
// Seed permissions
|
||||||
|
const permNames = [
|
||||||
|
'product.read', 'product.write',
|
||||||
|
'stock.read', 'stock.write',
|
||||||
|
'distributor.read', 'distributor.write',
|
||||||
|
'storage.read', 'storage.write',
|
||||||
|
'billing.read', 'billing.write',
|
||||||
|
'staff.read', 'staff.write',
|
||||||
|
'customer.read', 'customer.write',
|
||||||
|
]
|
||||||
|
for (let i = 0; i < permNames.length; i++) {
|
||||||
|
sqlite.run(`INSERT OR IGNORE INTO permissions (id, name) VALUES (${i + 1}, '${permNames[i]}')`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed role_permissions — Admin gets all
|
||||||
|
for (let i = 1; i <= permNames.length; i++) {
|
||||||
|
sqlite.run(`INSERT OR IGNORE INTO role_permissions (role_id, permission_id) VALUES (1, ${i})`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed staff_roles — admin gets Admin role
|
||||||
|
sqlite.run("INSERT OR IGNORE INTO staff_roles (staff_id, role_id) VALUES (1, 1)")
|
||||||
|
|
||||||
export { db, sqlite }
|
export { db, sqlite }
|
||||||
|
|
|
||||||
42
packages/data-manager-sqlite/src/enterpriseStaff.ts
Normal file
42
packages/data-manager-sqlite/src/enterpriseStaff.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { eq } from 'drizzle-orm'
|
||||||
|
|
||||||
|
import { db } from './db-instance'
|
||||||
|
import { enterpriseStaff } from './schema/enterpriseStaff'
|
||||||
|
|
||||||
|
export type EnterpriseStaff = {
|
||||||
|
id: number
|
||||||
|
staff_id: number
|
||||||
|
enterprise_id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EnterpriseStaffRepo = {
|
||||||
|
getByStaffId: (staffId: number) => EnterpriseStaff | null
|
||||||
|
create: (input: { staff_id: number; enterprise_id: number }) => EnterpriseStaff
|
||||||
|
}
|
||||||
|
|
||||||
|
function toEnterpriseStaff(row: typeof enterpriseStaff.$inferSelect): EnterpriseStaff {
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
staff_id: row.staffId,
|
||||||
|
enterprise_id: row.enterpriseId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEnterpriseStaffRepo(): { repo: EnterpriseStaffRepo } {
|
||||||
|
const repo: EnterpriseStaffRepo = {
|
||||||
|
getByStaffId(staffId) {
|
||||||
|
const row = db.select().from(enterpriseStaff).where(eq(enterpriseStaff.staffId, staffId)).get()
|
||||||
|
return row ? toEnterpriseStaff(row) : null
|
||||||
|
},
|
||||||
|
|
||||||
|
create(input) {
|
||||||
|
const created = db.insert(enterpriseStaff).values({
|
||||||
|
staffId: input.staff_id,
|
||||||
|
enterpriseId: input.enterprise_id,
|
||||||
|
}).returning().get()
|
||||||
|
return toEnterpriseStaff(created)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return { repo }
|
||||||
|
}
|
||||||
51
packages/data-manager-sqlite/src/enterprises.ts
Normal file
51
packages/data-manager-sqlite/src/enterprises.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { eq } from 'drizzle-orm'
|
||||||
|
|
||||||
|
import { db } from './db-instance'
|
||||||
|
import { enterprises } from './schema/enterprises'
|
||||||
|
|
||||||
|
export type Enterprise = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
owner_name: string
|
||||||
|
mobile: string
|
||||||
|
address: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EnterpriseRepo = {
|
||||||
|
getEnterpriseById: (id: number) => Promise<Enterprise | null>
|
||||||
|
createEnterprise: (input: { name: string; type: string; owner_name: string; mobile: string; address?: string | null }) => Promise<Enterprise>
|
||||||
|
}
|
||||||
|
|
||||||
|
function toEnterprise(row: typeof enterprises.$inferSelect): Enterprise {
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
type: row.type,
|
||||||
|
owner_name: row.ownerName,
|
||||||
|
mobile: row.mobile,
|
||||||
|
address: row.address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEnterpriseRepo(): { repo: EnterpriseRepo } {
|
||||||
|
const repo: EnterpriseRepo = {
|
||||||
|
getEnterpriseById(id) {
|
||||||
|
const row = db.select().from(enterprises).where(eq(enterprises.id, id)).get()
|
||||||
|
return Promise.resolve(row ? toEnterprise(row) : null)
|
||||||
|
},
|
||||||
|
|
||||||
|
createEnterprise(input) {
|
||||||
|
const created = db.insert(enterprises).values({
|
||||||
|
name: input.name,
|
||||||
|
type: input.type,
|
||||||
|
ownerName: input.owner_name,
|
||||||
|
mobile: input.mobile,
|
||||||
|
address: input.address ?? null,
|
||||||
|
}).returning().get()
|
||||||
|
return Promise.resolve(toEnterprise(created))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return { repo }
|
||||||
|
}
|
||||||
|
|
@ -40,3 +40,25 @@ export {
|
||||||
type StockBatchesRepo,
|
type StockBatchesRepo,
|
||||||
} from './stockBatches'
|
} from './stockBatches'
|
||||||
export { stockBatches } from './schema/stockBatches'
|
export { stockBatches } from './schema/stockBatches'
|
||||||
|
export { enterprises } from './schema/enterprises'
|
||||||
|
export { staff } from './schema/staff'
|
||||||
|
export { roles } from './schema/roles'
|
||||||
|
export { permissions } from './schema/permissions'
|
||||||
|
export { rolePermissions } from './schema/rolePermissions'
|
||||||
|
export { staffRoles } from './schema/staffRoles'
|
||||||
|
export { enterpriseStaff } from './schema/enterpriseStaff'
|
||||||
|
export {
|
||||||
|
createEnterpriseRepo,
|
||||||
|
type Enterprise,
|
||||||
|
type EnterpriseRepo,
|
||||||
|
} from './enterprises'
|
||||||
|
export {
|
||||||
|
createStaffRepo,
|
||||||
|
type Staff,
|
||||||
|
type StaffRepo,
|
||||||
|
} from './staff'
|
||||||
|
export {
|
||||||
|
createEnterpriseStaffRepo,
|
||||||
|
type EnterpriseStaff,
|
||||||
|
type EnterpriseStaffRepo,
|
||||||
|
} from './enterpriseStaff'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
import { integer, sqliteTable, text, index } from 'drizzle-orm/sqlite-core'
|
||||||
|
import { enterprises } from './enterprises'
|
||||||
|
|
||||||
export const distributors = sqliteTable('distributors', {
|
export const distributors = sqliteTable('distributors', {
|
||||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
|
|
@ -6,4 +7,7 @@ export const distributors = sqliteTable('distributors', {
|
||||||
contact: text('contact').notNull(),
|
contact: text('contact').notNull(),
|
||||||
mobile: text('mobile').notNull(),
|
mobile: text('mobile').notNull(),
|
||||||
address: text('address'),
|
address: text('address'),
|
||||||
})
|
enterpriseId: integer('enterprise_id').references(() => enterprises.id),
|
||||||
|
}, (table) => ({
|
||||||
|
enterpriseIdx: index('distributors_enterprise_idx').on(table.enterpriseId),
|
||||||
|
}))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { integer, sqliteTable } from 'drizzle-orm/sqlite-core'
|
||||||
|
import { staff } from './staff'
|
||||||
|
import { enterprises } from './enterprises'
|
||||||
|
|
||||||
|
export const enterpriseStaff = sqliteTable('enterprise_staff', {
|
||||||
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
|
staffId: integer('staff_id').notNull().references(() => staff.id),
|
||||||
|
enterpriseId: integer('enterprise_id').notNull().references(() => enterprises.id),
|
||||||
|
})
|
||||||
10
packages/data-manager-sqlite/src/schema/enterprises.ts
Normal file
10
packages/data-manager-sqlite/src/schema/enterprises.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||||
|
|
||||||
|
export const enterprises = sqliteTable('enterprises', {
|
||||||
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
type: text('type').notNull(),
|
||||||
|
ownerName: text('owner_name').notNull(),
|
||||||
|
mobile: text('mobile').notNull(),
|
||||||
|
address: text('address'),
|
||||||
|
})
|
||||||
|
|
@ -5,3 +5,10 @@ export * from './drugInfo'
|
||||||
export * from './units'
|
export * from './units'
|
||||||
export * from './productCompositions'
|
export * from './productCompositions'
|
||||||
export * from './stockBatches'
|
export * from './stockBatches'
|
||||||
|
export * from './enterprises'
|
||||||
|
export * from './staff'
|
||||||
|
export * from './roles'
|
||||||
|
export * from './permissions'
|
||||||
|
export * from './rolePermissions'
|
||||||
|
export * from './staffRoles'
|
||||||
|
export * from './enterpriseStaff'
|
||||||
|
|
|
||||||
6
packages/data-manager-sqlite/src/schema/permissions.ts
Normal file
6
packages/data-manager-sqlite/src/schema/permissions.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||||
|
|
||||||
|
export const permissions = sqliteTable('permissions', {
|
||||||
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
|
name: text('name').notNull().unique(),
|
||||||
|
})
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { integer, sqliteTable, text, real } from 'drizzle-orm/sqlite-core'
|
import { integer, sqliteTable, text, real, index } from 'drizzle-orm/sqlite-core'
|
||||||
import { distributors } from './distributors'
|
import { distributors } from './distributors'
|
||||||
import { units } from './units'
|
import { units } from './units'
|
||||||
|
import { enterprises } from './enterprises'
|
||||||
|
|
||||||
export const products = sqliteTable('products', {
|
export const products = sqliteTable('products', {
|
||||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
|
|
@ -17,4 +18,7 @@ export const products = sqliteTable('products', {
|
||||||
unitsPerStrip: integer('units_per_strip'),
|
unitsPerStrip: integer('units_per_strip'),
|
||||||
hideProductFromPublic: integer('hide_product_from_public', { mode: 'boolean' }).notNull().default(false),
|
hideProductFromPublic: integer('hide_product_from_public', { mode: 'boolean' }).notNull().default(false),
|
||||||
hidePriceFromPublic: integer('hide_price_from_public', { mode: 'boolean' }).notNull().default(false),
|
hidePriceFromPublic: integer('hide_price_from_public', { mode: 'boolean' }).notNull().default(false),
|
||||||
})
|
enterpriseId: integer('enterprise_id').references(() => enterprises.id),
|
||||||
|
}, (table) => ({
|
||||||
|
enterpriseIdx: index('products_enterprise_idx').on(table.enterpriseId),
|
||||||
|
}))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { integer, sqliteTable } from 'drizzle-orm/sqlite-core'
|
||||||
|
import { roles } from './roles'
|
||||||
|
import { permissions } from './permissions'
|
||||||
|
|
||||||
|
export const rolePermissions = sqliteTable('role_permissions', {
|
||||||
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
|
roleId: integer('role_id').notNull().references(() => roles.id),
|
||||||
|
permissionId: integer('permission_id').notNull().references(() => permissions.id),
|
||||||
|
})
|
||||||
6
packages/data-manager-sqlite/src/schema/roles.ts
Normal file
6
packages/data-manager-sqlite/src/schema/roles.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||||
|
|
||||||
|
export const roles = sqliteTable('roles', {
|
||||||
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
|
name: text('name').notNull().unique(),
|
||||||
|
})
|
||||||
12
packages/data-manager-sqlite/src/schema/staff.ts
Normal file
12
packages/data-manager-sqlite/src/schema/staff.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||||
|
|
||||||
|
export const staff = sqliteTable('staff', {
|
||||||
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
username: text('username').notNull().unique(),
|
||||||
|
email: text('email'),
|
||||||
|
mobile: text('mobile'),
|
||||||
|
addedOn: text('added_on').notNull(),
|
||||||
|
password: text('password').notNull(),
|
||||||
|
isPasswordResetNeeded: integer('is_password_reset_needed', { mode: 'boolean' }).notNull().default(true),
|
||||||
|
})
|
||||||
9
packages/data-manager-sqlite/src/schema/staffRoles.ts
Normal file
9
packages/data-manager-sqlite/src/schema/staffRoles.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { integer, sqliteTable } from 'drizzle-orm/sqlite-core'
|
||||||
|
import { staff } from './staff'
|
||||||
|
import { roles } from './roles'
|
||||||
|
|
||||||
|
export const staffRoles = sqliteTable('staff_roles', {
|
||||||
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
|
staffId: integer('staff_id').notNull().references(() => staff.id),
|
||||||
|
roleId: integer('role_id').notNull().references(() => roles.id),
|
||||||
|
})
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
import { integer, sqliteTable, text, index } from 'drizzle-orm/sqlite-core'
|
||||||
import { products } from './products'
|
import { products } from './products'
|
||||||
import { storageSpaces } from './storageSpacesSchema'
|
import { storageSpaces } from './storageSpacesSchema'
|
||||||
import { distributors } from './distributors'
|
import { distributors } from './distributors'
|
||||||
|
import { enterprises } from './enterprises'
|
||||||
|
|
||||||
export const stockBatches = sqliteTable('stock_batches', {
|
export const stockBatches = sqliteTable('stock_batches', {
|
||||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
|
|
@ -14,4 +15,7 @@ export const stockBatches = sqliteTable('stock_batches', {
|
||||||
distributorId: integer('distributor_id').references(() => distributors.id),
|
distributorId: integer('distributor_id').references(() => distributors.id),
|
||||||
quantity: integer('quantity').notNull().default(0),
|
quantity: integer('quantity').notNull().default(0),
|
||||||
isDefault: integer('is_default', { mode: 'boolean' }).notNull().default(false),
|
isDefault: integer('is_default', { mode: 'boolean' }).notNull().default(false),
|
||||||
})
|
enterpriseId: integer('enterprise_id').references(() => enterprises.id),
|
||||||
|
}, (table) => ({
|
||||||
|
enterpriseIdx: index('stock_batches_enterprise_idx').on(table.enterpriseId),
|
||||||
|
}))
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
import { integer, sqliteTable, text, index } from 'drizzle-orm/sqlite-core'
|
||||||
|
import { enterprises } from './enterprises'
|
||||||
|
|
||||||
export const storageSpaces = sqliteTable('storage_spaces', {
|
export const storageSpaces = sqliteTable('storage_spaces', {
|
||||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||||
name: text('name').notNull(),
|
name: text('name').notNull(),
|
||||||
description: text('description'),
|
description: text('description'),
|
||||||
aliases: text('aliases').notNull().default('[]'),
|
aliases: text('aliases').notNull().default('[]'),
|
||||||
// JSON array string of image URLs
|
|
||||||
imageUrls: text('image_urls').notNull().default('[]'),
|
imageUrls: text('image_urls').notNull().default('[]'),
|
||||||
})
|
enterpriseId: integer('enterprise_id').references(() => enterprises.id),
|
||||||
|
}, (table) => ({
|
||||||
|
enterpriseIdx: index('storage_spaces_enterprise_idx').on(table.enterpriseId),
|
||||||
|
}))
|
||||||
|
|
|
||||||
70
packages/data-manager-sqlite/src/staff.ts
Normal file
70
packages/data-manager-sqlite/src/staff.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { eq } from 'drizzle-orm'
|
||||||
|
import bcrypt from 'bcryptjs'
|
||||||
|
|
||||||
|
import { db } from './db-instance'
|
||||||
|
import { staff } from './schema/staff'
|
||||||
|
|
||||||
|
export type Staff = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
username: string
|
||||||
|
email: string | null
|
||||||
|
mobile: string | null
|
||||||
|
added_on: string
|
||||||
|
password: string
|
||||||
|
is_password_reset_needed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StaffRepo = {
|
||||||
|
getStaffById: (id: number) => Promise<Staff | null>
|
||||||
|
getStaffByUsername: (username: string) => Promise<Staff | null>
|
||||||
|
createStaff: (input: { name: string; username: string; email?: string | null; mobile?: string | null; added_on: string; password: string; is_password_reset_needed?: boolean }) => Promise<Staff>
|
||||||
|
verifyPassword: (staff: Staff, password: string) => Promise<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
function toStaff(row: typeof staff.$inferSelect): Staff {
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
username: row.username,
|
||||||
|
email: row.email,
|
||||||
|
mobile: row.mobile,
|
||||||
|
added_on: row.addedOn,
|
||||||
|
password: row.password,
|
||||||
|
is_password_reset_needed: row.isPasswordResetNeeded,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createStaffRepo(): { repo: StaffRepo } {
|
||||||
|
const repo: StaffRepo = {
|
||||||
|
getStaffById(id) {
|
||||||
|
const row = db.select().from(staff).where(eq(staff.id, id)).get()
|
||||||
|
return Promise.resolve(row ? toStaff(row) : null)
|
||||||
|
},
|
||||||
|
|
||||||
|
getStaffByUsername(username) {
|
||||||
|
const row = db.select().from(staff).where(eq(staff.username, username)).get()
|
||||||
|
return Promise.resolve(row ? toStaff(row) : null)
|
||||||
|
},
|
||||||
|
|
||||||
|
createStaff(input) {
|
||||||
|
const hashed = bcrypt.hashSync(input.password, 10)
|
||||||
|
const created = db.insert(staff).values({
|
||||||
|
name: input.name,
|
||||||
|
username: input.username,
|
||||||
|
email: input.email ?? null,
|
||||||
|
mobile: input.mobile ?? null,
|
||||||
|
addedOn: input.added_on,
|
||||||
|
password: hashed,
|
||||||
|
isPasswordResetNeeded: input.is_password_reset_needed ?? true,
|
||||||
|
}).returning().get()
|
||||||
|
return Promise.resolve(toStaff(created))
|
||||||
|
},
|
||||||
|
|
||||||
|
async verifyPassword(s, password) {
|
||||||
|
return bcrypt.compare(password, s.password)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return { repo }
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue