diff --git a/src/config/configuration.js b/src/config/configuration.js new file mode 100644 index 0000000..ad30fb4 --- /dev/null +++ b/src/config/configuration.js @@ -0,0 +1,51 @@ +const path = require("path"); +const Field = require("./field.js"); + +let instance = null; + +class Config { + constructor() { + this.location = Config.determineLocation(); + this.fields = require(`${this.location}`); + } + + static getInstance() { + if (instance == null) { + instance = new Config(); + } + return instance; + } + + static determineLocation() { + if (process.env.NODE_ENV === "production") + return path.join(__dirname, "../../config/env/production.json"); + return path.join(__dirname, "../../config/env/development.json"); + } + + get(section, option) { + if ( + this.fields[section] === undefined || + this.fields[section][option] === undefined + ) { + throw new Error(`Field "${section} => ${option}" does not exist.`); + } + + const field = new Field(this.fields[section][option]); + + if (field.value === "") { + const envField = + process.env[[section.toUpperCase(), option.toUpperCase()].join("_")]; + if (envField !== undefined && envField.length !== 0) { + return envField; + } + } + + if (field.value === undefined) { + throw new Error(`${section} => ${option} is empty.`); + } + + return field.value; + } +} + +module.exports = Config; diff --git a/src/config/environmentVariables.js b/src/config/environmentVariables.js new file mode 100644 index 0000000..22d467f --- /dev/null +++ b/src/config/environmentVariables.js @@ -0,0 +1,15 @@ +class EnvironmentVariables { + constructor(variables) { + this.variables = variables || process.env; + } + + get(variable) { + return this.variables[variable]; + } + + has(variable) { + return this.get(variable) !== undefined; + } +} + +module.exports = EnvironmentVariables; diff --git a/src/config/field.js b/src/config/field.js new file mode 100644 index 0000000..546513b --- /dev/null +++ b/src/config/field.js @@ -0,0 +1,53 @@ +const Filters = require("./filters.js"); +const EnvironmentVariables = require("./environmentVariables.js"); + +class Field { + constructor(rawValue, environmentVariables) { + this.rawValue = rawValue; + this.filters = new Filters(rawValue); + this.valueWithoutFilters = this.filters.removeFiltersFromValue(); + this.environmentVariables = new EnvironmentVariables(environmentVariables); + } + + get value() { + if (this.filters.isEmpty()) { + return this.valueWithoutFilters; + } + + if (this.filters.has("base64") && !this.filters.has("env")) { + return Field.base64Decode(this.valueWithoutFilters); + } + + if ( + this.environmentVariables.has(this.valueWithoutFilters) && + this.environmentVariables.get(this.valueWithoutFilters) === "" + ) { + return undefined; + } + + if (!this.filters.has("base64") && this.filters.has("env")) { + if (this.environmentVariables.has(this.valueWithoutFilters)) { + return this.environmentVariables.get(this.valueWithoutFilters); + } + return undefined; + } + + if (this.filters.has("env") && this.filters.has("base64")) { + if (this.environmentVariables.has(this.valueWithoutFilters)) { + const encodedEnvironmentVariable = this.environmentVariables.get( + this.valueWithoutFilters + ); + return Field.base64Decode(encodedEnvironmentVariable); + } + return undefined; + } + + return this.valueWithoutFilters; + } + + static base64Decode(string) { + return new Buffer(string, "base64").toString("utf-8"); + } +} + +module.exports = Field; diff --git a/src/config/filters.js b/src/config/filters.js new file mode 100644 index 0000000..8d5fad1 --- /dev/null +++ b/src/config/filters.js @@ -0,0 +1,34 @@ +class Filters { + constructor(value) { + this.value = value; + this.delimiter = "|"; + } + + get filters() { + return this.value.split(this.delimiter).slice(0, -1); + } + + isEmpty() { + return !this.hasValidType() || this.value.length === 0; + } + + has(filter) { + return this.filters.includes(filter); + } + + hasValidType() { + return typeof this.value === "string"; + } + + removeFiltersFromValue() { + if (this.hasValidType() === false) { + return this.value; + } + + let filtersCombined = this.filters.join(this.delimiter); + filtersCombined += this.filters.length >= 1 ? this.delimiter : ""; + return this.value.replace(filtersCombined, ""); + } +} + +module.exports = Filters; diff --git a/src/enums/generic.js b/src/enums/generic.js new file mode 100644 index 0000000..3ab1da5 --- /dev/null +++ b/src/enums/generic.js @@ -0,0 +1,24 @@ +const GENERIC_STATUS_CODES = { + NON_EXISTANT: "NON_EXISTANT", + NOT_STARTED: "NOT_STARTED", + RESERVED: "RESERVED", + RESERVE_FAILED: "RESERVE_FAILED", + PARTIALLY_CAPTURED: "PARTIALLY_CAPTURED", + CAPTURED: "CAPTURED", + INITIATED: "INITIATED", + CONFIRMED: "CONFIRMED", + COMPLETED: "COMPLETED", + CANCELLED: "CANCELLED", + CANCELLED_CONFLICTING: "CANCELLED_CONFLICTING", + CANCEL_REQUESTED: "CANCEL_REQUESTED", + FAILED: "FAILED", + REFUNDED: "REFUNDED", + REJECTED: "REJECTED", + TIMED_OUT_REJECT: "TIMED_OUT_REJECT", + REFUNDED_PARTIAL: "REFUNDED_PARTIAL", + REFUND_REQUESTED: "REFUND_REQUESTED", +}; + +module.exports = { + GENERIC_STATUS_CODES, +}; diff --git a/src/enums/order.js b/src/enums/order.js new file mode 100644 index 0000000..157ac9c --- /dev/null +++ b/src/enums/order.js @@ -0,0 +1,9 @@ +const { GENERIC_STATUS_CODES } = require(`./generic`); + +const ORDER_STATUS_CODES = { + ...GENERIC_STATUS_CODES, +}; + +module.exports = { + ORDER_STATUS_CODES, +}; diff --git a/src/enums/vipps.js b/src/enums/vipps.js new file mode 100644 index 0000000..e15c8cb --- /dev/null +++ b/src/enums/vipps.js @@ -0,0 +1,9 @@ +const { GENERIC_STATUS_CODES } = require(`./generic`); + +const VIPPS_STATUS_CODES = { + ...GENERIC_STATUS_CODES, +}; + +module.exports = { + VIPPS_STATUS_CODES, +}; diff --git a/src/errors/index.js b/src/errors/index.js new file mode 100644 index 0000000..4208337 --- /dev/null +++ b/src/errors/index.js @@ -0,0 +1,28 @@ +const WAGO_ERROR_STATUS_CODES = { + CONFLICTING_ORDER_RESERVATION: { + status: "CONFLICTING_ORDER_RESERVATION", + message: + "An order for this product has been already reserved, before this order that is being processed.", + }, + UNKNOWN_ERROR: { + status: "UNKNOWN_ERROR", + message: "An unknown error has occured", + }, + NO_CAPTURED_PAYMENTS: { + status: "NO_CAPTURED_PAYMENTS", + message: "No captured payments has been found for this order.", + }, +}; + +class WagoError extends Error { + constructor(error = WAGO_ERROR_STATUS_CODES.UNKNOWN_ERROR) { + super(error.message); + + this.error = error; + } +} + +module.exports = { + WagoError, + WAGO_ERROR_STATUS_CODES, +}; diff --git a/src/interfaces/ICart.ts b/src/interfaces/ICart.ts new file mode 100644 index 0000000..fa9ac43 --- /dev/null +++ b/src/interfaces/ICart.ts @@ -0,0 +1,15 @@ +export default interface ICart { + client_id: string; + cart_id: number; + lineitem_id: number; + quantity: number; + sku_id: number; + size: string; + price: number; + product_no: number; + name: string; + description: string; + subtext: string; + image: string; + primary_color: string; +} diff --git a/src/interfaces/ICustomer.ts b/src/interfaces/ICustomer.ts new file mode 100644 index 0000000..8e38bbf --- /dev/null +++ b/src/interfaces/ICustomer.ts @@ -0,0 +1,9 @@ +export default interface ICustomer { + city: string; + customer_no?: string; + email: string; + first_name: string; + last_name: string; + street_address: string; + zip_code: number; +} diff --git a/src/interfaces/IDeliveryAddress.ts b/src/interfaces/IDeliveryAddress.ts new file mode 100644 index 0000000..e931af3 --- /dev/null +++ b/src/interfaces/IDeliveryAddress.ts @@ -0,0 +1,7 @@ +export default interface IDeliveryAddress { + firstName: string; + lastName: string; + address: string; + zipCode: string; + city: string; +} diff --git a/src/interfaces/IOrder.ts b/src/interfaces/IOrder.ts new file mode 100644 index 0000000..8a7e415 --- /dev/null +++ b/src/interfaces/IOrder.ts @@ -0,0 +1,61 @@ +// import type IProduct from './IProduct'; +// import type BadgeType from './BadgeType'; +import type ICustomer from "./ICustomer"; + +export interface IStripePayment { + amount: number; + currency: string; +} + +export interface IOrderSummary { + created: Date; + email: string; + first_name: string; + last_name: string; + order_id: string; + order_sum: number; + status: string; +} + +export interface IOrder { + customer: ICustomer; + lineItems: ILineItem[]; + orderid: string; + shipping: IShipping; + status: string; + updated?: Date; + created?: Date; +} + +export interface ILineItem { + sku_id: number; + image: string; + name: string; + price: number; + quantity: number; + size: string; +} + +export interface IShipping { + company: string; + tracking_code: string; + tracking_link: string; + user_notified: null; +} + +export interface IOrdersLineitem { + orders_lineitem_id: string; + order_id: string; + product_no: number; + product_sku_no: number; + quantity: number; + created?: Date; + updated?: Date; +} + +export interface ITracking { + orderId: string; + trackingCode: string; + trackingCompany: string; + trackingLink: string; +} diff --git a/src/interfaces/IProduct.ts b/src/interfaces/IProduct.ts new file mode 100644 index 0000000..25248c2 --- /dev/null +++ b/src/interfaces/IProduct.ts @@ -0,0 +1,25 @@ +export interface IProduct { + product_no: number; + name: string; + subtext?: string; + description?: string; + image?: string; + variation_count?: string; + sum_stock?: number; + updated?: Date; + created?: Date; +} + +export interface IProductSku { + sku_id: number; + price: number; + size: number; + stock: number; + default_price: boolean; + updated?: Date; + created?: Date; +} + +export interface IProductWithSkus extends IProduct { + variations: IProductSku[] | number; +} diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..d767b62 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,61 @@ +import winston from "winston"; +import ecsFormat from "@elastic/ecs-winston-format"; +import httpContext from "express-http-context"; + +const logLevel = "debug"; +// const logLevel = 'trace'; + +const customLevels = { + levels: { + fatal: 0, + error: 1, + warning: 2, + info: 3, + debug: 4, + trace: 5, + }, + colors: { + trace: "blue", + debug: "white", + info: "green", + warning: "yellow", + error: "red", + fatal: "red", + }, +}; + +const appendPlanetId = winston.format((log) => { + log.planetId = httpContext.get("planetId"); + return log; +}); + +const appName = winston.format((log) => { + log.application = "planetposen-backend"; + return log; +}); + +const logger = winston.createLogger({ + level: logLevel, + levels: customLevels.levels, + transports: [ + new winston.transports.File({ + filename: "./logs/all-logs.log", + format: winston.format.combine(appendPlanetId(), appName(), ecsFormat()), + }), + ], +}); + +winston.addColors(customLevels.colors); + +if (process.env.NODE_ENV !== "production") { + logger.add( + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.simple() + ), + }) + ); +} + +export default logger; diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000..f761995 --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,11 @@ +declare global { + interface NodeJS { + __base: string; + __dirname: string; + __enums: string; + __middleware: string; + __controllers: string; + } +} + +export {}; diff --git a/src/utils/formValidation.ts b/src/utils/formValidation.ts new file mode 100644 index 0000000..6e34add --- /dev/null +++ b/src/utils/formValidation.ts @@ -0,0 +1,4 @@ +export function validEmail(email: string): boolean { + const emailRegex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; + return emailRegex.test(email); +} diff --git a/src/utils/generateUUID.ts b/src/utils/generateUUID.ts new file mode 100644 index 0000000..3bf49cb --- /dev/null +++ b/src/utils/generateUUID.ts @@ -0,0 +1,10 @@ +const hex = "0123456789abcdef"; + +export default function generateClientId(len = 22) { + let output = ""; + for (let i = 0; i < len; ++i) { + output += hex.charAt(Math.floor(Math.random() * hex.length)); + } + + return output; +}