🧭 Full Stack TypeScript Application Developed as POC

Overview

Logo

Tractian Challenge POC

Full Stack Development Project
Browse Back End code» - Browse Front End code»

Built With


Table of Contents

Deployed Instances

  • Server https://tractian-poc.herokuapp.com/
  • Client https://tractian-poc.vercel.app/

Installation and Usage

Pre-requisites: Node.js ^16.14.0, TypeScript ^4.7.4

Download the zip file and extract it in the root of a new project folder by running these commands:

wget https://github.com/NivaldoFarias/tractian-challenge/archive/main.zip

Then run the following command to install the project's dependencies:

npm install

That's it! You can now start developing your TypeScript Project by running the command below. Happy coding!

npm run dev

Error Handling and Logging

While dealing with errors in a Layered Structure Project enviroment, you may notice that the project's debugging complexity scales beyond common console.log() usage. The AppLog Object and AppError Object structures were set to counter that exact issue, by trying to keep the Development process as clean and concise as possible. Both are frequently referenced in the code, but do have a specific usage.

▸   AppError

An AppError Object is used to handle errors in the application. It that takes four parameters:

  • log: A string containing a simplified error message, for Server side use. This is the message that will be used by the AppLog Object
  • statusCode: An integer containing the HTTP status code.
  • message: A string containing a simplified error message, for Client side use. This is the message that will be displayed to the user.
  • details: A string containing a detailed error message, for Client side use. Can be used to provide more information about the error, such as the stack trace, or suggestions on how to counter the error.
Example Usage
  // ..../middlewares/auth.middleware.ts

  import * as repository from './../repositories/auth.repository.ts';
  import AppError from './../events/AppError';
  ...
  ..

  async function usersExists(req: Request,...){
    ...
    ..
    const user = await repository.findbyId(req.body.id);

    if (!user){
      throw new AppError(
        'User not found',
        404,
        'User not found',
        'Ensure to provide a valid user ID.'
      );
    }
    ..
    ...
  }

▸   AppLog

An AppLog Object is used to handle logs in the application. It takes two parameters:

  • type: A string containing the main Layer Structure that contains the log. There are seven allowed values: Error, Server, Controller, Middleware, Repository, Service, and Util.
  • text: A descriptive string containing the log message. Generally, a short message that describes the output event of the function that generated the log.
Example Usage
  // ..../middlewares/auth.middleware.ts

  import AppLog from './events/AppLog';
  ...
  ..

  async function usersExists(req: Request,...){
    ...
    ..

    // output: [Middleware] User Found
    AppLog('Middleware', 'User found');
    res.locals.user = user;
    return next();
  }
  ..
  ...

Middlewares

While aiming to provide a reusable, modular and extensible architecture, the middlewares are generally the first structures to be refactored into self-contained modules. The validateSchema(), processHeader() and requireToken() middlewares were set in order to achieve that goal. The following section describes useMiddleware(), which incorporates the forementioned functions as key–value pairs in an Object, along with their structure and usage.

‣  UseMiddleware

The useMiddleware() function takes two parameters:

  • middlewares: An Object containing the key–value pairs of the middlewares to be used, takes one to three parameters:
    • schema: A Joi Schema Object that will be used to validate the data provided by the client. If the data provided by the client is not valid, an AppError Object will be thrown.
    • header: A string containing the name of the header that will be used to authenticate the action. If the client-provided header is missing, an AppError Object will be thrown.
    • token: A boolean indicating whether the token provided by the client will be verified or not. If the token is not valid, an AppError Object will be thrown.
  • endpoint: A string that will be used to identify the endpoint at which the client–api interaction is undergoing, which will be logged to console by the AppLog Object.
Reference: useMiddleware function declaration
Example Usage
// ..../routes/admin.route.ts
import useMiddleware from '../utils/middleware.util';
import * as schema from '../models/admin.model';
...
..
const endpoint = '/admin';

const registerEndpoint = '/create';
adminRouter.post(endpoint,
  createEndpoint,
  useMiddleware({
    schema: schema.create,
    header: 'admin-api-key',
    token: true
  },
  endpoint + createEndpoint),
  middleware.createValidations,
  controller.create,
);
..
...

API Reference

In this section, you will find the example API's endpoints and their respective descriptions, along with the request and response examples, as well as the MongoDB BSON types for each entity, that can be used as guide for data formatting. All data is sent and received as JSON.

Models

