This commit is contained in:
2024-07-26 12:59:11 +05:00
commit b3e6ff5e9c
856 changed files with 30776 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
import { connect } from "mongoose";
async function connectDB() {
try {
await connect(process.env.MONGO_URI!, { dbName: "irth" });
console.log("MongoDB connected...");
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
}
process.exit(1);
}
}
export default connectDB;
+25
View File
@@ -0,0 +1,25 @@
const refreshToken =
"1000.da3146d49fa8a399f0c635e74954ff9c.e010dbb1bb605d7e1aa5bf7fc0521f8b";
const clientId = "1000.6ZV07WFOC7PQOY3X109UN55Q9BMBBY";
const clientSecret = "595f5262886a6e81475b533350a81e46fecda57fb5";
const grantType = "refresh_token";
const updateAccessTokenApi = `https://accounts.zoho.com/oauth/v2/token?refresh_token=${refreshToken}&client_id=${clientId}&client_secret=${clientSecret}&grant_type=${grantType}`;
const aparmentsApi =
"https://www.zohoapis.com/crm/v6/Apartments?fields=Floor,Property_Status,Project_Name,Balcony_Area_Sqft,Unit_Type,Suite_Area_Sqft,No_Of_Bedrooms,Total_Area_Sqft,No_of_Bathrooms,Property_Name,Unit_View,Balcony_Area_Sqft,Unit_No,Suite_Area_Sqft";
const searchApartmentApi =
"https://www.zohoapis.com/crm/v6/Apartments/search?fields=Floor,Property_Status,Project_Name,Balcony_Area_Sqft,Unit_Type,Suite_Area_Sqft,No_Of_Bedrooms,Total_Area_Sqft,No_of_Bathrooms,Property_Name,Unit_View,Balcony_Area_Sqft,Unit_No,Suite_Area_Sqft";
const searchCurrentApartmentApi = "https://www.zohoapis.com/crm/v6/Apartments";
export {
updateAccessTokenApi,
aparmentsApi,
refreshToken,
clientId,
clientSecret,
grantType,
searchApartmentApi,
searchCurrentApartmentApi,
};
+302
View File
@@ -0,0 +1,302 @@
[
{
"unitName": "RHMD-8E-06",
"bedrooms": "1 BR Squared",
"floor": 8,
"position": "Front",
"suiteArea": 487,
"balconyArea": 121,
"unitArea": 608,
"unitPrice": 1699888
},
{
"unitName": "RHMD-9E-01",
"bedrooms": "2 BR Squared",
"floor": 9,
"position": "Front",
"suiteArea": 737,
"balconyArea": 177,
"unitArea": 914,
"unitPrice": 2430888
},
{
"unitName": "RHMD-9W-06",
"bedrooms": "1 BR Squared",
"floor": 9,
"position": "Front",
"suiteArea": 496,
"balconyArea": 121,
"unitArea": 618,
"unitPrice": 1732888
},
{
"unitName": "RHMD-9W-15",
"bedrooms": "1 BR Squared",
"floor": 9,
"position": "Back",
"suiteArea": 520,
"balconyArea": 122,
"unitArea": 642,
"unitPrice": 1594888
},
{
"unitName": "RHMD-9W-17",
"bedrooms": "2 BR Squared",
"floor": 9,
"position": "Back",
"suiteArea": 729,
"balconyArea": 328,
"unitArea": " 1 058 ",
"unitPrice": 2364888
},
{
"unitName": "RHMD-11E-14",
"bedrooms": "Studio Squared",
"floor": 11,
"position": "Back",
"suiteArea": 339,
"balconyArea": 78,
"unitArea": 416,
"unitPrice": 1175888
},
{
"unitName": "RHMD-11W-12",
"bedrooms": "Studio Flex",
"floor": 11,
"position": "Back",
"suiteArea": 284,
"balconyArea": 72,
"unitArea": 356,
"unitPrice": 1054888
},
{
"unitName": "RHMD-12W-01",
"bedrooms": "2 BR Squared",
"floor": 12,
"position": "Front",
"suiteArea": 737,
"balconyArea": 177,
"unitArea": 914,
"unitPrice": 2454888
},
{
"unitName": "RHMD-12W-07",
"bedrooms": "1 BR Squared",
"floor": 12,
"position": "Front",
"suiteArea": 492,
"balconyArea": 129,
"unitArea": 622,
"unitPrice": 1784888
},
{
"unitName": "RHMD-12W-13",
"bedrooms": "Studio Flex",
"floor": 12,
"position": "Back",
"suiteArea": 273,
"balconyArea": 68,
"unitArea": 341,
"unitPrice": 1014888
},
{
"unitName": "RHMD-12W-17",
"bedrooms": "2 BR Squared",
"floor": 12,
"position": "Back",
"suiteArea": 729,
"balconyArea": 328,
"unitArea": " 1 058 ",
"unitPrice": 2388888
},
{
"unitName": "RHMD-15W-04",
"bedrooms": "Studio Squared",
"floor": 15,
"position": "Front",
"suiteArea": 320,
"balconyArea": 81,
"unitArea": 401,
"unitPrice": 1294888
},
{
"unitName": "RHMD-15W-16",
"bedrooms": "1 BR Squared",
"floor": 15,
"position": "Back",
"suiteArea": 487,
"balconyArea": 122,
"unitArea": 609,
"unitPrice": 1544888
},
{
"unitName": "RHMD-20E-02",
"bedrooms": "Studio Squared",
"floor": 20,
"position": "Front",
"suiteArea": 325,
"balconyArea": 78,
"unitArea": 403,
"unitPrice": 1339888
},
{
"unitName": "RHMD-21W-04",
"bedrooms": "Studio Squared",
"floor": 21,
"position": "Front",
"suiteArea": 320,
"balconyArea": 81,
"unitArea": 401,
"unitPrice": 1340888
},
{
"unitName": "RHMD-21W-10",
"bedrooms": "Studio Flex",
"floor": 21,
"position": "Back",
"suiteArea": 273,
"balconyArea": 68,
"unitArea": 341,
"unitPrice": 1052888
},
{
"unitName": "RHMD-24E-04",
"bedrooms": "Studio Squared",
"floor": 24,
"position": "Front",
"suiteArea": 320,
"balconyArea": 81,
"unitArea": 401,
"unitPrice": 1366888
},
{
"unitName": "RHMD-24W-10",
"bedrooms": "Studio Squared",
"floor": 24,
"position": "Back",
"suiteArea": 320,
"balconyArea": 81,
"unitArea": 401,
"unitPrice": 1202888
},
{
"unitName": "RHMD-24W-12",
"bedrooms": "1 BR Squared",
"floor": 24,
"position": "Back",
"suiteArea": 501,
"balconyArea": 119,
"unitArea": 619,
"unitPrice": 1605888
},
{
"unitName": "RHMD-25E-15",
"bedrooms": "1 BR Squared",
"floor": 25,
"position": "Back",
"suiteArea": 520,
"balconyArea": 122,
"unitArea": 642,
"unitPrice": 1674888
},
{
"unitName": "RHMD-26E-12",
"bedrooms": "Studio Squared",
"floor": 26,
"position": "Back",
"suiteArea": 319,
"balconyArea": 81,
"unitArea": 400,
"unitPrice": 1216888
},
{
"unitName": "RHMD-26E-14",
"bedrooms": "Studio Squared",
"floor": 26,
"position": "Back",
"suiteArea": 339,
"balconyArea": 78,
"unitArea": 416,
"unitPrice": 1264888
},
{
"unitName": "RHMD-26W-09",
"bedrooms": "1 BR Squared",
"floor": 26,
"position": "Back",
"suiteArea": 500,
"balconyArea": 118,
"unitArea": 618,
"unitPrice": 1624888
},
{
"unitName": "RHMD-26W-14",
"bedrooms": "1 BR Squared",
"floor": 26,
"position": "Back",
"suiteArea": 487,
"balconyArea": 122,
"unitArea": 609,
"unitPrice": 1635888
},
{
"unitName": "RHMD-27E-12",
"bedrooms": "Studio Squared",
"floor": 27,
"position": "Back",
"suiteArea": 319,
"balconyArea": 81,
"unitArea": 400,
"unitPrice": 1224888
},
{
"unitName": "RHMD-27E-13",
"bedrooms": "Studio Squared",
"floor": 27,
"position": "Back",
"suiteArea": 309,
"balconyArea": 78,
"unitArea": 386,
"unitPrice": 1181888
},
{
"unitName": "RHMD-28E-05",
"bedrooms": "Studio Squared",
"floor": 28,
"position": "Front",
"suiteArea": 319,
"balconyArea": 81,
"unitArea": 400,
"unitPrice": 1397888
},
{
"unitName": "RHMD-29W-13",
"bedrooms": "1 BR Squared",
"floor": 29,
"position": "Back",
"suiteArea": 520,
"balconyArea": 122,
"unitArea": 642,
"unitPrice": 1756888
},
{
"unitName": "RHMD-30W-11",
"bedrooms": "Studio Squared",
"floor": 30,
"position": "Back",
"suiteArea": 320,
"balconyArea": 81,
"unitArea": 401,
"unitPrice": 1248888
},
{
"unitName": "RHMD-30W-15",
"bedrooms": "2 BR Squared",
"floor": 30,
"position": "Back",
"suiteArea": 729,
"balconyArea": 328,
"unitArea": " 1 058 ",
"unitPrice": 2730888
}
]
+41
View File
@@ -0,0 +1,41 @@
import "dotenv/config";
import express, { json } from "express";
import cors from "cors";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import connectDB from "./config/db.js";
import morgan from "morgan";
import apartmentRoute from "./routes/apartment.js";
import apartmentsRoute from "./routes/apartments.js";
import updateAccessToken from "./routes/zohoAccessToken.js";
import unitsRoute from "./routes/unitsRoute.js";
// import updateApartmentsRoute from "./routes/updateApartmentsRoute.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
!fs.existsSync("logs") && fs.mkdirSync("logs");
const accessLogStream = fs.createWriteStream(
path.join(__dirname, "../logs/access.log"),
{ flags: "a" }
);
await connectDB();
const app = express();
const port = process.env.PORT || 3000;
app.use(cors());
app.use(json());
app.use(morgan("combined", { stream: accessLogStream }));
app.use("/apartments", apartmentsRoute);
app.use("/apartment", apartmentRoute);
app.use("/updateAccessToken", updateAccessToken);
app.use("/units", unitsRoute);
// app.use("/update-apartments", updateApartmentsRoute);
app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});
+60
View File
@@ -0,0 +1,60 @@
import { model, Schema } from "mongoose";
const unitSchema = new Schema(
{
propertyName: {
type: String,
},
projectName: {
type: String,
},
floor: {
type: Number,
},
unitNo: {
type: String,
},
propertyStatus: {
type: String,
},
unitType: {
type: String,
},
unitView: {
type: String,
},
furnished: {
type: String,
},
totalArea: {
type: Number,
},
suiteArea: {
type: Number,
},
balconyArea: {
type: Number,
},
bedrooms: {
type: Number,
},
bathrooms: {
type: Number,
},
parkingSpaces: {
type: Number,
},
unitPrice: {
type: Number,
},
},
{
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
const Unit = model("Unit", unitSchema);
export default Unit;
+88
View File
@@ -0,0 +1,88 @@
import { Router } from "express";
import { searchCurrentApartmentApi } from "../consts.js";
import { logger } from "../utils/logger.js";
import { IApartment } from "../types/apartment.js";
const router = Router();
function ConvertApartmentRes(apartment: IApartment) {
const convertedApartment: IApartment = {
Balcony_Area_Sqft: apartment.Balcony_Area_Sqft,
Floor: apartment.Floor,
Property_Status: apartment.Property_Status,
Unit_Type: apartment.Unit_Type,
Project_Name: apartment.Project_Name,
Suite_Area_Sqft: apartment.Suite_Area_Sqft,
No_Of_Bedrooms: apartment.No_Of_Bedrooms,
Unit_No: apartment.Unit_No,
id: apartment.id,
Total_Area_Sqft: apartment.Total_Area_Sqft,
No_of_Bathrooms: apartment.No_of_Bathrooms,
Property_Name: apartment.Property_Name,
Unit_View: apartment.Unit_View,
};
return convertedApartment;
}
router.get("/:id", async (req, res) => {
const accessToken = req?.headers?.authorization;
const { id } = req.params;
try {
const url = `${searchCurrentApartmentApi}/${id}`;
const requestHeaders: HeadersInit = new Headers();
requestHeaders.set("Authorization", `${accessToken}`);
const response = await fetch(url, {
headers: requestHeaders,
});
if (response.status === 404) {
return res
.status(404)
.json({ message: "Квартира не найдена", code: 404 });
}
if (response.status === 401) {
return res
.status(401)
.json({ message: "Неправильный токен или токен устарел", code: 401 });
}
const result = await response.json();
const convertedApartment = ConvertApartmentRes(result.data[0]);
return res.status(200).json({
message: "ok",
apartment: convertedApartment,
code: 200,
});
} catch (error) {
if (
(error as Error).message === "invalid oauth token" ||
(error as Error).message === "INVALID_TOKEN"
) {
console.log("error", error);
logger.error(error);
return res
.status(401)
.json({ message: "Неправильный токен или токен устарел", code: 401 });
}
if (
(error as Error).message ===
"Please check if the URL trying to access is a correct one"
) {
return res
.status(404)
.json({ message: "Квартира не найдена", code: 404 });
}
console.log("error", error);
logger.error(error);
return res.status(500).json({ message: "Server Error", code: 500 });
}
});
const apartmentRoute = router;
export default apartmentRoute;
+267
View File
@@ -0,0 +1,267 @@
import { Router } from "express";
import { aparmentsApi, searchCurrentApartmentApi } from "../consts.js";
import { logger } from "../utils/logger.js";
import { IApartment } from "../types/apartment.js";
const router = Router();
async function getAllApartments(
page: number,
accessToken: string,
apartments: IApartment[]
) {
const response = await fetch(`${aparmentsApi}&page=${page}&per_page=200`, {
headers: {
Authorization: accessToken,
},
});
const result = await response.json();
if (result?.code && result?.code === "INVALID_TOKEN") {
throw new Error("INVALID_TOKEN");
}
const updatedApartment = apartments.concat([...result.data]);
const isMoreRecords = result.info.more_records;
if (!isMoreRecords) {
return updatedApartment;
}
return await getAllApartments(page + 1, accessToken, updatedApartment);
}
function filterApartments(
apartments: IApartment[],
roveHome?: string[],
apartmentType?: string[],
costBetween?: string[],
totalAreaBetween?: string[],
floorBetween?: string[],
views?: string[]
) {
const filteredApartments = apartments
.filter((apart) => {
// Rove home filter
if (!roveHome || roveHome.length === 0) {
return true;
}
if (roveHome.some((rove) => rove === apart.Project_Name)) {
return true;
}
return false;
})
.filter((apart) => {
// Apartment type filter
if (!apartmentType || apartmentType.length === 0) {
return true;
}
if (apartmentType.some((rove) => rove === apart.Unit_Type)) {
return true;
}
return false;
})
.filter((apart) => {
// Cost between filter еще не доступен тк нет цен
if (!costBetween || costBetween.length === 0) {
return true;
}
const leftCost = Number(costBetween[0]);
const rightCost = Number(costBetween[1]);
return true;
// if (costBetween.some((rove) => rove === apart.Unit_Type)) {
// return true;
// }
// return false;
})
.filter((apart) => {
// Total area filter
if (!totalAreaBetween || totalAreaBetween.length === 0) {
return true;
}
const leftArea = Number(totalAreaBetween[0]);
const rightArea = Number(totalAreaBetween[1]);
if (
(leftArea <= apart.Total_Area_Sqft &&
rightArea >= apart.Total_Area_Sqft) ||
(leftArea >= apart.Total_Area_Sqft &&
rightArea <= apart.Total_Area_Sqft)
) {
return true;
}
return false;
})
.filter((apart) => {
// Views filter
if (!views || views.length === 0) {
return true;
}
//view "Canal" "Amenities"
// Canal / Amenities
// разделение по / с пробелом и без
const apartViews = apart.Unit_View.split(" / ").join("/").split("/");
for (let i = 0; i < apartViews.length; i++) {
// Удаление Corner-
const withoutCorner =
apartViews[i].split("-").length > 1
? apartViews[i].split("-")[1]
: apartViews[i];
//удаление View
const withoutView = withoutCorner.replace(" View", "");
if (views.some((view) => view === withoutView)) {
return true;
}
}
return false;
})
.filter((apart) => {
// Floor filter
if (!floorBetween || floorBetween.length === 0) {
return true;
}
const leftFloor = Number(floorBetween[0]);
const rightFloor = Number(floorBetween[1]);
if (
(leftFloor <= apart.Floor && rightFloor >= apart.Floor) ||
(leftFloor >= apart.Floor && rightFloor <= apart.Floor)
) {
return true;
}
return false;
});
return filteredApartments;
}
router.get("/", async (req, res) => {
const accessToken = req?.headers?.authorization;
const {
per_page = 1000,
page = 1,
rove_home = "",
apartment_type = "",
cost_between = "",
total_area_between = "",
floor_between = "",
views = "",
sort_by = "",
} = req.query;
try {
const roveHomeFilter = (rove_home as string)
.split(",")
.filter((rove) => rove !== "");
const apartmentTypeFilter = (apartment_type as string)
.split(",")
.filter((rove) => rove !== "");
const costBetweenFilter = (cost_between as string)
.split(",")
.filter((cost) => cost !== "");
const totalAreaBetween = (total_area_between as string)
.split(",")
.filter((area) => area !== "");
const viewsFilter = (views as string)
.split(",")
.filter((view) => view !== "");
const floorBetweenFilter = (floor_between as string)
.split(",")
.filter((floor) => floor !== "");
if (!accessToken)
return res
.status(401)
.json({ message: "Отсутсвует access token", code: 401 });
try {
const allApartments = await getAllApartments(1, accessToken, []);
const filteredApartments = filterApartments(
allApartments,
roveHomeFilter,
apartmentTypeFilter,
costBetweenFilter,
totalAreaBetween,
floorBetweenFilter,
viewsFilter
);
const sortedApartments = [...filteredApartments].sort((a, b) => {
if (sort_by === "asc_price") {
return 1;
}
if (sort_by === "decr_price") {
return 1;
}
if (sort_by === "asc_sqr") {
return a.Total_Area_Sqft - b.Total_Area_Sqft;
}
if (sort_by === "asc_floor") {
return a.Floor - b.Floor;
}
return 1;
});
const slicedApartments = sortedApartments.slice(
((page as number) - 1) * (per_page as number),
(per_page as number) * (page as number)
);
res.status(200).json({
message: "ok",
apartments: slicedApartments,
code: 200,
});
return;
} catch (error) {
if (
(error as Error).message === "invalid oauth token" ||
(error as Error).message === "INVALID_TOKEN"
) {
console.log("error", error);
logger.error(error);
return res
.status(401)
.json({ message: "Неправильный токен или токен устарел", code: 401 });
}
console.log("error", error);
logger.error(error);
return res.status(500).json({ message: "Server Error", code: 500 });
}
} catch (error) {
logger.error(error);
console.log("error", error);
}
});
const apartmentsRoute = router;
export default apartmentsRoute;
+28
View File
@@ -0,0 +1,28 @@
import { Router } from "express";
import Unit from "../models/Unit.js";
const router = Router();
router.get("/", async (req, res) => {
try {
const units = await Unit.find(req.query);
res.json(units);
} catch (error) {
res.json({ error: (error as Error).message });
}
});
router.get("/:id", async (req, res) => {
try {
const unit = await Unit.findById(req.params.id);
res.json(unit);
} catch (error) {
res.json({ error: (error as Error).message });
}
});
const unitsRoute = router;
export default unitsRoute;
@@ -0,0 +1,30 @@
import { Router } from "express";
import data from "../data/irth_unit_pirces.json" assert { type: "json" };
import Unit from "../models/Unit.js";
const router = Router();
router.get("/", async (req, res) => {
for (const item of data) {
const result = await Unit.findOneAndUpdate(
{ propertyName: item.unitName },
{ unitPrice: item.unitPrice },
{ new: true }
);
console.log("result", result);
}
// const file = fs.readFileSync(
// path.resolve("./src/data/irth_unit_pirces.json"),
// { encoding: "utf8" }
// );
// const data = JSON.parse(file);
res.json({ ok: 1 });
});
const updateApartmentsRoute = router;
export default updateApartmentsRoute;
+21
View File
@@ -0,0 +1,21 @@
import { Request, Response, NextFunction } from "express";
import { updateAccessTokenApi } from "../consts.js";
import { logger } from "../utils/logger.js";
var updateAccessToken = async function (req: Request, res: Response) {
try {
const response = await fetch(updateAccessTokenApi, {
method: "post",
});
const { access_token } = await response.json();
return res.json({ accessToken: access_token });
} catch (error) {
console.log("error", (error as Error).message);
logger.error(error);
return res.json({ error: (error as Error).message });
}
};
export default updateAccessToken;
+17
View File
@@ -0,0 +1,17 @@
interface IApartment {
Floor: number;
Property_Status: string;
Unit_Type: string;
Project_Name: string;
Suite_Area_Sqft: number;
Balcony_Area_Sqft: number;
No_Of_Bedrooms: number;
Unit_No: string;
id: string;
Total_Area_Sqft: number;
No_of_Bathrooms: number;
Property_Name: string;
Unit_View: string;
}
export type { IApartment };
+27
View File
@@ -0,0 +1,27 @@
import * as winston from "winston";
const { combine, timestamp, json } = winston.format;
export const logger = winston.createLogger({
level: "info",
format: combine(
timestamp({
format: "YYYY-MM-DD hh:mm:ss.SSS A",
}),
json()
),
transports: [
//
// - Write to all logs with level `debug` and below to `all.log`
// - Write all logs error (and below) to `error.log`.
//
new winston.transports.File({
filename: "logs/error.log",
level: "error",
}),
new winston.transports.File({
filename: "logs/all.log",
level: "debug",
}),
],
});