refactor model structure to remove boilerplate in the main file, created connection with the floriday staging API

This commit is contained in:
Pau 2022-12-19 13:21:35 +01:00
parent 9d20ba5314
commit 8473880eb1
7 changed files with 251 additions and 282 deletions

View File

@ -7,7 +7,7 @@ The Floriday service project should perform the following tasks:
1. Create / mantain the table structure to allow the storage of the data provided by the Floriday API.
This is done using the [Sequelize](https://sequelize.org/) ORM.
2. Query the Floriday API and store the data in the database.
This is done using the [Axios](https://axios-http.com/) module.
This is done using the [node-fetch](https://www.npmjs.com/package/node-fetch) package.
2.1. The data is requested every minute, but only the data that has changed is stored in the database.
This is done using the [Sequelize](https://sequelize.org/) ORM and storing the data as it is received from the API,
so it can be compared with the previous data.

314
index.js
View File

@ -1,219 +1,39 @@
import { Sequelize } from "sequelize";
import TradeItem from "./models/tradeItem.js";
import Characteristics from "./models/characteristics.js";
import SeasonalPeriod from "./models/seasonalPeriod.js";
import Photos from "./models/photos.js";
import PackagingConfigurations from "./models/packagingConfigurations.js";
import Package from "./models/package.js";
import AdditionalPricePerPiece from "./models/additionalPricePerPiece.js";
import BotanicalNames from "./models/botanicalNames.js";
import CountryOfOriginIsoCodes from "./models/countryOfOriginIsoCodes.js";
import fetch from "node-fetch";
import moment from "moment";
let sequelize = new Sequelize("edi", "root", "root", {
host: "localhost",
dialect: "mariadb",
logging: false,
});
const _accessTokenEndpoint =
"https://idm.staging.floriday.io/oauth2/ausmw6b47z1BnlHkw0h7/v1/token";
const tradeItemModel = TradeItem(sequelize);
const characteristicsModel = Characteristics(sequelize);
const seasonalPeriodModel = SeasonalPeriod(sequelize);
const photosModel = Photos(sequelize);
const packagingConfigurationsModel = PackagingConfigurations(sequelize);
const packageModel = Package(sequelize);
const additionalPricePerPieceModel = AdditionalPricePerPiece(sequelize);
const botanicalNamesModel = BotanicalNames(sequelize);
const countryOfOriginIsoCodesModel = CountryOfOriginIsoCodes(sequelize);
import models from "./models/index.js";
try {
// Just there to hide the mock data insertion
characteristicsModel.belongsTo(tradeItemModel, {
foreignKey: "tradeItemFk",
targetKey: "id",
onDelete: "CASCADE",
onUpdate: "CASCADE",
});
seasonalPeriodModel.belongsTo(tradeItemModel, {
foreignKey: "tradeItemFk",
targetKey: "id",
onDelete: "CASCADE",
onUpdate: "CASCADE",
});
photosModel.belongsTo(tradeItemModel, {
foreignKey: "tradeItemFk",
targetKey: "id",
onDelete: "CASCADE",
onUpdate: "CASCADE",
});
photosModel.belongsTo(seasonalPeriodModel, {
foreignKey: "seasonalPeriodFk",
targetKey: "id",
onDelete: "CASCADE",
onUpdate: "CASCADE",
});
packagingConfigurationsModel.belongsTo(tradeItemModel, {
foreignKey: "tradeItemFk",
targetKey: "id",
onDelete: "CASCADE",
onUpdate: "CASCADE",
});
packageModel.belongsTo(packagingConfigurationsModel, {
foreignKey: "packingConfigurationsFk",
targetKey: "id",
onDelete: "CASCADE",
onUpdate: "CASCADE",
});
additionalPricePerPieceModel.belongsTo(packagingConfigurationsModel, {
foreignKey: "packingConfigurationsFk",
targetKey: "id",
onDelete: "CASCADE",
onUpdate: "CASCADE",
});
botanicalNamesModel.belongsTo(tradeItemModel, {
foreignKey: "tradeItemFk",
targetKey: "id",
onDelete: "CASCADE",
onUpdate: "CASCADE",
});
countryOfOriginIsoCodesModel.belongsTo(tradeItemModel, {
foreignKey: "tradeItemFk",
targetKey: "id",
onDelete: "CASCADE",
onUpdate: "CASCADE",
});
await sequelize.sync({
// alter: true,
force: true,
});
const tradeItem = await tradeItemModel.create({
tradeItemId: "123",
supplierOrganizationId: "123",
code: "123",
gtin: "123",
vbnProductCode: "123",
name: "123",
isDeleted: false,
sequenceNumber: 1,
tradeItemVersion: 1,
isCustomerSpecific: false,
isHiddenInCatalog: false,
});
const characteristics1 = await characteristicsModel.create({
tradeItemFk: tradeItem.id,
vnbCode: "123",
vnbValueCode: "123",
});
const characteristics2 = await characteristicsModel.create({
tradeItemFk: tradeItem.id,
vnbCode: "234",
vnbValueCode: "234",
});
const seasonalPeriod1 = await seasonalPeriodModel.create({
tradeItemFk: tradeItem.id,
startWeek: "40",
endWeek: "42",
});
const seasonalPeriod2 = await seasonalPeriodModel.create({
tradeItemFk: tradeItem.id,
startWeek: "43",
endWeek: "45",
});
const photos1 = await photosModel.create({
tradeItemFk: tradeItem.id,
seasonalPeriodFk: seasonalPeriod1.id,
photoId: "123",
url: "123",
type: "123",
primary: false,
});
const photos2 = await photosModel.create({
tradeItemFk: tradeItem.id,
seasonalPeriodFk: seasonalPeriod2.id,
photoId: "234",
url: "234",
type: "234",
primary: false,
});
const packagingConfigurations1 = await packagingConfigurationsModel.create({
tradeItemFk: tradeItem.id,
piecesPerPackage: 1,
bunchesPerPackage: 1,
photoUrl: photos1.url,
});
const packagingConfigurations2 = await packagingConfigurationsModel.create({
tradeItemFk: tradeItem.id,
piecesPerPackage: 2,
bunchesPerPackage: 2,
photoUrl: photos2.url,
});
const package1 = await packageModel.create({
packingConfigurationsFk: packagingConfigurations1.id,
vbnPackageCode: "123",
customPackageId: "123",
});
const package2 = await packageModel.create({
packingConfigurationsFk: packagingConfigurations2.id,
vbnPackageCode: "234",
customPackageId: "234",
});
const additionalPricePerPiece1 = await additionalPricePerPieceModel.create({
packingConfigurationsFk: packagingConfigurations1.id,
value: 1,
currency: "EUR",
});
const additionalPricePerPiece2 = await additionalPricePerPieceModel.create({
packingConfigurationsFk: packagingConfigurations2.id,
value: 2,
currency: "USD",
});
const botanicalNames1 = await botanicalNamesModel.create({
tradeItemFk: tradeItem.id,
name: "test123",
});
const botanicalNames2 = await botanicalNamesModel.create({
tradeItemFk: tradeItem.id,
name: "test234",
});
const countryOfOriginIsoCodes1 = await countryOfOriginIsoCodesModel.create({
tradeItemFk: tradeItem.id,
isoCode: "ES",
});
const countryOfOriginIsoCodes2 = await countryOfOriginIsoCodesModel.create({
tradeItemFk: tradeItem.id,
isoCode: "DE",
});
} catch (error) {
console.log(error);
}
const AccessToken = getClientToken();
try {
// Every 30 sec query the database
setInterval(async () => {
console.log("Querying the API to check for new data...");
const query = tradeItemModel.findAll({
const query = models.tradeItem.findAll({
include: [
{
model: characteristicsModel,
association: tradeItemModel.hasMany(characteristicsModel, {
model: models.characteristics,
association: models.tradeItem.hasMany(models.characteristics, {
foreignKey: "tradeItemFk",
sourceKey: "id",
}),
as: "characteristics",
},
{
model: photosModel,
association: tradeItemModel.hasMany(photosModel, {
model: models.photos,
association: models.tradeItem.hasMany(models.photos, {
foreignKey: "tradeItemFk",
sourceKey: "id",
}),
as: "photos",
include: [
{
model: seasonalPeriodModel,
association: photosModel.hasOne(seasonalPeriodModel, {
model: models.seasonalPeriod,
association: models.photos.hasOne(models.seasonalPeriod, {
foreignKey: "id",
sourceKey: "seasonalPeriodFk",
}),
@ -222,25 +42,25 @@ try {
],
},
{
model: packagingConfigurationsModel,
association: tradeItemModel.hasMany(packagingConfigurationsModel, {
model: models.packagingConfigurations,
association: models.tradeItem.hasMany(models.packagingConfigurations, {
foreignKey: "tradeItemFk",
sourceKey: "id",
}),
as: "packagingConfigurations",
include: [
{
model: packageModel,
association: packagingConfigurationsModel.hasOne(packageModel, {
model: models.package,
association: models.packagingConfigurations.hasOne(models.package, {
foreignKey: "packingConfigurationsFk",
sourceKey: "id",
}),
as: "package",
},
{
model: additionalPricePerPieceModel,
association: packagingConfigurationsModel.hasOne(
additionalPricePerPieceModel,
model: models.additionalPricePerPiece,
association: models.packagingConfigurations.hasOne(
models.additionalPricePerPiece,
{
foreignKey: "packingConfigurationsFk",
sourceKey: "id",
@ -251,16 +71,16 @@ try {
],
},
{
model: botanicalNamesModel,
association: tradeItemModel.hasMany(botanicalNamesModel, {
model: models.botanicalNames,
association: models.tradeItem.hasMany(models.botanicalNames, {
foreignKey: "tradeItemFk",
sourceKey: "id",
}),
as: "botanicalNames",
},
{
model: countryOfOriginIsoCodesModel,
association: tradeItemModel.hasMany(countryOfOriginIsoCodesModel, {
model: models.countryOfOriginIsoCodes,
association: models.tradeItem.hasMany(models.countryOfOriginIsoCodes, {
foreignKey: "tradeItemFk",
sourceKey: "id",
}),
@ -276,3 +96,89 @@ try {
} catch (error) {
console.error("Unable to connect to the database:", error);
}
async function getClientToken() {
let clientConfigData = await models.clientConfig.findAll();
const now = moment().format("YYYY-MM-DD HH:mm:ss");
const tokenExpirationDate = clientConfigData[0].tokenExpiration;
console.log("tokenExpirationDate: ", tokenExpirationDate);
if (
clientConfigData[0].tokenExpiration == null ||
moment(now).isAfter(tokenExpirationDate)
) {
console.log("Getting a new token...");
let clientId = clientConfigData[0].clientId;
let clientSecret = clientConfigData[0].clientSecret;
const tokenRequest = await fetch(_accessTokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: `grant_type=client_credentials&client_id=${clientId}&client_secret=${clientSecret}&scope=role:app catalog:read`,
});
const tokenResponse = await tokenRequest.json();
const accessToken = tokenResponse.access_token;
let now = moment().format("YYYY-MM-DD HH:mm:ss");
let tokenExpirationDate = moment(now)
.add(tokenResponse.expires_in, "s")
.format("YYYY-MM-DD HH:mm:ss");
console.log(tokenResponse);
console.log("now: ", now);
console.log("tokenExpirationDate: ", tokenExpirationDate);
updateClientConfig(
clientId,
clientSecret,
accessToken,
tokenExpirationDate
);
return accessToken;
} else {
console.log("Using the current token...");
return clientConfigData[0].currentToken;
}
}
async function updateClientConfig(
clientId,
clientSecret,
accessToken,
tokenExpirationDate
) {
try {
await models.clientConfig.update(
{
clientId: clientId,
clientSecret: clientSecret,
currentToken: accessToken,
tokenExpiration: tokenExpirationDate,
},
{
where: {
id: 1,
},
}
);
console.log("Client config updated, new Token set");
console.log("New token expiration date: ", tokenExpirationDate );
} catch (error) {
console.log("There was a error while updating the client config");
console.log(error);
}
}
console.log = function () {
var args = Array.prototype.slice.call(arguments);
args.unshift(new moment().format("HH:mm:ss") + " -");
console.info.apply(console, args);
};

33
models/clientConfig.js Normal file
View File

@ -0,0 +1,33 @@
import { Sequelize } from "sequelize";
const clientConfig = {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
clientId: {
type: Sequelize.STRING,
},
clientSecret: {
type: Sequelize.STRING,
},
currentToken: {
type: Sequelize.STRING,
},
tokenExpiration: {
type: Sequelize.STRING,
},
};
export default (sequelize) => {
const ClientConfig = sequelize.define(
"FDClientConfig",
clientConfig,
{
timestamps: false,
freezeTableName: true,
}
);
return ClientConfig;
};

33
models/index.js Normal file
View File

@ -0,0 +1,33 @@
import { Sequelize } from "sequelize";
let sequelize = new Sequelize("edi", "root", "root", {
host: "localhost",
dialect: "mariadb",
logging: false,
});
import additionalPricePerPiece from "./additionalPricePerPiece.js";
import botanicalNames from "./botanicalNames.js";
import countryOfOriginIsoCodes from "./countryOfOriginIsoCodes.js";
import packageModel from "./package.js";
import packagingConfigurations from "./packagingConfigurations.js";
import photos from "./photos.js";
import seasonalPeriod from "./seasonalPeriod.js";
import tradeItem from "./tradeItem.js";
import clientConfig from "./clientConfig.js";
import characteristics from "./characteristics.js";
let models = {
additionalPricePerPiece: additionalPricePerPiece(sequelize),
botanicalNames: botanicalNames(sequelize),
countryOfOriginIsoCodes: countryOfOriginIsoCodes(sequelize),
package: packageModel(sequelize),
packagingConfigurations: packagingConfigurations(sequelize),
photos: photos(sequelize),
seasonalPeriod: seasonalPeriod(sequelize),
tradeItem: tradeItem(sequelize),
clientConfig: clientConfig(sequelize),
characteristics: characteristics(sequelize),
};
export default models;

View File

@ -16,6 +16,9 @@ const photos = {
primary: {
type: Sequelize.BOOLEAN,
},
seasonalPeriodFk: {
type: Sequelize.INTEGER,
},
};
export default (sequelize) => {

146
package-lock.json generated
View File

@ -6,9 +6,9 @@
"": {
"name": "floriday",
"dependencies": {
"axios": "^1.2.0",
"mariadb": "^3.0.2",
"moment": "^2.29.4",
"node-fetch": "^3.3.0",
"sequelize": "^6.26.0"
}
},
@ -40,30 +40,12 @@
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.10.tgz",
"integrity": "sha512-t1yxFAR2n0+VO6hd/FJ9F2uezAZVWHLmpmlJzm1eX03+H7+HsuTAp7L8QJs+2pQCfWkP1+EXsGK9Z9v7o/qPVQ=="
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.0.tgz",
"integrity": "sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
"engines": {
"node": ">= 0.8"
"node": ">= 12"
}
},
"node_modules/debug": {
@ -82,14 +64,6 @@
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
@ -103,36 +77,37 @@
"resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz",
"integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg=="
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=4.0"
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">= 6"
"node": ">=12.20.0"
}
},
"node_modules/iconv-lite": {
@ -183,25 +158,6 @@
"node": ">= 12"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
@ -226,16 +182,46 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.0.tgz",
"integrity": "sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/pg-connection-string": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/retry-as-promised": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-6.1.0.tgz",
@ -361,6 +347,14 @@
"node": ">= 0.10"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
"engines": {
"node": ">= 8"
}
},
"node_modules/wkx": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",

View File

@ -6,9 +6,9 @@
"start": "node index.js"
},
"dependencies": {
"axios": "^1.2.0",
"mariadb": "^3.0.2",
"moment": "^2.29.4",
"node-fetch": "^3.3.0",
"sequelize": "^6.26.0"
}
}