Compare commits

...

42 Commits

Author SHA1 Message Date
Allan Carr
dab78e3dc9 IO-2190 Paint Costs for JC if Cost/Hr is 0 2023-05-11 16:07:28 -07:00
Patrick Fic
051ee347a9 Merged in release/2023-05-05 (pull request #768)
Release/2023 05 05
2023-05-05 22:51:16 +00:00
John Allen Delos Reyes
d350515c90 Merged in feature/IO-1722-job-size-color (pull request #763)
Feature/IO-1722 job size color

Approved-by: Patrick Fic
2023-05-05 22:02:55 +00:00
Allan Carr
121e579388 Merged in feature/IO-2261-opensearch-replacements (pull request #766)
IO-2261 Missing loader handler for owner_owing, correct payment

Approved-by: Patrick Fic
2023-05-05 22:02:03 +00:00
swtmply
04509fa587 IO-1722 adjusted logic for card color 2023-05-06 04:56:03 +08:00
swtmply
56fef0f43c IO-1722 Added reset button 2023-05-06 04:55:21 +08:00
Allan Carr
25bee3cfdf Merged in feature/IO-2261-opensearch-replacements (pull request #764)
Feature/IO-2261 opensearch replacements

Approved-by: Patrick Fic
2023-05-05 20:14:02 +00:00
swtmply
3e00e7981d IO-1722 added placement to popover 2023-05-06 03:34:55 +08:00
swtmply
12fa270a1a IO-1722 adjusted background colors 2023-05-06 03:34:22 +08:00
swtmply
3988386c79 IO-1722 replaced hex to rgba 2023-05-06 02:56:02 +08:00
Allan Carr
8222e56485 Merged in feature/IO-2261-opensearch-replacements (pull request #761)
IO-2261 Add in missing field "date" for Payments

Approved-by: Patrick Fic
2023-05-05 17:56:14 +00:00
John Allen Delos Reyes
00eb7926f9 Merged in feature/IO-2266-duplicate-option (pull request #759)
IO-2266 fixed key issue causing option to duplicate

Approved-by: Patrick Fic
2023-05-05 17:44:19 +00:00
Patrick Fic
854ad21b20 Update autohouse header auth. 2023-05-05 10:16:41 -07:00
swtmply
3c3f9521f6 IO-2266 fixed key issue causing option to duplicate 2023-05-05 23:43:48 +08:00
Allan Carr
75d9faa05b Merged in feature/IO-2261-opensearch-replacements (pull request #757)
IO-2261 All Jobs, Bills, Payments

Approved-by: Patrick Fic
2023-05-05 14:49:23 +00:00
Patrick Fic
90cba9ed24 Merge branch 'feature/IO-1722-job-size-color' into release/2023-05-05 2023-05-04 15:32:30 -07:00
Patrick Fic
a0702785c5 Revert "Revert "Merged in feature/IO-1722-job-size-color (pull request #746)""
This reverts commit 7dc3c00628.
2023-05-04 15:32:21 -07:00
Allan Carr
d70893e2ba Merged in feature/IO-2261-opensearch-replacements (pull request #748)
Feature/IO-2261 opensearch replacements
2023-05-04 22:16:49 +00:00
Patrick Fic
6155b8bf24 Merged in release/2023-05-05 (pull request #754)
Add firebase auth to JSR call.
2023-05-04 21:04:16 +00:00
Patrick Fic
2586855f11 Add firebase auth to JSR call. 2023-05-04 13:33:16 -07:00
Patrick Fic
b3b3c4c737 Merged in release/2023-05-05 (pull request #752)
Resolve health check
2023-05-04 20:26:04 +00:00
Patrick Fic
abe5fadeea Resolve health check 2023-05-04 13:25:45 -07:00
swtmply
08e5543536 IO-1722 adjusted condition for text color 2023-05-05 04:00:44 +08:00
Patrick Fic
f1a10e0df4 Merged in release/2023-05-05 (pull request #750)
Release/2023 05 05
2023-05-04 19:57:22 +00:00
Patrick Fic
7dc3c00628 Revert "Merged in feature/IO-1722-job-size-color (pull request #746)"
This reverts commit 7594f53e88, reversing
changes made to b861957342.
2023-05-04 12:50:28 -07:00
John Allen Delos Reyes
7594f53e88 Merged in feature/IO-1722-job-size-color (pull request #746)
Feature/IO-1722 job size color

Approved-by: Patrick Fic
2023-05-04 19:03:30 +00:00
Patrick Fic
b861957342 Additional security hardening. 2023-05-04 11:59:39 -07:00
swtmply
2bf24ff5a1 IO-1722 refactor color function 2023-05-04 23:00:37 +08:00
swtmply
833baca9cc Merge branch 'feature/IO-1722-job-size-color' of https://bitbucket.org/snaptsoft/bodyshop into feature/IO-1722-job-size-color 2023-05-04 01:27:24 +08:00
swtmply
889ef61185 IO-1722 fixed spacing on translations 2023-05-04 01:25:17 +08:00
swtmply
add1eddbc1 IO-1722 added translations 2023-05-04 01:18:16 +08:00
swtmply
0cabd80b94 IO-1722 job size color in production card 2023-05-04 00:39:50 +08:00
Patrick Fic
7f756bab88 Merged in feature/IO-2190-Autohouse (pull request #743)
IO-2190 Autohouse & Job Costing
2023-05-02 21:53:40 +00:00
Allan Carr
99b847822f Merged in feature/IO-2190-Autohouse (pull request #742)
IO-2190 Autohouse & Job Costing

Approved-by: Patrick Fic
2023-05-02 21:52:50 +00:00
Allan Carr
f66d9b8c09 IO-2190 Autohouse & Job Costing
Insure that amount going into Dinero is Integer
2023-05-02 14:51:47 -07:00
Patrick Fic
5660de42af Add support email to server side emails. 2023-05-02 12:31:18 -07:00
Patrick Fic
ccbe92c275 Merged in feature/IO-2190-Autohouse (pull request #741)
IO-2190 Correct lookup location for MAPA Hrs
2023-05-02 17:06:12 +00:00
Allan Carr
b0ea8a71fb Merged in feature/IO-2190-Autohouse (pull request #738)
IO-2190 Correct lookup location for MAPA Hrs

Approved-by: Patrick Fic
2023-05-01 22:30:13 +00:00
Allan Carr
060871306f Merged in feature/IO-2257-PBS-EXPORT-CREDIT (pull request #737)
IO-2257 PBS Export of Credits

Approved-by: Patrick Fic
2023-05-01 22:30:00 +00:00
Allan Carr
2eb4e142ff IO-2190 Correct lookup location for MAPA Hrs 2023-05-01 13:48:18 -07:00
Patrick Fic
adf8cf9e8d Additional hasura indexes. 2023-05-01 13:05:59 -07:00
Allan Carr
79a90bb9ee IO-2257 PBS Export of Credits
Amounts less then 0 (ie all credits) would not get pushed to transactionObject. Expand to be both sides of zero to allow for credits within transactionObject
2023-04-27 17:13:37 -07:00
23 changed files with 240 additions and 52 deletions

View File

@@ -1,3 +1,4 @@
GENERATE_SOURCEMAP=false
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
REACT_APP_GA_CODE=231103507

View File

@@ -109,8 +109,8 @@ export function JobsConvertButton({
]}
>
<Select>
{bodyshop.md_ins_cos.map((s) => (
<Select.Option key={s.name} value={s.name}>
{bodyshop.md_ins_cos.map((s, i) => (
<Select.Option key={i} value={s.name}>
{s.name}
</Select.Option>
))}

View File

@@ -0,0 +1,51 @@
import { Col, List, Space, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
const CardColorLegend = ({ bodyshop }) => {
const { t } = useTranslation();
const data = bodyshop.ssbuckets.map((bucket) => {
let color = { r: 255, g: 255, b: 255 };
if (bucket.color) {
color = bucket.color;
if (bucket.color.rgb) {
color = bucket.color.rgb;
}
}
return {
label: bucket.label,
color,
};
});
return (
<Col>
<Typography>{t("production.labels.legend")}</Typography>
<List
grid={{
gutter: 16,
}}
dataSource={data}
renderItem={(item) => (
<List.Item>
<Space>
<div
style={{
width: "1.5rem",
aspectRatio: "1/1",
backgroundColor: `rgba(${item.color.r},${item.color.g},${item.color.b},${item.color.a})`,
}}
></div>
<div>{item.label}</div>
</Space>
</List.Item>
)}
/>
</Col>
);
};
export default CardColorLegend;

View File

@@ -18,6 +18,31 @@ import moment from "moment";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
const cardColor = (ssbuckets, totalHrs) => {
const bucket = ssbuckets.filter(
(bucket) =>
bucket.gte <= totalHrs && (!!bucket.lt ? bucket.lt > totalHrs : true)
)[0];
let color = { r: 255, g: 255, b: 255 };
if (bucket && bucket.color) {
color = bucket.color;
if (bucket.color.rgb) {
color = bucket.color.rgb;
}
}
return color;
};
function getContrastYIQ(bgColor) {
const yiq = (bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000;
return yiq >= 128 ? "black" : "white";
}
export default function ProductionBoardCard(
technician,
card,
@@ -54,10 +79,22 @@ export default function ProductionBoardCard(
.isSame(moment(card.scheduled_completion), "day") &&
"production-completion-soon"));
const totalHrs =
card.labhrs.aggregate.sum.mod_lb_hrs + card.larhrs.aggregate.sum.mod_lb_hrs;
const bgColor = cardColor(bodyshop.ssbuckets, totalHrs);
return (
<Card
className="react-kanban-card imex-kanban-card"
size="small"
style={{
backgroundColor:
cardSettings &&
cardSettings.cardcolor &&
`rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
color:
cardSettings && cardSettings.cardcolor && getContrastYIQ(bgColor),
}}
title={
<Space>
<ProductionAlert record={card} key="alert" />

View File

@@ -104,6 +104,13 @@ export default function ProductionBoardKanbanCardSettings({
>
<Switch />
</Form.Item>
<Form.Item
valuePropName="checked"
label={t("production.labels.cardcolor")}
name="cardcolor"
>
<Switch />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
@@ -166,7 +173,7 @@ export default function ProductionBoardKanbanCardSettings({
</div>
);
return (
<Popover content={overlay} visible={visible}>
<Popover content={overlay} visible={visible} placement="topRight">
<Button loading={loading} onClick={() => setVisible(true)}>
{t("production.labels.cardsettings")}
</Button>

View File

@@ -22,6 +22,7 @@ import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-se
//import "@asseinfo/react-kanban/dist/styles.css";
import "./production-board-kanban.styles.scss";
import { createBoardData } from "./production-board-kanban.utils.js";
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician,
@@ -221,6 +222,7 @@ export function ProductionBoardKanbanComponent({
employeeassignments: true,
scheduled_completion: true,
stickyheader: false,
cardcolor: false,
};
return (
@@ -256,6 +258,11 @@ export function ProductionBoardKanbanComponent({
</Space>
}
/>
{cardSettings.cardcolor && (
<CardColorLegend cardSettings={cardSettings} bodyshop={bodyshop} />
)}
<ProductionListDetailComponent jobs={data} />
<StickyContainer>
<Board

View File

@@ -396,7 +396,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
);
}
const ColorPicker = ({ value, onChange, style, ...restProps }) => {
export const ColorPicker = ({ value, onChange, style, ...restProps }) => {
const handleChange = (color) => {
if (onChange) onChange(color.rgb);
};

View File

@@ -15,6 +15,7 @@ import { useTranslation } from "react-i18next";
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { ColorPicker } from "./shop-info.rostatus.component";
export default function ShopInfoSchedulingComponent({ form }) {
const { t } = useTranslation();
@@ -277,17 +278,50 @@ export default function ShopInfoSchedulingComponent({ form }) {
>
<InputNumber />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
<Space direction="horizontal">
<Form.Item
label={
<Space>
{t("bodyshop.fields.ssbuckets.color")}
<Button
size="small"
onClick={() => {
form.setFieldValue([
"ssbuckets",
field.name,
"color",
]);
form.setFields([
{
name: ["ssbuckets", field.name, "color"],
touched: true,
},
]);
}}
>
Reset
</Button>
</Space>
}
key={`${index}color`}
name={[field.name, "color"]}
>
<ColorPicker />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</Space>
</LayoutFormRow>
</Form.Item>

View File

@@ -508,7 +508,8 @@
"id": "ID",
"label": "Label",
"lt": "Less than (hrs)",
"target": "Target (count)"
"target": "Target (count)",
"color": "Job Color"
},
"state": "Province/State",
"state_tax_id": "Provincial/State Tax ID (PST, QST)",
@@ -2385,7 +2386,9 @@
"sublets": "Sublets",
"totalhours": "Total Hrs ",
"touchtime": "T/T",
"viewname": "View Name"
"viewname": "View Name",
"legend": "Legend:",
"cardcolor": "Card Colors"
},
"successes": {
"removed": "Job removed from production."

View File

@@ -1,6 +1,5 @@
import { gql } from "@apollo/client";
import { notification } from "antd";
import axios from "axios";
import jsreport from "@jsreport/browser-client";
import _ from "lodash";
import moment from "moment";
@@ -9,7 +8,8 @@ import { setEmailOptions } from "../redux/email/email.actions";
import { store } from "../redux/store";
import client from "../utils/GraphQLClient";
import { TemplateList } from "./TemplateConstants";
import cleanAxios from "./CleanAxios";
import axios from "axios";
const server = process.env.REACT_APP_REPORTS_SERVER_URL;
jsreport.serverUrl = server;
@@ -26,10 +26,14 @@ export default async function RenderTemplate(
if (window.jsr3) {
jsreport.serverUrl = "https://reports3.test.imex.online/";
}
const jsrAuth = (await axios.post("/utils/jsr")).data;
jsreport.headers["Authorization"] = jsrAuth;
//Query assets that match the template name. Must be in format <<templateName>>.query
let { contextData, useShopSpecificTemplate } = await fetchContextData(
templateObject
templateObject,
jsrAuth
);
const { ignoreCustomMargins } = Templates[templateObject.name];
@@ -137,11 +141,15 @@ export async function RenderTemplates(
//Query assets that match the template name. Must be in format <<templateName>>.query
let unsortedTemplatesAndData = [];
let proms = [];
const jsrAuth = (await axios.post("/utils/jsr")).data;
jsreport.headers["Authorization"] = jsrAuth;
templateObjects.forEach((template) => {
proms.push(
(async () => {
let { contextData, useShopSpecificTemplate } = await fetchContextData(
template
template,
jsrAuth
);
unsortedTemplatesAndData.push({
templateObject: template,
@@ -298,19 +306,22 @@ export const GenerateDocuments = async (templates) => {
await RenderTemplates(templates, bodyshop);
};
const fetchContextData = async (templateObject) => {
const fetchContextData = async (templateObject, jsrAuth) => {
const bodyshop = store.getState().user.bodyshop;
jsreport.headers["Authorization"] =
jsreport.headers["FirebaseAuthorization"] =
"Bearer " + (await auth.currentUser.getIdToken());
const folders = await axios.get(`${server}/odata/folders`);
const folders = await cleanAxios.get(`${server}/odata/folders`, {
headers: { Authorization: jsrAuth },
});
const shopSpecificFolder = folders.data.value.find(
(f) => f.name === bodyshop.imexshopid
);
const jsReportQueries = await axios.get(
`${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`
const jsReportQueries = await cleanAxios.get(
`${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`,
{ headers: { Authorization: jsrAuth } }
);
let templateQueryToExecute;

View File

@@ -0,0 +1,3 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE INDEX idx_associations_active_true ON associations(active) WHERE active = true;

View File

@@ -0,0 +1 @@
CREATE INDEX idx_associations_active_true ON associations(active) WHERE active = true;

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."idx_associations_shopid_user";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "idx_associations_shopid_user" on
"public"."associations" using btree ("shopid", "useremail", "active");

View File

@@ -123,7 +123,11 @@ app.post(
twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }),
smsStatus.status
);
app.post("/sms/markConversationRead", smsStatus.markConversationRead);
app.post(
"/sms/markConversationRead",
fb.validateFirebaseIdToken,
smsStatus.markConversationRead
);
var job = require("./server/job/job");
app.post("/job/totals", fb.validateFirebaseIdToken, job.totals);
@@ -147,11 +151,11 @@ app.post("/scheduling/job", fb.validateFirebaseIdToken, scheduling.job);
var inlineCss = require("./server/render/inlinecss");
app.post("/render/inlinecss", fb.validateFirebaseIdToken, inlineCss.inlinecss);
app.post(
"/notifications/send",
// app.post(
// "/notifications/send",
fb.sendNotification
);
// fb.sendNotification
// );
app.post("/notifications/subscribe", fb.validateFirebaseIdToken, fb.subscribe);
app.post(
"/notifications/unsubscribe",
@@ -188,13 +192,13 @@ app.post(
);
//Stripe Processing
var stripe = require("./server/stripe/payment");
app.post("/stripe/payment", fb.validateFirebaseIdToken, stripe.payment);
app.post(
"/stripe/mobilepayment",
fb.validateFirebaseIdToken,
stripe.mobile_payment
);
// var stripe = require("./server/stripe/payment");
// app.post("/stripe/payment", fb.validateFirebaseIdToken, stripe.payment);
// app.post(
// "/stripe/mobilepayment",
// fb.validateFirebaseIdToken,
// stripe.mobile_payment
// );
//Tech Console
var tech = require("./server/tech/tech");
@@ -202,7 +206,7 @@ app.post("/tech/login", fb.validateFirebaseIdToken, tech.techLogin);
var utils = require("./server/utils/utils");
app.post("/utils/time", utils.servertime);
app.post("/utils/jsr", fb.validateFirebaseIdToken, utils.jsrAuth);
var qbo = require("./server/accounting/qbo/qbo");
app.post("/qbo/authorize", fb.validateFirebaseIdToken, qbo.authorize);
app.get("/qbo/callback", qbo.callback);
@@ -215,7 +219,7 @@ app.post("/data/ah", data.autohouse);
app.post("/record-handler/arms", data.arms);
var taskHandler = require("./server/tasks/tasks");
app.post("/taskHandler", taskHandler.taskHandler);
app.post("/taskHandler", fb.validateFirebaseIdToken, taskHandler.taskHandler);
var mixdataUpload = require("./server/mixdata/mixdata");
@@ -228,10 +232,10 @@ app.post(
var ioevent = require("./server/ioevent/ioevent");
app.post("/ioevent", ioevent.default);
app.post("/newlog", (req, res) => {
const { message, type, user, record, object } = req.body;
logger.log(message, type, user, record, object);
});
// app.post("/newlog", (req, res) => {
// const { message, type, user, record, object } = req.body;
// logger.log(message, type, user, record, object);
// });
var os = require("./server/opensearch/os-handler");
app.post(

View File

@@ -164,7 +164,7 @@ async function PbsCalculateAllocationsAp(socket, billids) {
let APAmount = Dinero();
Object.keys(billHash).map((key) => {
if (billHash[key].Amount.getAmount() > 0) {
if (billHash[key].Amount.getAmount() > 0 || billHash[key].Amount.getAmount() < 0) {
transactionObject.Posting.Lines.push({
...billHash[key],
Amount: billHash[key].Amount.toFormat("0.00"),

View File

@@ -50,7 +50,7 @@ async function getEntegralShopData() {
}
exports.default = async (req, res) => {
res.sendStatus(200);
res.sendStatus(401);
return;
//Query for the List of Bodyshop Clients.
const job = req.body.event.data.new;

View File

@@ -40,6 +40,10 @@ exports.default = async (req, res) => {
const specificShopIds = req.body.bodyshopIds; // ['uuid]
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
res.sendStatus(401);
return;
}
const allxmlsToUpload = [];
const allErrors = [];
try {
@@ -772,7 +776,9 @@ const CreateCosts = (job) => {
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = Dinero({
amount: (job.mixdata[0] && job.mixdata[0].totalliquidcost * 100) || 0,
amount: Math.round(
((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100
),
});
} else {
billTotalsByCostCenters[
@@ -799,7 +805,7 @@ const CreateCosts = (job) => {
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mapa * 100) ||
0,
}).multiply(materialsHours.mapaHrs)
}).multiply(job.job_totals.rates.mapa.hours)
);
}
}

View File

@@ -28,7 +28,7 @@ exports.sendServerEmail = async function ({ subject, text }) {
transporter.sendMail(
{
from: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
to: ["patrick@imexsystems.ca"],
to: ["patrick@imexsystems.ca", "support@thinkimex.com"],
subject: subject,
text: text,
ses: {

View File

@@ -612,7 +612,12 @@ function GenerateCostingData(job) {
//If the hourly rates for job costing are set, add them in.
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa) {
if (
job.bodyshop.jc_hourly_rates &&
(job.bodyshop.jc_hourly_rates.mapa ||
typeof job.bodyshop.jc_hourly_rates.mapa === "number" ||
isNaN(job.bodyshop.jc_hourly_rates.mapa) === false)
) {
if (
!billTotalsByCostCenters.additionalCosts[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
@@ -626,7 +631,9 @@ function GenerateCostingData(job) {
billTotalsByCostCenters.additionalCosts[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = Dinero({
amount: (job.mixdata[0] && job.mixdata[0].totalliquidcost * 100) || 0,
amount: Math.round(
((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100
),
});
} else {
billTotalsByCostCenters.additionalCosts[

View File

@@ -17,7 +17,7 @@ require("dotenv").config({
});
async function StatusTransition(req, res) {
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
res.status(403).send("Unauthorized");
res.status(401).send("Unauthorized");
return;
}
res.sendStatus(200);

View File

@@ -45,6 +45,10 @@ const getClient = async () => {
};
async function OpenSearchUpdateHandler(req, res) {
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
res.status(401).send("Unauthorized");
return;
}
try {
var osClient = await getClient();
// const osClient = new Client({

View File

@@ -1,3 +1,12 @@
exports.servertime = (req, res) => {
res.status(200).send(new Date());
};
exports.jsrAuth = async (req, res) => {
res.send(
"Basic " +
Buffer.from(
`${process.env.JSR_USER}:${process.env.JSR_PASSWORD}`
).toString("base64")
);
};