User model User

  • _id: A unique identifier for each user. ObjectId
  • full_name: The user's full name. String required max(100)
  • username: The user's username. String required unique max(25)
  • password: The user's password. String required max(50)
  • last_update: The date and time when the user was last updated. Date
  • created_at: The date and time when the user was created. Date

Company model Company

  • _id: A unique identifier for each company. ObjectId
  • name: The companys's name. String required unique max(100)
  • units: An array containing the company's units. Unit[]
  • users: An array containing the company's users. User[]
  • x-api-key: The company's API key. String required
  • last_update: The date and time when the company was last updated. Date
  • created_at: The date and time when the company was created. Date

Unit model Unit

  • _id: A unique identifier for each unit. ObjectId
  • name: The units's name. String required unique max(50)
  • street: The unit's street. String max(100)
  • number: The unit's number. String max(10)
  • city: The unit's city. String required max(50)
  • state: The unit's state. String required max(50)
  • postal_code: The unit's postal code. String max(20)
  • assets: An array containing the unit's assets. Asset[]
  • opens_at: The date and time when the unit opens. String required length(5)
  • closes_at: The date and time when the unit closes. String required length(5)
  • last_update: The date and time when the unit was last updated. Date
  • created_at: The date and time when the unit was created. Date

Asset model Asset

  • _id: A unique identifier for each asset. ObjectId
  • name: The assets's name. String required max(50)
  • description: The assets's description. String
  • model: The assets's model. String required max(100)
  • owner: The assets owner's user. User required
  • image: The assets's image URL. String
  • status: The assets's status. String required enum('RUNNING', 'ALERTING', 'STOPPED')
  • health: The assets's healthscore. Number required min(0) max(100)
  • last_update: The date and time when the asset was last updated. Date
  • created_at: The date and time when the asset was created. Date

Routes

Authentication /auth

Users /users

Companies /companies

Units /units

Assets /assets

Authentication

  ‣   Sign in

    POST /auth/sign-in

  ☰   Request

Body
{
  "username": "JohnDoe",
  "password": "123456789"
}
Headers
{
  "Content-Type": "application/json"
}

  ☰   Responses

Status Code Description Properties
200 OK data: { token }
400 Invalid Syntax error: { message, detail }
404 User not Found error: { message, detail }
409 User has Active Session error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Sign out

    POST /auth/sign-out

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "token": "server-generated-token"
}

  ☰   Responses

Status Code Description Properties
200 OK data: null
404 Session not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

Users

  ‣   Create an User

    POST /users/create

  ☰   Request

Body
{
  "full_name": "John Doe Junior the Third",
  "username": "JohnDoe",
  "password": "123456789",
  "company": "5f9f1b9f9d1b9d1b9f1b9d1b"
}
Headers
{
  "Content-Type": "application/json",
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
201 Created data: null
400 Invalid Syntax error: { message, detail }
403 Forbidden x-api-key error: { message, detail }
409 Username Already Registered error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Search all Users

    GET /users/all

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}
Query Parameters
Name Type Description Default
per_page Number The number of results per page (max 100) 10
page Number Page number of the results to fetch 1

  ☰   Responses

Status Code Description Properties
200 OK data: { User[] | null}
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Session not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Search User by id

    GET /users/:id

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}

  ☰   Responses

Status Code Description Properties
200 OK data: User
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 User not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Update an User

    PUT /users/:id/update

  ☰   Request

Body
{
  "full_name": "John Doe Junior the Second",
  "username": "JohnDoe"
}
Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>",
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
200 OK data: null
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 User not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Delete an User

    DELETE /users/:id/delete

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>".
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
200 OK data: null
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 User not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

Companies

  ‣   Create a Company

    POST /companies/create

  ☰   Request

Body
{
  "name": "Acme Inc."
}
Headers
{
  "Content-Type": "application/json",
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
201 Created data: null
400 Invalid Syntax error: { message, detail }
401 Missing x-api-key error: { message, detail }
403 Forbidden x-api-key error: { message, detail }
409 Company Already Registered error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Search all Companies

    GET /companies/all

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}
Query Parameters
Name Type Description Default
per_page Number The number of results per page (max 100) 5
page Number Page number of the results to fetch 1

  ☰   Responses

Status Code Description Properties
200 OK data: { Company[] | null}
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Session not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Search Company by id

    GET /companies/:id

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}

  ☰   Responses

Status Code Description Properties
200 OK data: Company
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Company not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Update a Company

    PUT /companies/:id/update

  ☰   Request

