refactor: update API endpoints and enhance loan management features
This commit is contained in:
@@ -27,7 +27,7 @@ function App() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Cookies.get("token")) {
|
if (Cookies.get("token")) {
|
||||||
const verifyToken = async () => {
|
const verifyToken = async () => {
|
||||||
const response = await fetch(`${API_BASE}/api/verifyToken`, {
|
const response = await fetch(`${API_BASE}/verify`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const Header = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE}/api/changePassword`, {
|
const response = await fetch(`${API_BASE}/api/users/change-password`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const useVersionInfoQuery = () =>
|
|||||||
useQuery({
|
useQuery({
|
||||||
queryKey: ["versionInfo"],
|
queryKey: ["versionInfo"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${API_BASE}/server-info`, {
|
const response = await fetch(`${API_BASE}/`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import {
|
|||||||
Spinner,
|
Spinner,
|
||||||
VStack,
|
VStack,
|
||||||
Table,
|
Table,
|
||||||
|
InputGroup,
|
||||||
|
Span,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { getBorrowableItems } from "@/utils/Fetcher";
|
import { getBorrowableItems } from "@/utils/Fetcher";
|
||||||
@@ -31,6 +33,9 @@ export const HomePage = () => {
|
|||||||
const [isLoadingA, setIsLoadingA] = useState(false);
|
const [isLoadingA, setIsLoadingA] = useState(false);
|
||||||
const [selectedItems, setSelectedItems] = useState<number[]>([]);
|
const [selectedItems, setSelectedItems] = useState<number[]>([]);
|
||||||
|
|
||||||
|
const MAX_CHARACTERS = 500;
|
||||||
|
const [note, setNote] = useState("");
|
||||||
|
|
||||||
// Error handling states
|
// Error handling states
|
||||||
const [isMsg, setIsMsg] = useState(false);
|
const [isMsg, setIsMsg] = useState(false);
|
||||||
const [msgStatus, setMsgStatus] = useState<"error" | "success">("error");
|
const [msgStatus, setMsgStatus] = useState<"error" | "success">("error");
|
||||||
@@ -136,13 +141,29 @@ export const HomePage = () => {
|
|||||||
</Table.Row>
|
</Table.Row>
|
||||||
))}
|
))}
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
|
<InputGroup
|
||||||
|
endElement={
|
||||||
|
<Span color="fg.muted" textStyle="xs">
|
||||||
|
{note.length} / {MAX_CHARACTERS}
|
||||||
|
</Span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder={t("optional-note")}
|
||||||
|
value={note}
|
||||||
|
maxLength={MAX_CHARACTERS}
|
||||||
|
onChange={(e) => {
|
||||||
|
setNote(e.currentTarget.value.slice(0, MAX_CHARACTERS));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
</Table.Root>
|
</Table.Root>
|
||||||
</Table.ScrollArea>
|
</Table.ScrollArea>
|
||||||
)}
|
)}
|
||||||
{selectedItems.length >= 1 && (
|
{selectedItems.length >= 1 && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
createLoan(selectedItems, startDate, endDate).then((response) => {
|
createLoan(selectedItems, startDate, endDate, note).then((response) => {
|
||||||
if (response.status === "error") {
|
if (response.status === "error") {
|
||||||
setMsgStatus("error");
|
setMsgStatus("error");
|
||||||
setMsgTitle(response.title || t("error"));
|
setMsgTitle(response.title || t("error"));
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ type Device = {
|
|||||||
can_borrow_role: string;
|
can_borrow_role: string;
|
||||||
inSafe: number;
|
inSafe: number;
|
||||||
entry_created_at: string;
|
entry_created_at: string;
|
||||||
|
last_borrowed_person: string | null;
|
||||||
|
currently_borrowing: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Landingpage: React.FC = () => {
|
const Landingpage: React.FC = () => {
|
||||||
@@ -68,7 +70,7 @@ const Landingpage: React.FC = () => {
|
|||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const loanRes = await fetch(`${API_BASE}/apiV2/allLoans`);
|
const loanRes = await fetch(`${API_BASE}/api/loans/all-loans`);
|
||||||
const loanData = await loanRes.json();
|
const loanData = await loanRes.json();
|
||||||
if (Array.isArray(loanData)) {
|
if (Array.isArray(loanData)) {
|
||||||
setLoans(loanData);
|
setLoans(loanData);
|
||||||
@@ -80,7 +82,7 @@ const Landingpage: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceRes = await fetch(`${API_BASE}/apiV2/allItems`);
|
const deviceRes = await fetch(`${API_BASE}/api/loans/all-items`);
|
||||||
const deviceData = await deviceRes.json();
|
const deviceData = await deviceRes.json();
|
||||||
if (Array.isArray(deviceData)) {
|
if (Array.isArray(deviceData)) {
|
||||||
setDevices(deviceData);
|
setDevices(deviceData);
|
||||||
@@ -200,6 +202,14 @@ const Landingpage: React.FC = () => {
|
|||||||
<Text>
|
<Text>
|
||||||
{t("rent-role")}: {device.can_borrow_role}
|
{t("rent-role")}: {device.can_borrow_role}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text>
|
||||||
|
{t("last-borrowed-person")}:{" "}
|
||||||
|
{device.last_borrowed_person || "N/A"}
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
{t("currently-borrowed-by")}:{" "}
|
||||||
|
{device.currently_borrowing || "N/A"}
|
||||||
|
</Text>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const LoginPage = () => {
|
|||||||
}, [isLoggedIn, navigate]);
|
}, [isLoggedIn, navigate]);
|
||||||
|
|
||||||
const loginFnc = async (username: string, password: string) => {
|
const loginFnc = async (username: string, password: string) => {
|
||||||
const response = await fetch(`${API_BASE}/api/login`, {
|
const response = await fetch(`${API_BASE}/api/users/login`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ username, password }),
|
body: JSON.stringify({ username, password }),
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const MyLoansPage = () => {
|
|||||||
const fetchLoans = async () => {
|
const fetchLoans = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const res = await fetch(`${API_BASE}/api/userLoans`, {
|
const res = await fetch(`${API_BASE}/api/loans/loans`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
@@ -75,7 +75,7 @@ export const MyLoansPage = () => {
|
|||||||
|
|
||||||
const deleteLoan = async (loanId: number) => {
|
const deleteLoan = async (loanId: number) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_BASE}/api/SETdeleteLoan/${loanId}`, {
|
const res = await fetch(`${API_BASE}/api/loans/delete-loan/${loanId}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const getBorrowableItems = async (
|
|||||||
endDate: string
|
endDate: string
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/api/borrowableItems`, {
|
const response = await fetch(`${API_BASE}/api/loans/borrowable-items`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${Cookies.get("token") || ""}`,
|
Authorization: `Bearer ${Cookies.get("token") || ""}`,
|
||||||
@@ -47,15 +47,16 @@ export const getBorrowableItems = async (
|
|||||||
export const createLoan = async (
|
export const createLoan = async (
|
||||||
itemIds: number[],
|
itemIds: number[],
|
||||||
startDate: string,
|
startDate: string,
|
||||||
endDate: string
|
endDate: string,
|
||||||
|
note: string | null
|
||||||
) => {
|
) => {
|
||||||
const response = await fetch(`${API_BASE}/api/createLoan`, {
|
const response = await fetch(`${API_BASE}/api/loans/createLoan`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${Cookies.get("token") || ""}`,
|
Authorization: `Bearer ${Cookies.get("token") || ""}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ items: itemIds, startDate, endDate }),
|
body: JSON.stringify({ items: itemIds, startDate, endDate, note }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ export const createLoanInDatabase = async (
|
|||||||
try {
|
try {
|
||||||
await conn.beginTransaction();
|
await conn.beginTransaction();
|
||||||
|
|
||||||
// Ensure all items exist and collect names
|
// Ensure all items exist and collect names + lockers
|
||||||
const [itemsRows] = await conn.query(
|
const [itemsRows] = await conn.query(
|
||||||
"SELECT id, item_name FROM items WHERE id IN (?)",
|
"SELECT id, item_name, safe_nr FROM items WHERE id IN (?)",
|
||||||
[itemIds]
|
[itemIds]
|
||||||
);
|
);
|
||||||
if (!itemsRows || itemsRows.length !== itemIds.length) {
|
if (!itemsRows || itemsRows.length !== itemIds.length) {
|
||||||
@@ -62,12 +62,22 @@ export const createLoanInDatabase = async (
|
|||||||
message: "One or more items not found",
|
message: "One or more items not found",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemNames = itemIds
|
const itemNames = itemIds
|
||||||
.map(
|
.map(
|
||||||
(id) => itemsRows.find((r) => Number(r.id) === Number(id))?.item_name
|
(id) => itemsRows.find((r) => Number(r.id) === Number(id))?.item_name
|
||||||
)
|
)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
// Build lockers array (unique, only 2-digit strings)
|
||||||
|
const lockers = [
|
||||||
|
...new Set(
|
||||||
|
itemsRows
|
||||||
|
.map((r) => r.safe_nr)
|
||||||
|
.filter((sn) => typeof sn === "string" && /^\d{2}$/.test(sn))
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
// Check availability (no overlap with existing loans)
|
// Check availability (no overlap with existing loans)
|
||||||
const [confRows] = await conn.query(
|
const [confRows] = await conn.query(
|
||||||
`
|
`
|
||||||
@@ -113,18 +123,18 @@ export const createLoanInDatabase = async (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert loan
|
// Insert loan (now includes lockers)
|
||||||
const [insertRes] = await conn.query(
|
const [insertRes] = await conn.query(
|
||||||
`
|
`
|
||||||
INSERT INTO loans (username, loan_code, start_date, end_date, loaned_items_id, loaned_items_name, note)
|
INSERT INTO loans (username, loan_code, start_date, end_date, lockers, loaned_items_id, loaned_items_name, note)
|
||||||
VALUES (?, ?, ?, ?, CAST(? AS JSON), CAST(? AS JSON), ?)
|
VALUES (?, ?, ?, ?, CAST(? AS JSON), CAST(? AS JSON), CAST(? AS JSON), ?)
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
username,
|
username,
|
||||||
loanCode,
|
loanCode,
|
||||||
// Use DATETIME/TIMESTAMP friendly format
|
|
||||||
new Date(start).toISOString().slice(0, 19).replace("T", " "),
|
new Date(start).toISOString().slice(0, 19).replace("T", " "),
|
||||||
new Date(end).toISOString().slice(0, 19).replace("T", " "),
|
new Date(end).toISOString().slice(0, 19).replace("T", " "),
|
||||||
|
JSON.stringify(lockers),
|
||||||
JSON.stringify(itemIds.map((n) => Number(n))),
|
JSON.stringify(itemIds.map((n) => Number(n))),
|
||||||
JSON.stringify(itemNames),
|
JSON.stringify(itemNames),
|
||||||
note,
|
note,
|
||||||
@@ -142,6 +152,7 @@ export const createLoanInDatabase = async (
|
|||||||
end_date: end,
|
end_date: end,
|
||||||
items: itemIds,
|
items: itemIds,
|
||||||
item_names: itemNames,
|
item_names: itemNames,
|
||||||
|
lockers,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -172,6 +183,70 @@ export const getLoansFromDatabase = async (username) => {
|
|||||||
"SELECT * FROM loans WHERE username = ? AND deleted = 0;",
|
"SELECT * FROM loans WHERE username = ? AND deleted = 0;",
|
||||||
[username]
|
[username]
|
||||||
);
|
);
|
||||||
|
if (result.length > 0) {
|
||||||
|
return { success: true, status: true, data: result };
|
||||||
|
} else if (result.length === 0) {
|
||||||
|
return { success: true, status: true, data: [] };
|
||||||
|
}
|
||||||
|
return { success: false };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBorrowableItemsFromDatabase = async (
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
role = 0
|
||||||
|
) => {
|
||||||
|
// Overlap if: loan.start < end AND effective_end > start
|
||||||
|
// effective_end is returned_date if set, otherwise end_date
|
||||||
|
const hasRoleFilter = Number(role) > 0;
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
SELECT i.*
|
||||||
|
FROM items i
|
||||||
|
WHERE ${hasRoleFilter ? "i.can_borrow_role >= ? AND " : ""}NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM loans l
|
||||||
|
JOIN JSON_TABLE(l.loaned_items_id, '$[*]' COLUMNS (item_id INT PATH '$')) jt
|
||||||
|
WHERE jt.item_id = i.id
|
||||||
|
AND l.deleted = 0
|
||||||
|
AND l.start_date < ?
|
||||||
|
AND COALESCE(l.returned_date, l.end_date) > ?
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const params = hasRoleFilter
|
||||||
|
? [role, endDate, startDate]
|
||||||
|
: [endDate, startDate];
|
||||||
|
|
||||||
|
const [rows] = await pool.query(sql, params);
|
||||||
|
if (rows.length > 0) {
|
||||||
|
return { success: true, data: rows };
|
||||||
|
}
|
||||||
|
return { success: false };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SETdeleteLoanFromDatabase = async (loanId) => {
|
||||||
|
const [result] = await pool.query(
|
||||||
|
"UPDATE loans SET deleted = 1 WHERE id = ?;",
|
||||||
|
[loanId]
|
||||||
|
);
|
||||||
|
if (result.affectedRows > 0) {
|
||||||
|
return { success: true };
|
||||||
|
} else {
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getALLLoans = async () => {
|
||||||
|
const [result] = await pool.query("SELECT * FROM loans WHERE deleted = 0;");
|
||||||
|
if (result.length > 0) {
|
||||||
|
return { success: true, data: result };
|
||||||
|
}
|
||||||
|
return { success: false };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getItems = async () => {
|
||||||
|
const [result] = await pool.query("SELECT * FROM items;");
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
return { success: true, data: result };
|
return { success: true, data: result };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,22 @@ export const loginFunc = async (username, password) => {
|
|||||||
return { success: false };
|
return { success: false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getItems = async () => {
|
||||||
|
const [rows] = await pool.query("SELECT * FROM items;");
|
||||||
|
if (rows.length > 0) {
|
||||||
|
return { success: true, data: rows };
|
||||||
|
}
|
||||||
|
return { success: false };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getALLLoans = async () => {
|
||||||
|
const [rows] = await pool.query("SELECT * FROM loans;");
|
||||||
|
if (rows.length > 0) {
|
||||||
|
return { success: true, data: rows };
|
||||||
|
}
|
||||||
|
return { success: false };
|
||||||
|
};
|
||||||
|
|
||||||
export const changePassword = async (username, oldPassword, newPassword) => {
|
export const changePassword = async (username, oldPassword, newPassword) => {
|
||||||
// get user current password
|
// get user current password
|
||||||
const [user] = await pool.query(
|
const [user] = await pool.query(
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import {
|
|||||||
createLoanInDatabase,
|
createLoanInDatabase,
|
||||||
getLoanInfoWithID,
|
getLoanInfoWithID,
|
||||||
getLoansFromDatabase,
|
getLoansFromDatabase,
|
||||||
|
getBorrowableItemsFromDatabase,
|
||||||
|
getALLLoans,
|
||||||
|
getItems,
|
||||||
|
SETdeleteLoanFromDatabase,
|
||||||
} from "./database/loansMgmt.database.js";
|
} from "./database/loansMgmt.database.js";
|
||||||
import { sendMailLoan } from "./services/mailer.js";
|
import { sendMailLoan } from "./services/mailer.js";
|
||||||
|
|
||||||
@@ -85,9 +89,62 @@ router.get("/loans", authenticate, async (req, res) => {
|
|||||||
const result = await getLoansFromDatabase(req.user.username);
|
const result = await getLoansFromDatabase(req.user.username);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
res.status(200).json(result.data);
|
res.status(200).json(result.data);
|
||||||
|
} else if (result.status) {
|
||||||
|
res.status(200).json([]);
|
||||||
} else {
|
} else {
|
||||||
res.status(500).json({ message: "Failed to fetch loans" });
|
res.status(500).json({ message: "Failed to fetch loans" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get("/all-items", authenticate, async (req, res) => {
|
||||||
|
const result = await getItems();
|
||||||
|
if (result.success) {
|
||||||
|
res.status(200).json(result.data);
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: "Failed to fetch items" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete("/delete-loan/:id", authenticate, async (req, res) => {
|
||||||
|
const loanId = req.params.id;
|
||||||
|
const result = await SETdeleteLoanFromDatabase(loanId);
|
||||||
|
if (result.success) {
|
||||||
|
res.status(200).json({ message: "Loan deleted successfully" });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: "Failed to delete loan" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/all-loans", authenticate, async (req, res) => {
|
||||||
|
const result = await getALLLoans();
|
||||||
|
if (result.success) {
|
||||||
|
res.status(200).json(result.data);
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: "Failed to fetch loans" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/borrowable-items", authenticate, async (req, res) => {
|
||||||
|
const { startDate, endDate } = req.body || {};
|
||||||
|
if (!startDate || !endDate) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ message: "startDate and endDate are required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await getBorrowableItemsFromDatabase(
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
req.user.role
|
||||||
|
);
|
||||||
|
if (result.success) {
|
||||||
|
// return the array directly for consistency with /items
|
||||||
|
return res.status(200).json(result.data);
|
||||||
|
} else {
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json({ message: "Failed to fetch borrowable items" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import nodemailer from "nodemailer";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
function buildLoanEmail({ user, items, startDate, endDate, createdDate }) {
|
function buildLoanEmail({ user, items, startDate, endDate, createdDate }) {
|
||||||
const brand = process.env.MAIL_BRAND_COLOR || "#0ea5e9";
|
const brand = process.env.MAIL_BRAND_COLOR || "#0ea5e9";
|
||||||
const itemsList =
|
const itemsList =
|
||||||
@@ -112,7 +116,7 @@ function buildLoanEmailText({ user, items, startDate, endDate, createdDate }) {
|
|||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendMailLoan(user, items, startDate, endDate, createdDate) {
|
export function sendMailLoan(user, items, startDate, endDate, createdDate) {
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
host: process.env.MAIL_HOST,
|
host: process.env.MAIL_HOST,
|
||||||
port: process.env.MAIL_PORT,
|
port: process.env.MAIL_PORT,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import express from "express";
|
|||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import env from "dotenv";
|
import env from "dotenv";
|
||||||
import info from "./info.json" assert { type: "json" };
|
import info from "./info.json" assert { type: "json" };
|
||||||
|
import { authenticate } from "./services/authentication.js";
|
||||||
|
|
||||||
// frontend routes
|
// frontend routes
|
||||||
import loansMgmtRouter from "./routes/app/loanMgmt.route.js";
|
import loansMgmtRouter from "./routes/app/loanMgmt.route.js";
|
||||||
@@ -46,6 +47,10 @@ app.listen(port, () => {
|
|||||||
console.log(`Server is running on port: ${port}`);
|
console.log(`Server is running on port: ${port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get("/verify", authenticate, async (req, res) => {
|
||||||
|
res.status(200).json({ message: "Token is valid", user: req.user });
|
||||||
|
});
|
||||||
|
|
||||||
app.get("/", (req, res) => {
|
app.get("/", (req, res) => {
|
||||||
res.send(info);
|
res.send(info);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user