Body
{
  "name": "Acme Inc. 2"
}
Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>",
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
200 OK data: null
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Company not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Delete a Company

    DELETE /companies/:id/delete

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>",
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
200 OK data: null
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Company not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

Units

  ‣   Create an Unit

    POST /units/create

  ☰   Request

Body
{
  "name": "Acme Inc. - Unit 1",
  "description": "Lorem Ipsum Dolor Sit Amet, Consectetur Adipiscing Elit.",
  "city": "New York",
  "state": "NY",
  "opens_at": "08:00",
  "closes_at": "18:00",
  "company": "5f9f1b9f9d1b9d1b9f1b9d1b"
}
Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>",
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
201 Created data: null
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
409 Unit Already Registered error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Search all Units

    GET /units/all

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}
Query Parameters
Name Type Description Default
per_page Number The number of results per page (max 100) 5
page Number Page number of the results to fetch 1

  ☰   Responses

Status Code Description Properties
200 OK data: { Unit[] | null}
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Session not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Search Unit by id

    GET /units/:id

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}

  ☰   Responses

Status Code Description Properties
200 OK data: Unit
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Unit not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Update an Unit

    PUT /units/:id/update

  ☰   Request

Body
{
  "name": "Acme Inc. - Unit 1",
  "description": "Now at a new location!",
  "address": "Main Street",
  "number": "123",
  "city": "New York",
  "state": "NY",
  "postal_code": "12345",
  "opens_at": "08:00",
  "closes_at": "18:00"
}
Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>",
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
200 OK data: null
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Unit not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Delete an Unit

    DELETE /units/:id/delete

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>",
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
200 OK data: null
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Unit not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

Assets

  ‣   Create an Asset

    POST /assets/create

  ☰   Request

Body
{
  "name": "Assembly Machine",
  "description": "This is a machine for assembly",
  "model": "AM-123",
  "owner": "7f9f1b9f9d1b9d1b9f1b9342",
  "image": "https://www.example.com/image.jpg",
  "status": "STOPPED",
  "health": 94
}
Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>",
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
201 Created data: null
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
409 Asset Already Registered error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Search all Assets

    GET /assets/all

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}
Query Parameters
Name Type Description Default
per_page Number The number of results per page (max 100) 10
page Number Page number of the results to fetch 1
owner String Username of the owner of the asset -
status String Status of the asset -
model String Model of the asset -

  ☰   Responses

Status Code Description Properties
200 OK data: { Asset[] | null}
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Session not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Search Asset by id

    GET /assets/:id

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}

  ☰   Responses

Status Code Description Properties
200 OK data: Asset
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Asset not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Update an Asset

    PUT /assets/:id/update

  ☰   Request

Body
{
  "name": "Assembly Machine - Now with more assembly",
  "description": "This is a machine for assembly, but now we use the Assembly programming language",
  "model": "AM-123",
  "status": "RUNNING",
  "health": 81
}
Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>",
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
200 OK data: null
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Asset not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

  ‣   Delete an Asset

    DELETE /assets/:id/delete

  ☰   Request

Headers
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>",
  "x-api-key": "extremely-secure-hash-key"
}

  ☰   Responses

Status Code Description Properties
200 OK data: null
400 Invalid Syntax error: { message, detail }
401 Missing Token error: { message, detail }
403 Forbidden Token error: { message, detail }
404 Asset not Found error: { message, detail }
422 Invalid Request Input error: { message, detail }
500 Internal Server Error error: { message, detail }

You might also like...

관세청 개인통관고유부호(PCCC)를 가져오는 PoC

pccc-poc 관세청 개인통관고유부호(PCCC)를 가져오는 PoC Installation git clone https://github.com/stevejkang/pccc-poc.git && cd pccc-poc npm install # edit index.ts (fi

Oct 5, 2022

POC OF CVE-2022-21970

POC OF CVE-2022-21970

CVE-2022-21970 Description Microsoft Edge (Chromium-based) Elevation of Privilege Vulnerability. This vulnerability allows an attacker to execute java

Dec 9, 2022

Full Stack project- Web application to create a biking session

Full Stack project- Web application to create a biking session

Bicycool Full Stack project: Web application to create a biking session To install and run the project: Run in the terminal of a server folder: -npm i

Mar 10, 2022

This is a full stack application where you can log all you places where you visited....-

This is a full stack application where you can log all you places where you visited....-

Full Stack Travelling Log 🌍 🎡 ✈️ This is a full stack application where you can log ✈️ your all places 🗽 🗼 🚲 you have visited .... 🗺️ 🗺️ 🗺️ Se

Sep 29, 2022

Very simple full-stack application using React, Java Spring Boot, and PostgreSQL

Very simple full-stack application using React, Java Spring Boot, and PostgreSQL. The API was built following the N-Tier architecture. The goal was to explore and learn more in-depth the development of APIs, the use of Docker and deploying with AWS.

Apr 23, 2022

The frontend of a full stack application of a personal wallet made with React, Node and MongoDB that allows you to add inputs, outputs and see all your extract.

The frontend of a full stack application of a personal wallet made with React, Node and MongoDB that allows you to add inputs, outputs and see all your extract.

The frontend of a full stack application of a personal wallet made with React, Node and MongoDB that allows you to add inputs, outputs and see all your extract.

Jun 2, 2022

The backend of a full stack application of a personal wallet made with React, Node and MongoDB that allows you to add inputs, outputs and see all your extract.

The backend of a full stack application of a personal wallet made with React, Node and MongoDB that allows you to add inputs, outputs and see all your extract.

My first full stack application with the concept of a personal wallet that allows you to create a personal account to keep track of your entire statement by adding incoming and outgoing transactions, as well as calculating the total balance and being able to edit and delete old transactions.

Jun 23, 2022

Another full-stack URL Shortener application built using web technologies

Another full-stack URL Shortener application built using web technologies

URL Shortener Another full-stack URL Shortener application built using web technologies. Technologies Node Express MongoDB React TypeScript Docker Pro

Dec 15, 2022

Full-Stack CRUD Application With Angular + Firebase Database + Authentication + REST APIs

Full-Stack CRUD Application With Angular + Firebase Database + Authentication + REST APIs

BookCompany Full-Stack CRUD Application With Angular + Firebase Database + Authentication + REST APIs Technologies & Features Angular front-end framew

Apr 10, 2022
Owner
Nivaldo Farias
Full Stack Software Engineer
Nivaldo Farias
17ᵗʰ Project developed as assessment practice during Driven's Full Stack Develpment course

Linkr | Back end WIP Full Stack Development Project Browse Nodejs code» - Front end code» Built With Study Playlist In this section I included all You

Nivaldo Farias 2 Aug 23, 2022
17ᵗʰ Project developed as assessment practice during Driven's Full Stack Develpment course

Linkr | Front end WIP Full Stack Development Project Browse JSX code» - Back end code» Built With Study Playlist In this section I included all Youtub

Nivaldo Farias 3 Jun 20, 2022
POC. Simple plugin-based meta-programming platform on top of Typescript

comp-plugins POC Running: yarn to install dependencies yarn dev to run the script The what The script creates a new typescript compiler instance (prog

Ciobanu Laurentiu 3 Jul 14, 2022
It is a solo Project and In this repo I try to build a E-Commerce full-stack website with MERN stack technologies. For Practice purpose.

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Alok Kumar 5 Aug 3, 2022
A POC of a Discord.js bot that sends 3D rendering instructions to a Go server through gRPC which responds with the image bytes which are then sent back on Discord.

A POC of a Discord.js bot that sends 3D rendering instructions to a Go server through gRPC which responds with the image bytes which are then sent back on Discord.

Henrique Corrêa 5 Jan 8, 2022
POC implementation of liveblocks.io obsidian plugin

Obsidian Liveblocks by shabegom A POC implementation of liveblocks.io inside an obsidian plugin. Install Create an account at https://liveblocks.io Gr

Sam 9 Oct 7, 2022
This repository aims to create a POC about authentication and authorization using NestJS, Prisma and JWT.

A progressive Node.js framework for building efficient and scalable server-side applications. Description Nest framework TypeScript starter repository

Vinícius Fraga Modesto 2 Nov 2, 2022
Embeddable 3D Rendering Engine with JS, a POC project.

Three.V8 Three.V8 is a proof of concept (POC) of an embedabble 3D rendering engine using JavaScript as user script. Currently, the whole project requi

Fei Yang 24 Nov 29, 2022
Minimal framework for SSG (WIP, PoC)

Frostleaf https://zenn.dev/0918nobita/scraps/64a268583b8463 Development Install tools asdf plugin-add nodejs asdf plugin-add pnpm asdf install Install

0918nobita 7 Jun 4, 2022
[OUTDATED] [PoC] Magnit bonus card numbers & QR code gen

magnitqr [OUTDATED] [PoC] Magnit bonus card numbers & QR code generator and saver https://rdavydov.github.io/magnitqr/ SPA that was used "in the field

Roman Davydov 6 Oct 25, 2022