diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..2e7dcdb4a406d310ccf1269d01542ee73011a4f6
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+node_modules
+.env.example
\ No newline at end of file
diff --git a/.env.docker b/.env.docker
deleted file mode 100644
index b9fac61931ddd9434b1b85925b50f0171f1a37dc..0000000000000000000000000000000000000000
--- a/.env.docker
+++ /dev/null
@@ -1,16 +0,0 @@
-
-# This was inserted by `prisma init`:
-# Environment variables declared in this file are automatically made available to Prisma.
-# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
-
-# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
-# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
-
-APP_PORT=3001
-DB_HOST=db_tonality_rest
-DBMS=postgresql#if you use another dbms, change at prisma/schema.prisma too
-DB_USER=
-DB_PASSWORD=
-DB_NAME=
-DB_PORT=
-DATABASE_URL="${DBMS}://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?schema=public&pgbouncer=true&connection_limit=1&pool_timeout=20"
diff --git a/.env.example b/.env.example
index 5fced6ad5dadbf4dc163eb7eda5ea4740e6ae905..afa7e9eed7289360bd7d898eecf607eb453423e5 100644
--- a/.env.example
+++ b/.env.example
@@ -1,16 +1,13 @@
+DATABASE_URL=
+JWT_SHARED_SECRET=
+EXPRESS_PORT=
 
-# This was inserted by `prisma init`:
-# Environment variables declared in this file are automatically made available to Prisma.
-# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
+# services
+# SOAP
+SOAP_URL=
+SOAP_WS_URL=
+SOAP_API_KEY=
 
-# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
-# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
-
-APP_PORT=3001
-DB_HOST=localhost
-DBMS=postgresql#if you use another dbms, change at prisma/schema.prisma too
-DB_USER=
-DB_PASSWORD=
-DB_NAME=
-DB_PORT=
-DATABASE_URL="${DBMS}://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?schema=public&pgbouncer=true&connection_limit=1&pool_timeout=20"
+# PHP
+PHP_URL=
+PHP_API_KEY=
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..0dcab8d97e578ac5b0e945a23cade3e8b3cec3cc
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,13 @@
+FROM node:18.18.2-alpine
+
+WORKDIR /tonality/tonality-rest
+
+COPY package*.json .
+
+RUN npm install
+
+COPY . .
+
+RUN npx prisma generate
+
+CMD ["sh", "-c", "npx prisma migrate deploy && npm run build-start"]
diff --git a/README.md b/README.md
index 5ff15c90280b95e620d947ebc24871526fe0fc13..3dc475916a66f7df7a9f2ba421883b1cbd1ded57 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,66 @@
 ## REST Service for Tonality
+This REST API service is used for the communication of client with other services.
+
+## Database Schema
+| user     |
+|----------|
+| user_id  |
+| username |
+| password |
+
+Types:\
+"user_id" SERIAL NOT NULL\
+"username" VARCHAR(50) NOT NULL\
+"password" VARCHAR(255) NOT NULL\
+PRIMARY KEY ("user_id")
+
+| premium_album  |
+|----------------|
+| album_id       |
+| album_name     |
+| release_date   |
+| genre          |
+| artist         |
+| cover_filename |
+
+Types:\
+"album_id" SERIAL NOT NULL\
+"album_name" VARCHAR(255) NOT NULL\
+"release_date" DATE NOT NULL\
+"genre" VARCHAR(255) NOT NULL\
+"artist" VARCHAR(255) NOT NULL\
+"cover_filename" VARCHAR(255) NOT NULL\
+PRIMARY KEY ("album_id")
+
+| premium_song    |
+|-----------------|
+| song_id         |
+| album_id        |
+| title           |
+| artist          |
+| song_number     |
+| disc_number     |
+| duration        |
+| audio_filename  |
+
+Types:\
+"song_id" SERIAL NOT NULL\
+"album_id" INTEGER NOT NULL\
+"title" VARCHAR(255) NOT NULL\
+"artist" VARCHAR(255) NOT NULL\
+"song_number" SMALLINT NOT NULL\
+"disc_number" SMALLINT\
+"duration" INTEGER NOT NULL\
+"audio_filename" VARCHAR(255) NOT NULL\
+PRIMARY KEY ("song_id")
+
+## API Endpoints
+See [the routers](./src/routers)
+
+## Task Distribution
+| Task                           | Student ID |
+|--------------------------------|------------|
+| Authentication & Authorization | 13521096   |
+| Albums & Songs                 | 13521063   |
+| Subscriptions                  | 13521087   |
+| Environment & Build            | 13521096   |
\ No newline at end of file
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..acc096f1705dbd0aa3f38ba7bb687f999c1026e5
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,210 @@
+/**
+ * For a detailed explanation regarding each configuration property, visit:
+ * https://jestjs.io/docs/configuration
+ */
+
+/** @type {import('jest').Config} */
+const config = {
+  preset: 'ts-jest',
+  testEnvironment: 'node',
+  testMatch: ["**/__tests__/**/*.ts?(x)", "**/?(*.)+(test).ts?(x)"],
+  transform: {
+    "^.+\\.ts$": "ts-jest",
+  },
+  transformIgnorePatterns: [
+    "/node_modules/(?![@autofiy/autofiyable|@autofiy/property]).+\\.js$",
+    "/node_modules/(?![@autofiy/autofiyable|@autofiy/property]).+\\.ts$",
+    "/node_modules/(?![@autofiy/autofiyable|@autofiy/property]).+\\.tsx$",
+  ],
+  // All imported modules in your tests should be mocked automatically
+  // automock: false,
+
+  // Stop running tests after `n` failures
+  // bail: 0,
+
+  // The directory where Jest should store its cached dependency information
+  // cacheDirectory: "C:\\Users\\ASUS\\AppData\\Local\\Temp\\jest",
+
+  // Automatically clear mock calls, instances, contexts and results before every test
+  // clearMocks: false,
+
+  // Indicates whether the coverage information should be collected while executing the test
+  // collectCoverage: false,
+
+  // An array of glob patterns indicating a set of files for which coverage information should be collected
+  // collectCoverageFrom: undefined,
+
+  // The directory where Jest should output its coverage files
+  // coverageDirectory: undefined,
+
+  // An array of regexp pattern strings used to skip coverage collection
+  // coveragePathIgnorePatterns: [
+  //   "\\\\node_modules\\\\"
+  // ],
+
+  // Indicates which provider should be used to instrument code for coverage
+  coverageProvider: "v8",
+
+  // A list of reporter names that Jest uses when writing coverage reports
+  // coverageReporters: [
+  //   "json",
+  //   "text",
+  //   "lcov",
+  //   "clover"
+  // ],
+
+  // An object that configures minimum threshold enforcement for coverage results
+  // coverageThreshold: undefined,
+
+  // A path to a custom dependency extractor
+  // dependencyExtractor: undefined,
+
+  // Make calling deprecated APIs throw helpful error messages
+  // errorOnDeprecated: false,
+
+  // The default configuration for fake timers
+  // fakeTimers: {
+  //   "enableGlobally": false
+  // },
+
+  // Force coverage collection from ignored files using an array of glob patterns
+  // forceCoverageMatch: [],
+
+  // A path to a module which exports an async function that is triggered once before all test suites
+  // globalSetup: undefined,
+
+  // A path to a module which exports an async function that is triggered once after all test suites
+  // globalTeardown: undefined,
+
+  // A set of global variables that need to be available in all test environments
+  // globals: {},
+
+  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
+  // maxWorkers: "50%",
+
+  // An array of directory names to be searched recursively up from the requiring module's location
+  // moduleDirectories: [
+  //   "node_modules"
+  // ],
+
+  // An array of file extensions your modules use
+  // moduleFileExtensions: [
+  //   "js",
+  //   "mjs",
+  //   "cjs",
+  //   "jsx",
+  //   "ts",
+  //   "tsx",
+  //   "json",
+  //   "node"
+  // ],
+
+  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
+  // moduleNameMapper: {},
+
+  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
+  // modulePathIgnorePatterns: [],
+
+  // Activates notifications for test results
+  // notify: false,
+
+  // An enum that specifies notification mode. Requires { notify: true }
+  // notifyMode: "failure-change",
+
+  // A preset that is used as a base for Jest's configuration
+  // preset: undefined,
+
+  // Run tests from one or more projects
+  // projects: undefined,
+
+  // Use this configuration option to add custom reporters to Jest
+  // reporters: undefined,
+
+  // Automatically reset mock state before every test
+  // resetMocks: false,
+
+  // Reset the module registry before running each individual test
+  // resetModules: false,
+
+  // A path to a custom resolver
+  // resolver: undefined,
+
+  // Automatically restore mock state and implementation before every test
+  // restoreMocks: false,
+
+  // The root directory that Jest should scan for tests and modules within
+  // rootDir: undefined,
+
+  // A list of paths to directories that Jest should use to search for files in
+  // roots: [
+  //   "<rootDir>"
+  // ],
+
+  // Allows you to use a custom runner instead of Jest's default test runner
+  // runner: "jest-runner",
+
+  // The paths to modules that run some code to configure or set up the testing environment before each test
+  // setupFiles: [],
+
+  // A list of paths to modules that run some code to configure or set up the testing framework before each test
+  // setupFilesAfterEnv: [],
+
+  // The number of seconds after which a test is considered as slow and reported as such in the results.
+  // slowTestThreshold: 5,
+
+  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
+  // snapshotSerializers: [],
+
+  // The test environment that will be used for testing
+  // testEnvironment: "jest-environment-node",
+
+  // Options that will be passed to the testEnvironment
+  // testEnvironmentOptions: {},
+
+  // Adds a location field to test results
+  // testLocationInResults: false,
+
+  // The glob patterns Jest uses to detect test files
+  // testMatch: [
+  //   "**/__tests__/**/*.[jt]s?(x)",
+  //   "**/?(*.)+(spec|test).[tj]s?(x)"
+  // ],
+
+  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
+  // testPathIgnorePatterns: [
+  //   "\\\\node_modules\\\\"
+  // ],
+
+  // The regexp pattern or array of patterns that Jest uses to detect test files
+  // testRegex: [],
+
+  // This option allows the use of a custom results processor
+  // testResultsProcessor: undefined,
+
+  // This option allows use of a custom test runner
+  // testRunner: "jest-circus/runner",
+
+  // A map from regular expressions to paths to transformers
+  // transform: undefined,
+
+  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
+  // transformIgnorePatterns: [
+  //   "\\\\node_modules\\\\",
+  //   "\\.pnp\\.[^\\\\]+$"
+  // ],
+
+  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
+  // unmockedModulePathPatterns: undefined,
+
+  // Indicates whether each individual test should be reported during the run
+  // verbose: undefined,
+
+  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
+  // watchPathIgnorePatterns: [],
+
+  // Whether to use watchman for file crawling
+  // watchman: true,
+};
+
+// eslint-disable-next-line no-undef
+module.exports = config;
diff --git a/package-lock.json b/package-lock.json
index ae48e9eafe957dd6e92f8d43ea6217c859e8be71..0a3ef585a45153cee08c1bc639faf5d3db8b14bb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,25 +10,39 @@
       "license": "ISC",
       "dependencies": {
         "@prisma/client": "^5.4.2",
-        "@types/bcrypt": "^5.0.1",
-        "@types/jsonwebtoken": "^9.0.4",
-        "bcrypt": "^5.1.1",
+        "@types/multer": "^1.4.10",
+        "argon2": "^0.31.1",
+        "axios": "^1.6.1",
+        "cookie-parser": "^1.4.6",
+        "cors": "^2.8.5",
         "dotenv": "^16.3.1",
         "express": "^4.17.1",
-        "ts-node": "^10.9.1"
+        "fast-xml-parser": "^4.3.2",
+        "http-status-codes": "2.3.0",
+        "jsonwebtoken": "^9.0.2",
+        "multer": "^1.4.5-lts.1",
+        "nodemon": "^3.0.1",
+        "ts-node": "^10.9.1",
+        "uuid": "^9.0.1",
+        "winston": "^3.11.0",
+        "zod": "^3.22.4"
       },
       "devDependencies": {
+        "@types/cookie-parser": "^1.4.6",
+        "@types/cors": "^2.8.16",
         "@types/express": "^4.17.1",
         "@types/jest": "^29.5.6",
+        "@types/jsonwebtoken": "^9.0.4",
         "@types/supertest": "^2.0.15",
+        "@types/uuid": "^9.0.7",
         "@typescript-eslint/eslint-plugin": "^6.8.0",
         "@typescript-eslint/parser": "^6.8.0",
         "eslint": "^8.52.0",
         "jest": "^29.7.0",
         "prisma": "^5.4.2",
         "supertest": "^6.3.3",
-        "typescript": "^5.2.2",
-        "zod": "^3.22.4"
+        "ts-jest": "^29.1.1",
+        "typescript": "^5.2.2"
       }
     },
     "node_modules/@aashutoshrathi/word-wrap": {
@@ -773,6 +787,14 @@
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "dev": true
     },
+    "node_modules/@colors/colors": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
+      "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
+      "engines": {
+        "node": ">=0.1.90"
+      }
+    },
     "node_modules/@cspotcode/source-map-support": {
       "version": "0.8.1",
       "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -784,6 +806,16 @@
         "node": ">=12"
       }
     },
+    "node_modules/@dabh/diagnostics": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
+      "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
+      "dependencies": {
+        "colorspace": "1.1.x",
+        "enabled": "2.0.x",
+        "kuler": "^2.0.0"
+      }
+    },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -1434,6 +1466,14 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@phc/format": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
+      "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==",
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/@prisma/client": {
       "version": "5.4.2",
       "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.4.2.tgz",
@@ -1551,19 +1591,10 @@
         "@babel/types": "^7.20.7"
       }
     },
-    "node_modules/@types/bcrypt": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.1.tgz",
-      "integrity": "sha512-dIIrEsLV1/v0AUNI8oHMaRRTSeVjoy5ID8oclJavtPj8CwPJoD1eFoNXEypuu6k091brEzBeOo3LlxeAH9zRZg==",
-      "dependencies": {
-        "@types/node": "*"
-      }
-    },
     "node_modules/@types/body-parser": {
       "version": "1.19.4",
       "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz",
       "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==",
-      "dev": true,
       "dependencies": {
         "@types/connect": "*",
         "@types/node": "*"
@@ -1573,22 +1604,38 @@
       "version": "3.4.37",
       "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz",
       "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==",
-      "dev": true,
       "dependencies": {
         "@types/node": "*"
       }
     },
+    "node_modules/@types/cookie-parser": {
+      "version": "1.4.6",
+      "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.6.tgz",
+      "integrity": "sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w==",
+      "dev": true,
+      "dependencies": {
+        "@types/express": "*"
+      }
+    },
     "node_modules/@types/cookiejar": {
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.3.tgz",
       "integrity": "sha512-LZ8SD3LpNmLMDLkG2oCBjZg+ETnx6XdCjydUE0HwojDmnDfDUnhMKKbtth1TZh+hzcqb03azrYWoXLS8sMXdqg==",
       "dev": true
     },
+    "node_modules/@types/cors": {
+      "version": "2.8.16",
+      "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.16.tgz",
+      "integrity": "sha512-Trx5or1Nyg1Fq138PCuWqoApzvoSLWzZ25ORBiHMbbUT42g578lH1GT4TwYDbiUOLFuDsCkfLneT2105fsFWGg==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/express": {
       "version": "4.17.1",
       "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.1.tgz",
       "integrity": "sha512-VfH/XCP0QbQk5B5puLqTLEeFgR8lfCJHZJKkInZ9mkYd+u8byX0kztXEQxEk4wZXJs8HI+7km2ALXjn4YKcX9w==",
-      "dev": true,
       "dependencies": {
         "@types/body-parser": "*",
         "@types/express-serve-static-core": "*",
@@ -1599,7 +1646,6 @@
       "version": "4.17.39",
       "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz",
       "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==",
-      "dev": true,
       "dependencies": {
         "@types/node": "*",
         "@types/qs": "*",
@@ -1619,8 +1665,7 @@
     "node_modules/@types/http-errors": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz",
-      "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==",
-      "dev": true
+      "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA=="
     },
     "node_modules/@types/istanbul-lib-coverage": {
       "version": "2.0.5",
@@ -1666,6 +1711,7 @@
       "version": "9.0.4",
       "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.4.tgz",
       "integrity": "sha512-8UYapdmR0QlxgvJmyE8lP7guxD0UGVMfknsdtCFZh4ovShdBl3iOI4zdvqBHrB/IS+xUj3PSx73Qkey1fhWz+g==",
+      "dev": true,
       "dependencies": {
         "@types/node": "*"
       }
@@ -1673,8 +1719,15 @@
     "node_modules/@types/mime": {
       "version": "1.3.4",
       "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz",
-      "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==",
-      "dev": true
+      "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw=="
+    },
+    "node_modules/@types/multer": {
+      "version": "1.4.10",
+      "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.10.tgz",
+      "integrity": "sha512-6l9mYMhUe8wbnz/67YIjc7ZJyQNZoKq7fRXVf7nMdgWgalD0KyzJ2ywI7hoATUSXSbTu9q2HBiEwzy0tNN1v2w==",
+      "dependencies": {
+        "@types/express": "*"
+      }
     },
     "node_modules/@types/node": {
       "version": "20.8.7",
@@ -1687,14 +1740,12 @@
     "node_modules/@types/qs": {
       "version": "6.9.9",
       "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz",
-      "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==",
-      "dev": true
+      "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg=="
     },
     "node_modules/@types/range-parser": {
       "version": "1.2.6",
       "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz",
-      "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==",
-      "dev": true
+      "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA=="
     },
     "node_modules/@types/semver": {
       "version": "7.5.4",
@@ -1706,7 +1757,6 @@
       "version": "0.17.3",
       "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz",
       "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==",
-      "dev": true,
       "dependencies": {
         "@types/mime": "^1",
         "@types/node": "*"
@@ -1716,7 +1766,6 @@
       "version": "1.15.4",
       "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz",
       "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==",
-      "dev": true,
       "dependencies": {
         "@types/http-errors": "*",
         "@types/mime": "*",
@@ -1748,6 +1797,17 @@
         "@types/superagent": "*"
       }
     },
+    "node_modules/@types/triple-beam": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.4.tgz",
+      "integrity": "sha512-HlJjF3wxV4R2VQkFpKe0YqJLilYNgtRtsqqZtby7RkVsSs+i+vbyzjtUwpFEdUCKcrGzCiEJE7F/0mKjh0sunA=="
+    },
+    "node_modules/@types/uuid": {
+      "version": "9.0.7",
+      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz",
+      "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==",
+      "dev": true
+    },
     "node_modules/@types/yargs": {
       "version": "17.0.29",
       "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz",
@@ -2197,7 +2257,6 @@
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
       "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
-      "dev": true,
       "dependencies": {
         "normalize-path": "^3.0.0",
         "picomatch": "^2.0.4"
@@ -2206,6 +2265,11 @@
         "node": ">= 8"
       }
     },
+    "node_modules/append-field": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
+      "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
+    },
     "node_modules/aproba": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
@@ -2228,6 +2292,20 @@
       "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
       "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
     },
+    "node_modules/argon2": {
+      "version": "0.31.1",
+      "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.31.1.tgz",
+      "integrity": "sha512-ik2xnJrLXazya7m4Nz1XfBSRjXj8Koq8qF9PsQC8059p20ifWc9zx/hgU3ItZh/3TnwXkv0RbhvjodPkmFf0bg==",
+      "hasInstallScript": true,
+      "dependencies": {
+        "@mapbox/node-pre-gyp": "^1.0.11",
+        "@phc/format": "^1.0.0",
+        "node-addon-api": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
     "node_modules/argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2254,11 +2332,25 @@
       "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
       "dev": true
     },
+    "node_modules/async": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
+      "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
-      "dev": true
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "node_modules/axios": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz",
+      "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==",
+      "dependencies": {
+        "follow-redirects": "^1.15.0",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
     },
     "node_modules/babel-jest": {
       "version": "29.7.0",
@@ -2381,17 +2473,12 @@
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
-    "node_modules/bcrypt": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
-      "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
-      "hasInstallScript": true,
-      "dependencies": {
-        "@mapbox/node-pre-gyp": "^1.0.11",
-        "node-addon-api": "^5.0.0"
-      },
+    "node_modules/binary-extensions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
       "engines": {
-        "node": ">= 10.0.0"
+        "node": ">=8"
       }
     },
     "node_modules/body-parser": {
@@ -2427,7 +2514,6 @@
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
       "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
-      "dev": true,
       "dependencies": {
         "fill-range": "^7.0.1"
       },
@@ -2467,6 +2553,18 @@
         "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
       }
     },
+    "node_modules/bs-logger": {
+      "version": "0.2.6",
+      "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+      "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+      "dev": true,
+      "dependencies": {
+        "fast-json-stable-stringify": "2.x"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/bser": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
@@ -2476,11 +2574,26 @@
         "node-int64": "^0.4.0"
       }
     },
+    "node_modules/buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+    },
     "node_modules/buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
-      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
-      "dev": true
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+    },
+    "node_modules/busboy": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+      "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+      "dependencies": {
+        "streamsearch": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=10.16.0"
+      }
     },
     "node_modules/bytes": {
       "version": "3.1.0",
@@ -2567,6 +2680,43 @@
         "node": ">=10"
       }
     },
+    "node_modules/chokidar": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/chokidar/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/chownr": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
@@ -2626,6 +2776,15 @@
       "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
       "dev": true
     },
+    "node_modules/color": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
+      "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
+      "dependencies": {
+        "color-convert": "^1.9.3",
+        "color-string": "^1.6.0"
+      }
+    },
     "node_modules/color-convert": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2641,8 +2800,16 @@
     "node_modules/color-name": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+    },
+    "node_modules/color-string": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+      "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+      "dependencies": {
+        "color-name": "^1.0.0",
+        "simple-swizzle": "^0.2.2"
+      }
     },
     "node_modules/color-support": {
       "version": "1.1.3",
@@ -2652,11 +2819,32 @@
         "color-support": "bin.js"
       }
     },
+    "node_modules/color/node_modules/color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/color/node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+    },
+    "node_modules/colorspace": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
+      "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
+      "dependencies": {
+        "color": "^3.1.3",
+        "text-hex": "1.0.x"
+      }
+    },
     "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==",
-      "dev": true,
       "dependencies": {
         "delayed-stream": "~1.0.0"
       },
@@ -2675,6 +2863,42 @@
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
     },
+    "node_modules/concat-stream": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+      "engines": [
+        "node >= 0.8"
+      ],
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
+      }
+    },
+    "node_modules/concat-stream/node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/concat-stream/node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
     "node_modules/console-control-strings": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@@ -2713,6 +2937,26 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/cookie-parser": {
+      "version": "1.4.6",
+      "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
+      "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
+      "dependencies": {
+        "cookie": "0.4.1",
+        "cookie-signature": "1.0.6"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/cookie-parser/node_modules/cookie": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+      "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/cookie-signature": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -2724,6 +2968,23 @@
       "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
       "dev": true
     },
+    "node_modules/core-util-is": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
+    },
+    "node_modules/cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
     "node_modules/create-jest": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
@@ -2819,7 +3080,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
-      "dev": true,
       "engines": {
         "node": ">=0.4.0"
       }
@@ -2921,6 +3181,14 @@
         "url": "https://github.com/motdotla/dotenv?sponsor=1"
       }
     },
+    "node_modules/ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -2949,6 +3217,11 @@
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
     },
+    "node_modules/enabled": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
+      "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
+    },
     "node_modules/encodeurl": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -3318,6 +3591,27 @@
       "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
       "dev": true
     },
+    "node_modules/fast-xml-parser": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz",
+      "integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/NaturalIntelligence"
+        },
+        {
+          "type": "paypal",
+          "url": "https://paypal.me/naturalintelligence"
+        }
+      ],
+      "dependencies": {
+        "strnum": "^1.0.5"
+      },
+      "bin": {
+        "fxparser": "src/cli/cli.js"
+      }
+    },
     "node_modules/fastq": {
       "version": "1.15.0",
       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
@@ -3336,6 +3630,11 @@
         "bser": "2.1.1"
       }
     },
+    "node_modules/fecha": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
+      "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
+    },
     "node_modules/file-entry-cache": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -3352,7 +3651,6 @@
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
       "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
-      "dev": true,
       "dependencies": {
         "to-regex-range": "^5.0.1"
       },
@@ -3413,11 +3711,34 @@
       "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
       "dev": true
     },
+    "node_modules/fn.name": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
+      "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.3",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
+      "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
     "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==",
-      "dev": true,
       "dependencies": {
         "asynckit": "^0.4.0",
         "combined-stream": "^1.0.8",
@@ -3504,7 +3825,6 @@
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
       "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
-      "dev": true,
       "hasInstallScript": true,
       "optional": true,
       "os": [
@@ -3778,6 +4098,11 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/http-status-codes": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
+      "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA=="
+    },
     "node_modules/https-proxy-agent": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
@@ -3840,6 +4165,11 @@
         "node": ">= 4"
       }
     },
+    "node_modules/ignore-by-default": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+      "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
+    },
     "node_modules/import-fresh": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -3912,6 +4242,17 @@
       "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
       "dev": true
     },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/is-core-module": {
       "version": "2.13.1",
       "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
@@ -3928,7 +4269,6 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
       "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -3954,7 +4294,6 @@
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
       "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
-      "dev": true,
       "dependencies": {
         "is-extglob": "^2.1.1"
       },
@@ -3966,7 +4305,6 @@
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
       "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-      "dev": true,
       "engines": {
         "node": ">=0.12.0"
       }
@@ -3984,7 +4322,6 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
       "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
-      "dev": true,
       "engines": {
         "node": ">=8"
       },
@@ -3992,6 +4329,11 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
+    },
     "node_modules/isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -4723,6 +5065,51 @@
         "node": ">=6"
       }
     },
+    "node_modules/jsonwebtoken": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+      "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+      "dependencies": {
+        "jws": "^3.2.2",
+        "lodash.includes": "^4.3.0",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isinteger": "^4.0.4",
+        "lodash.isnumber": "^3.0.3",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.once": "^4.0.0",
+        "ms": "^2.1.1",
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": ">=12",
+        "npm": ">=6"
+      }
+    },
+    "node_modules/jsonwebtoken/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "node_modules/jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "dependencies": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "dependencies": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/keyv": {
       "version": "4.5.4",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -4741,6 +5128,11 @@
         "node": ">=6"
       }
     },
+    "node_modules/kuler": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
+      "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
+    },
     "node_modules/leven": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@@ -4784,12 +5176,74 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/lodash.includes": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+      "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+    },
+    "node_modules/lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+    },
+    "node_modules/lodash.isinteger": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+      "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
+    },
+    "node_modules/lodash.isnumber": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+      "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
+    },
+    "node_modules/lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+    },
+    "node_modules/lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
+    },
+    "node_modules/lodash.memoize": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+      "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+      "dev": true
+    },
     "node_modules/lodash.merge": {
       "version": "4.6.2",
       "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
       "dev": true
     },
+    "node_modules/lodash.once": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+      "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
+    },
+    "node_modules/logform": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz",
+      "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==",
+      "dependencies": {
+        "@colors/colors": "1.6.0",
+        "@types/triple-beam": "^1.3.2",
+        "fecha": "^4.2.0",
+        "ms": "^2.1.1",
+        "safe-stable-stringify": "^2.3.1",
+        "triple-beam": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 12.0.0"
+      }
+    },
+    "node_modules/logform/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
     "node_modules/lru-cache": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -4936,6 +5390,14 @@
         "node": "*"
       }
     },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/minipass": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
@@ -4983,6 +5445,34 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
     },
+    "node_modules/multer": {
+      "version": "1.4.5-lts.1",
+      "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
+      "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
+      "dependencies": {
+        "append-field": "^1.0.0",
+        "busboy": "^1.0.0",
+        "concat-stream": "^1.5.2",
+        "mkdirp": "^0.5.4",
+        "object-assign": "^4.1.1",
+        "type-is": "^1.6.4",
+        "xtend": "^4.0.0"
+      },
+      "engines": {
+        "node": ">= 6.0.0"
+      }
+    },
+    "node_modules/multer/node_modules/mkdirp": {
+      "version": "0.5.6",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+      "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+      "dependencies": {
+        "minimist": "^1.2.6"
+      },
+      "bin": {
+        "mkdirp": "bin/cmd.js"
+      }
+    },
     "node_modules/natural-compare": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -4998,9 +5488,9 @@
       }
     },
     "node_modules/node-addon-api": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
-      "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz",
+      "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA=="
     },
     "node_modules/node-fetch": {
       "version": "2.7.0",
@@ -5033,6 +5523,65 @@
       "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
       "dev": true
     },
+    "node_modules/nodemon": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz",
+      "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==",
+      "dependencies": {
+        "chokidar": "^3.5.2",
+        "debug": "^3.2.7",
+        "ignore-by-default": "^1.0.1",
+        "minimatch": "^3.1.2",
+        "pstree.remy": "^1.1.8",
+        "semver": "^7.5.3",
+        "simple-update-notifier": "^2.0.0",
+        "supports-color": "^5.5.0",
+        "touch": "^3.1.0",
+        "undefsafe": "^2.0.5"
+      },
+      "bin": {
+        "nodemon": "bin/nodemon.js"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/nodemon"
+      }
+    },
+    "node_modules/nodemon/node_modules/debug": {
+      "version": "3.2.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+      "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+      "dependencies": {
+        "ms": "^2.1.1"
+      }
+    },
+    "node_modules/nodemon/node_modules/has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/nodemon/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "node_modules/nodemon/node_modules/supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/nopt": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
@@ -5051,7 +5600,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
       "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -5115,6 +5663,14 @@
         "wrappy": "1"
       }
     },
+    "node_modules/one-time": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
+      "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
+      "dependencies": {
+        "fn.name": "1.x.x"
+      }
+    },
     "node_modules/onetime": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
@@ -5280,7 +5836,6 @@
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
       "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-      "dev": true,
       "engines": {
         "node": ">=8.6"
       },
@@ -5412,6 +5967,11 @@
         "node": ">=16.13"
       }
     },
+    "node_modules/process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+    },
     "node_modules/prompts": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -5437,6 +5997,16 @@
         "node": ">= 0.10"
       }
     },
+    "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/pstree.remy": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+      "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
+    },
     "node_modules/punycode": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
@@ -5531,6 +6101,17 @@
         "node": ">= 6"
       }
     },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
     "node_modules/require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -5648,6 +6229,14 @@
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
     },
+    "node_modules/safe-stable-stringify": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
+      "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==",
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/safer-buffer": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -5774,6 +6363,30 @@
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
       "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
     },
+    "node_modules/simple-swizzle": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+      "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+      "dependencies": {
+        "is-arrayish": "^0.3.1"
+      }
+    },
+    "node_modules/simple-swizzle/node_modules/is-arrayish": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+      "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+    },
+    "node_modules/simple-update-notifier": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+      "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+      "dependencies": {
+        "semver": "^7.5.3"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/sisteransi": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -5814,6 +6427,14 @@
       "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
       "dev": true
     },
+    "node_modules/stack-trace": {
+      "version": "0.0.10",
+      "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
+      "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/stack-utils": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
@@ -5843,6 +6464,14 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/streamsearch": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+      "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/string_decoder": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -5937,6 +6566,11 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/strnum": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
+      "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
+    },
     "node_modules/superagent": {
       "version": "8.1.2",
       "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz",
@@ -6075,6 +6709,11 @@
         "node": ">=8"
       }
     },
+    "node_modules/text-hex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
+      "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
+    },
     "node_modules/text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -6100,7 +6739,6 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
       "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-      "dev": true,
       "dependencies": {
         "is-number": "^7.0.0"
       },
@@ -6116,11 +6754,44 @@
         "node": ">=0.6"
       }
     },
+    "node_modules/touch": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
+      "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
+      "dependencies": {
+        "nopt": "~1.0.10"
+      },
+      "bin": {
+        "nodetouch": "bin/nodetouch.js"
+      }
+    },
+    "node_modules/touch/node_modules/nopt": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+      "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
+      "dependencies": {
+        "abbrev": "1"
+      },
+      "bin": {
+        "nopt": "bin/nopt.js"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/tr46": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
       "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
     },
+    "node_modules/triple-beam": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
+      "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
+      "engines": {
+        "node": ">= 14.0.0"
+      }
+    },
     "node_modules/ts-api-utils": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
@@ -6133,6 +6804,49 @@
         "typescript": ">=4.2.0"
       }
     },
+    "node_modules/ts-jest": {
+      "version": "29.1.1",
+      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz",
+      "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==",
+      "dev": true,
+      "dependencies": {
+        "bs-logger": "0.x",
+        "fast-json-stable-stringify": "2.x",
+        "jest-util": "^29.0.0",
+        "json5": "^2.2.3",
+        "lodash.memoize": "4.x",
+        "make-error": "1.x",
+        "semver": "^7.5.3",
+        "yargs-parser": "^21.0.1"
+      },
+      "bin": {
+        "ts-jest": "cli.js"
+      },
+      "engines": {
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+      },
+      "peerDependencies": {
+        "@babel/core": ">=7.0.0-beta.0 <8",
+        "@jest/types": "^29.0.0",
+        "babel-jest": "^29.0.0",
+        "jest": "^29.0.0",
+        "typescript": ">=4.3 <6"
+      },
+      "peerDependenciesMeta": {
+        "@babel/core": {
+          "optional": true
+        },
+        "@jest/types": {
+          "optional": true
+        },
+        "babel-jest": {
+          "optional": true
+        },
+        "esbuild": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/ts-node": {
       "version": "10.9.1",
       "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
@@ -6220,6 +6934,11 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
+    },
     "node_modules/typescript": {
       "version": "5.2.2",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
@@ -6232,6 +6951,11 @@
         "node": ">=14.17"
       }
     },
+    "node_modules/undefsafe": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+      "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="
+    },
     "node_modules/undici-types": {
       "version": "5.25.3",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz",
@@ -6297,6 +7021,18 @@
         "node": ">= 0.4.0"
       }
     },
+    "node_modules/uuid": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/v8-compile-cache-lib": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -6380,6 +7116,40 @@
         "string-width": "^1.0.2 || 2 || 3 || 4"
       }
     },
+    "node_modules/winston": {
+      "version": "3.11.0",
+      "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz",
+      "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==",
+      "dependencies": {
+        "@colors/colors": "^1.6.0",
+        "@dabh/diagnostics": "^2.0.2",
+        "async": "^3.2.3",
+        "is-stream": "^2.0.0",
+        "logform": "^2.4.0",
+        "one-time": "^1.0.0",
+        "readable-stream": "^3.4.0",
+        "safe-stable-stringify": "^2.3.1",
+        "stack-trace": "0.0.x",
+        "triple-beam": "^1.3.0",
+        "winston-transport": "^4.5.0"
+      },
+      "engines": {
+        "node": ">= 12.0.0"
+      }
+    },
+    "node_modules/winston-transport": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz",
+      "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==",
+      "dependencies": {
+        "logform": "^2.3.2",
+        "readable-stream": "^3.6.0",
+        "triple-beam": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 12.0.0"
+      }
+    },
     "node_modules/wrap-ansi": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -6415,6 +7185,14 @@
         "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
       }
     },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "engines": {
+        "node": ">=0.4"
+      }
+    },
     "node_modules/y18n": {
       "version": "5.0.8",
       "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -6480,7 +7258,6 @@
       "version": "3.22.4",
       "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
       "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
-      "dev": true,
       "funding": {
         "url": "https://github.com/sponsors/colinhacks"
       }
diff --git a/package.json b/package.json
index 2241b0cdc133c0fbb78b9a5dc37fd3b129ce235f..af251a1a79dc7da1cb19e0ccc4cb15ee3aa8d2ea 100644
--- a/package.json
+++ b/package.json
@@ -2,35 +2,56 @@
   "name": "tonality-rest",
   "version": "1.0.0",
   "description": "",
-  "main": "dist/app.js",
+  "main": "dist/src/cores/app.js",
   "scripts": {
-    "start": "tsc && node dist/app.js",
+    "dev": "nodemon src/cores/app.ts",
+    "migrate:dev": "prisma migrate dev --preview-feature",
+    "start": "node dist/src/cores/app.js",
+    "build": "tsc",
     "lint": "eslint . --ext .ts",
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "test": "jest --runInBand --forceExit --detectOpenHandles --coverage",
+    "build-start": "npm run build && npm run start"
+  },
+  "prisma": {
+    "seed": "ts-node prisma/seed.ts"
   },
   "keywords": [],
   "author": "",
   "license": "ISC",
   "devDependencies": {
+    "@types/cookie-parser": "^1.4.6",
+    "@types/cors": "^2.8.16",
     "@types/express": "^4.17.1",
     "@types/jest": "^29.5.6",
+    "@types/jsonwebtoken": "^9.0.4",
     "@types/supertest": "^2.0.15",
+    "@types/uuid": "^9.0.7",
     "@typescript-eslint/eslint-plugin": "^6.8.0",
     "@typescript-eslint/parser": "^6.8.0",
     "eslint": "^8.52.0",
     "jest": "^29.7.0",
     "prisma": "^5.4.2",
     "supertest": "^6.3.3",
-    "typescript": "^5.2.2",
-    "zod": "^3.22.4"
+    "ts-jest": "^29.1.1",
+    "typescript": "^5.2.2"
   },
   "dependencies": {
     "@prisma/client": "^5.4.2",
-    "@types/bcrypt": "^5.0.1",
-    "@types/jsonwebtoken": "^9.0.4",
-    "bcrypt": "^5.1.1",
+    "@types/multer": "^1.4.10",
+    "argon2": "^0.31.1",
+    "axios": "^1.6.1",
+    "cookie-parser": "^1.4.6",
+    "cors": "^2.8.5",
     "dotenv": "^16.3.1",
     "express": "^4.17.1",
-    "ts-node": "^10.9.1"
+    "fast-xml-parser": "^4.3.2",
+    "http-status-codes": "2.3.0",
+    "jsonwebtoken": "^9.0.2",
+    "multer": "^1.4.5-lts.1",
+    "nodemon": "^3.0.1",
+    "ts-node": "^10.9.1",
+    "uuid": "^9.0.1",
+    "winston": "^3.11.0",
+    "zod": "^3.22.4"
   }
 }
diff --git a/prisma/migrations/20231031075205_init/migration.sql b/prisma/migrations/20231031075205_init/migration.sql
new file mode 100644
index 0000000000000000000000000000000000000000..241bdde062db877af727a9a38f06aac64d19f968
--- /dev/null
+++ b/prisma/migrations/20231031075205_init/migration.sql
@@ -0,0 +1,40 @@
+-- CreateTable
+CREATE TABLE "user" (
+    "user_id" SERIAL NOT NULL,
+    "username" VARCHAR(50) NOT NULL,
+    "password" VARCHAR(255) NOT NULL,
+
+    CONSTRAINT "user_pkey" PRIMARY KEY ("user_id")
+);
+
+-- CreateTable
+CREATE TABLE "premium_album" (
+    "album_id" SERIAL NOT NULL,
+    "album_name" VARCHAR(255) NOT NULL,
+    "release_date" DATE NOT NULL,
+    "genre" VARCHAR(255) NOT NULL,
+    "artist" VARCHAR(255) NOT NULL,
+    "cover_filename" VARCHAR(255) NOT NULL,
+
+    CONSTRAINT "premium_album_pkey" PRIMARY KEY ("album_id")
+);
+
+-- CreateTable
+CREATE TABLE "premium_song" (
+    "song_id" SERIAL NOT NULL,
+    "album_id" INTEGER NOT NULL,
+    "title" VARCHAR(255) NOT NULL,
+    "artist" VARCHAR(255) NOT NULL,
+    "song_number" SMALLINT NOT NULL,
+    "disc_number" SMALLINT,
+    "duration" INTEGER NOT NULL,
+    "audio_filename" VARCHAR(255) NOT NULL,
+
+    CONSTRAINT "premium_song_pkey" PRIMARY KEY ("song_id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "user_username_key" ON "user"("username");
+
+-- AddForeignKey
+ALTER TABLE "premium_song" ADD CONSTRAINT "premium_song_album_id_fkey" FOREIGN KEY ("album_id") REFERENCES "premium_album"("album_id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml
new file mode 100644
index 0000000000000000000000000000000000000000..fbffa92c2bb7c748d6fc78f9f9dcac604dabb87d
--- /dev/null
+++ b/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "postgresql"
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 843f6bdd02c83895f00752a8410a01f66d6bd05d..0ad09b2d862954593af1a99eeef8cf2ccf814f91 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -10,3 +10,36 @@ datasource db {
   url      = env("DATABASE_URL")
 }
 
+model User {
+  userId   Int    @id @default(autoincrement()) @map("user_id")
+  username String @unique @db.VarChar(50)
+  password String @db.VarChar(255)
+
+  @@map("user")
+}
+
+model PremiumAlbum {
+  albumId       Int           @id @default(autoincrement()) @map("album_id")
+  albumName     String        @map("album_name") @db.VarChar(255)
+  releaseDate   DateTime      @map("release_date") @db.Date
+  genre         String        @db.VarChar(255)
+  artist        String        @db.VarChar(255)
+  coverFilename String        @map("cover_filename") @db.VarChar(255)
+  songs         PremiumSong[]
+
+  @@map("premium_album")
+}
+
+model PremiumSong {
+  songId        Int          @id @default(autoincrement()) @map("song_id")
+  albumId       Int          @map("album_id")
+  title         String       @db.VarChar(255)
+  artist        String       @db.VarChar(255)
+  songNumber    Int          @map("song_number") @db.SmallInt
+  discNumber    Int?         @map("disc_number") @db.SmallInt
+  duration      Int
+  audioFilename String       @map("audio_filename") @db.VarChar(255)
+  PremiumAlbum  PremiumAlbum @relation(fields: [albumId], references: [albumId], onUpdate: Cascade, onDelete: Cascade)
+
+  @@map("premium_song")
+}
diff --git a/prisma/seed.ts b/prisma/seed.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app.ts b/src/app.ts
deleted file mode 100644
index ce36fc4164e7b227cd6812a4b04916332235a556..0000000000000000000000000000000000000000
--- a/src/app.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import express from 'express';
-import {z} from 'zod';
-import dotenv from 'dotenv';
-
-dotenv.config();
-const app = express();
-const port = process.env.APP_PORT;
-app.get('/', (req, res) => {
-    res.send(
-        {
-            message: 'Hello World!',
-        }
-    );
-});
-
-app.listen(port, () => {
-    return console.log(`Express is listening at http://localhost:${port}`);
-});
diff --git a/src/clients/php-client.ts b/src/clients/php-client.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6f34e9e1fdd3bc773b506b5d6a658125009be9e6
--- /dev/null
+++ b/src/clients/php-client.ts
@@ -0,0 +1,55 @@
+import axios from "axios";
+
+const phpClient = async (url : string, method : string, data : any, is_multipart_form_data : boolean = false) : Promise<object> => {
+    const responseData : string = await phpRequest(url, method, data, is_multipart_form_data);
+    return phpResponseDataParser(responseData);
+}
+
+const phpRequest = async (
+    url : string,
+    method : string,
+    data : any,
+    is_multipart_form_data : boolean,
+) : Promise<string> => {
+    if (method === "GET") {
+        const response = await axios.get(
+            url,
+            {
+                params: data,
+            }
+        );
+        return response.data as string;
+    }
+    else if (method === "POST") {
+        const formData = new FormData();
+        if (is_multipart_form_data) {
+            // only accept 1 file
+            Object.keys(data).forEach((key) => {
+                (key !== "file") && formData.append(key, data[key]);
+            });
+            const blobFile = new Blob([data.file.buffer]);
+            formData.append("rest-file", blobFile, data.file.filename)
+        }
+
+        const response = await axios.post(
+            url,
+            is_multipart_form_data ? formData : data,
+            {
+                headers: {
+                    "Content-Type": is_multipart_form_data ? "multipart/form-data" : "application/json",
+                    "X-API-KEY": process.env.PHP_API_KEY
+                }
+            }
+        );
+        return response.data as string;
+    }
+    else {
+        throw new Error("Invalid method");
+    }
+}
+
+const phpResponseDataParser = (data : string) : object => {
+    return JSON.parse(data);
+}
+
+export default phpClient;
\ No newline at end of file
diff --git a/src/clients/soap-client.ts b/src/clients/soap-client.ts
new file mode 100644
index 0000000000000000000000000000000000000000..236b3321cb20dca1628561dc04de700d17caa997
--- /dev/null
+++ b/src/clients/soap-client.ts
@@ -0,0 +1,75 @@
+import axios from "axios";
+import {XMLBuilder, XMLParser} from "fast-xml-parser";
+
+const xmlOptions = {
+    ignoreAttributes: false,
+    attributeNamePrefix: "@_",
+}
+
+const xmlBuilder = new XMLBuilder(xmlOptions);
+const xmlParser = new XMLParser();
+
+const soapClient = async (url : string, ws_url : string, function_name : string, data : object) : Promise<object> => {
+    const responseData : string = await soapRequest(url, ws_url, function_name, data);
+    return soapResponseDataParser(responseData);
+}
+
+const soapRequest = async (
+    url : string,
+    ws_url : string,
+    function_name : string,
+    data : object
+) : Promise<string> => {
+    const envelope = {
+        "?xml": {
+            "@_version": "1.0",
+            "@_encoding": "utf-8",
+        },
+        "Envelope": {
+            "@_xmlns": "http://schemas.xmlsoap.org/soap/envelope/",
+            // "Header": {},
+            "Body": {
+                [function_name]: {
+                    "@_xmlns": ws_url,
+                    ...soapRequestDataBuilder(data),
+                }
+            },
+            }
+        }
+
+    const xmlRequest : string = xmlBuilder.build(envelope) as string;
+    const response = await axios.post(
+        url,
+        xmlRequest,
+        {
+            headers: {
+                "Content-Type": "text/xml",
+                "SOAPAction": "#POST",
+                "X-API-Key": process.env.SOAP_API_KEY,
+                Accept: "*/*",
+            },
+    });
+    return response.data as string;
+}
+
+const soapRequestDataBuilder = (data : object) : object => {
+    const soapData = {}
+    for (const [key, value ] of Object.entries(data)) {
+        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+        // @ts-ignore
+        soapData[key] = {
+            "@_xmlns": "",
+            "#text": value,
+        }
+    }
+    return soapData;
+}
+
+const soapResponseDataParser = (data : string) : object => {
+
+    const parsedData = xmlParser.parse(data);
+    const parsedBody = parsedData["S:Envelope"]["S:Body"] || parsedData["soap:Envelope"]["soap:Body"];
+    return parsedBody[Object.keys(parsedBody)[0]];
+}
+
+export { soapClient }
\ No newline at end of file
diff --git a/src/controllers/auth-controller.ts b/src/controllers/auth-controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb1044b670700a2e36f32f12d880da02f2653b1f
--- /dev/null
+++ b/src/controllers/auth-controller.ts
@@ -0,0 +1,60 @@
+import * as AuthService from "../services/auth-service";
+import { NextFunction, Request, Response } from "express";
+import { generateResponse } from "../utils/response";
+import { StatusCodes } from "http-status-codes";
+
+const signup = async (
+  req: Request,
+  res: Response,
+  next: NextFunction,
+): Promise<void> => {
+  try {
+    const createdUser = await AuthService.signup(req.body);
+    generateResponse(res, StatusCodes.OK, createdUser);
+  } catch (err) {
+    next(err);
+  }
+};
+
+// The storage of tokens on the client side follows the recommendations provided by OWASP
+// https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html#token-storage-on-client-side
+const login = async (
+  req: Request,
+  res: Response,
+  next: NextFunction,
+): Promise<void> => {
+  try {
+    console.log(req.body)
+    const accessTokenAndFingerPrint = await AuthService.login(req.body);
+    setFingerprintCookie(res, accessTokenAndFingerPrint.fingerprint);
+    generateResponse(res, StatusCodes.OK, {accessToken: accessTokenAndFingerPrint.accessToken});
+  } catch (err) {
+    next(err);
+  }
+};
+
+const setFingerprintCookie = (
+  res: Response,
+  fingerprint: string,
+): void => {
+  res.cookie("__Secure-fingerprint", fingerprint, {
+    maxAge: 60 * 60, // 60 minutes max age (same as access token expiry)
+    httpOnly: true,
+    secure: true,
+  });
+};
+
+const isUsernameAvailable = async (
+  req: Request,
+  res: Response,
+  next: NextFunction,
+) => {
+  try {
+    const isUsernameAvailable = await AuthService.isUsernameAvailable(req.body);
+    generateResponse(res, StatusCodes.OK, isUsernameAvailable);
+  } catch (err) {
+    next(err)
+  }
+}
+
+export { signup, login, isUsernameAvailable };
diff --git a/src/controllers/premium-album-controller.ts b/src/controllers/premium-album-controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bddbdef5dddfef6271819716bf9e906783f9ae18
--- /dev/null
+++ b/src/controllers/premium-album-controller.ts
@@ -0,0 +1,172 @@
+import { NextFunction, Request, Response } from "express";
+import * as PremiumAlbumService from "../services/premium-album-service";
+import { generateResponse } from "../utils/response";
+import { StatusCodes } from "http-status-codes";
+import {ErrorType, StandardError} from "../errors/standard-error";
+import phpClient from "../clients/php-client";
+import * as path from "path";
+import {v4 as uuidv4} from 'uuid';
+import saveFile from "../utils/file-processing";
+
+const createPremiumAlbum = async (
+  req: Request,
+  res: Response,
+  next: NextFunction,
+): Promise<void> => {
+  try {
+    const data = req.body;
+    if (!req.file) {
+        throw new StandardError(ErrorType.FILE_NOT_VALID);
+    }
+    // data.coverFilename = req.file.filename;
+    data.coverFilename = uuidv4() + path.extname(req.file.originalname);
+    const responseData = await PremiumAlbumService.createPremiumAlbum(data);
+    await phpClient(
+        process.env.PHP_URL + "upload",
+        "POST",
+        {
+          file : {
+            filename: data.coverFilename,
+            buffer: req.file.buffer
+          }
+        },
+        true
+    )
+    await saveFile(req.file, data.coverFilename)
+    generateResponse(res, StatusCodes.OK, responseData);
+  } catch (err) {
+    next(err);
+  }
+};
+
+const getPremiumAlbumById = async (
+  req: Request,
+  res: Response,
+  next: NextFunction,
+): Promise<void> => {
+  try {
+    const premiumAlbumId = Number(req.params.premiumAlbumId);
+    const responseData = await PremiumAlbumService.getPremiumAlbumById(
+      premiumAlbumId,
+    );
+    generateResponse(res, StatusCodes.OK, responseData);
+  } catch (err) {
+    next(err);
+  }
+}
+
+const searchPremiumAlbum = async (
+  req: Request,
+  res: Response,
+  next: NextFunction,
+): Promise<void> => {
+  try {
+    const allPremiumAlbum = await PremiumAlbumService.searchPremiumAlbum(
+        {
+          size: req.query.size ? Number(req.query.size) : undefined,
+          page: req.query.page ? Number(req.query.page) : undefined,
+          searchQuery: req.query.searchQuery ? String(req.query.searchQuery) : undefined,
+        }
+    );
+    generateResponse(res, StatusCodes.OK, allPremiumAlbum);
+  } catch (err) {
+    next(err);
+  }
+};
+
+const searchPremiumAlbumOwned = async (
+    req: Request,
+    res: Response,
+    next: NextFunction,
+): Promise<void> => {
+    try {
+        const allPremiumAlbum = await PremiumAlbumService.searchPremiumAlbumOwned(
+            {
+                size: req.query.size ? Number(req.query.size) : undefined,
+                page: req.query.page ? Number(req.query.page) : undefined,
+                searchQuery: req.query.searchQuery ? String(req.query.searchQuery) : undefined,
+                premiumAlbumIds: JSON.parse(<string>req.query.premiumAlbumIds, (key, value) => {
+                    if (key === "") return value;
+                    return typeof value === "string" ? Number(value) : value;
+                }),
+            },
+        );
+        generateResponse(res, StatusCodes.OK, allPremiumAlbum);
+    } catch (err) {
+        next(err);
+    }
+}
+
+const updatePremiumAlbum = async (
+  req: Request,
+  res: Response,
+  next: NextFunction,
+): Promise<void> => {
+  try {
+    const premiumAlbumId = Number(req.params.premiumAlbumId);
+    const data = req.body;
+
+    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+    // @ts-ignore
+    if (req.files && req.files[0]) {
+      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+      // @ts-ignore
+      data.coverFilename = uuidv4() + path.extname(req.files[0].originalname);
+    }
+
+    const updatedPremiumAlbum = await PremiumAlbumService.updatePremiumAlbum(
+      data,
+      premiumAlbumId,
+    );
+    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+    // @ts-ignore
+    if (req.files && req.files[0]) {
+      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+      // @ts-ignore
+      await phpClient(
+          process.env.PHP_URL + "upload",
+          "POST",
+          {
+              file : {
+                  filename: data.coverFilename,
+                  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+                  // @ts-ignore
+                  buffer: req.files[0].buffer
+              }
+          },
+          true
+      )
+      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+      // @ts-ignore
+      await saveFile(req.files[0], data.coverFilename)
+    }
+    generateResponse(res, StatusCodes.OK, updatedPremiumAlbum);
+  } catch (err) {
+    next(err);
+  }
+};
+
+const deletePremiumAlbum = async (
+  req: Request,
+  res: Response,
+  next: NextFunction,
+): Promise<void> => {
+  try {
+    const premiumAlbumId = Number(req.params.premiumAlbumId);
+
+    const deletedPremiumAlbum =
+      await PremiumAlbumService.deletePremiumAlbum(premiumAlbumId);
+    generateResponse(res, StatusCodes.OK, deletedPremiumAlbum);
+  } catch (err) {
+    next(err);
+  }
+};
+
+export {
+  createPremiumAlbum,
+  getPremiumAlbumById,
+  searchPremiumAlbum,
+  searchPremiumAlbumOwned,
+  updatePremiumAlbum,
+  deletePremiumAlbum,
+};
diff --git a/src/controllers/premium-song-controller.ts b/src/controllers/premium-song-controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..104df9bbf89699afe78bdeb7233dbb89eb11d847
--- /dev/null
+++ b/src/controllers/premium-song-controller.ts
@@ -0,0 +1,137 @@
+import { NextFunction, Request, Response } from "express";
+import * as PremiumSongService from "../services/premium-song-services"
+import { generateResponse } from "../utils/response";
+import { StatusCodes } from "http-status-codes";
+import {ErrorType, StandardError} from "../errors/standard-error";
+import phpClient from "../clients/php-client";
+import {v4 as uuidv4} from "uuid";
+import path from "path";
+import saveFile from "../utils/file-processing";
+
+const addNewSong = async (
+    req: Request,
+    res: Response,
+    next: NextFunction,
+): Promise<void> => {
+    try {
+        const data = req.body;
+        const premiumAlbumId = Number(req.params.premiumAlbumId);
+        if (!req.file) {
+            throw new StandardError(ErrorType.FILE_NOT_VALID);
+        }
+        data.audioFilename = uuidv4() + path.extname(req.file.originalname);
+        data.songNumber = Number(data.songNumber);
+        if (data.discNumber) {
+            data.discNumber = Number(data.discNumber);
+        }
+        data.duration = Number(data.duration);
+        const responseData = await PremiumSongService.addNewSong(data, premiumAlbumId);
+        await phpClient(
+            process.env.PHP_URL + "upload",
+            "POST",
+            {
+                file : {
+                    filename: data.audioFilename,
+                    buffer: req.file.buffer
+                }
+            },
+            true
+        )
+        await saveFile(req.file, data.audioFilename)
+        generateResponse(res, StatusCodes.OK, responseData);
+    } catch (err) {
+        next(err);
+    }
+};
+
+const getAllSongFromAlbum = async (
+    req: Request,
+    res: Response,
+    next: NextFunction,
+): Promise<void> => {
+    try {
+        const premiumAlbumId = Number(req.params.premiumAlbumId);
+        const responseData = await PremiumSongService.getAllSongFromAlbum(premiumAlbumId);
+        generateResponse(res, StatusCodes.OK, responseData);
+    } catch (err) {
+        next(err);
+    }
+};
+
+const updatePremiumSong = async (
+    req: Request,
+    res: Response,
+    next: NextFunction,
+): Promise<void> => {
+    try {
+        const data = req.body;
+        const premiumAlbumId = Number(req.params.premiumAlbumId);
+        const premiumSongId = Number(req.params.premiumSongId);
+        if (data.songNumber) {
+            data.songNumber = Number(data.songNumber);
+        }
+        if (data.discNumber) {
+            data.discNumber = Number(data.discNumber);
+        }
+        if (data.duration) {
+            data.duration = Number(data.duration);
+        }
+
+        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+        // @ts-ignore
+        if (req.files && req.files[0]) {
+            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+            // @ts-ignore
+            data.audioFilename = uuidv4() + path.extname(req.files[0].originalname);
+        }
+        const responseData = await PremiumSongService.updatePremiumSong(data, premiumAlbumId, premiumSongId);
+        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+        // @ts-ignore
+        if (req.files && req.files[0]) {
+            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+            // @ts-ignore
+            await phpClient(
+                process.env.PHP_URL + "upload",
+                "POST",
+                {
+                    file : {
+                        filename: data.audioFilename,
+                        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+                        // @ts-ignore
+                        buffer: req.files[0].buffer
+                    }
+                },
+                true
+            )
+            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+            // @ts-ignore
+            await saveFile(req.files[0], data.audioFilename)
+        }
+        generateResponse(res, StatusCodes.OK, responseData);
+    } catch (err) {
+        next(err);
+    }
+};
+
+const deletePremiumSong = async (
+    req: Request,
+    res: Response,
+    next: NextFunction,
+): Promise<void> => {
+    try {
+        const premiumAlbumId = Number(req.params.premiumAlbumId);
+        const premiumSongId = Number(req.params.premiumSongId);
+        const responseData = await PremiumSongService.deletePremiumSong(premiumAlbumId, premiumSongId);
+        generateResponse(res, StatusCodes.OK, responseData);
+    } catch (err) {
+        next(err);
+    }
+};
+
+
+export {
+    addNewSong,
+    getAllSongFromAlbum,
+    updatePremiumSong,
+    deletePremiumSong,
+};
diff --git a/src/controllers/subscription-controller.ts b/src/controllers/subscription-controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a8eafba279e916200814055139a0dfc275ee4c28
--- /dev/null
+++ b/src/controllers/subscription-controller.ts
@@ -0,0 +1,46 @@
+import { NextFunction, Request, Response } from "express";
+import * as SubscriptionService from "../services/subscription-service";
+import {generateResponse} from "../utils/response";
+import {StatusCodes} from "http-status-codes";
+import SubscriptionStatus from "../type/subscription-status";
+
+const soapUrl = process.env.SOAP_URL + "subscription";
+const soapWSUrl = process.env.SOAP_WS_URL as string
+
+const updateSubscription = async (
+    req: Request,
+    res: Response,
+    next: NextFunction,
+): Promise<void> => {
+    try {
+        const data = req.body;
+        const responseData = await SubscriptionService.updateSubscription(soapUrl, soapWSUrl, data);
+        generateResponse(res, StatusCodes.OK, responseData);
+    } catch (err) {
+        next(err);
+    }
+}
+
+const searchSubscription = async (
+    req: Request,
+    res: Response,
+    next: NextFunction,
+): Promise<void> => {
+    try {
+        const responseData = await SubscriptionService.searchSubscription(soapUrl, soapWSUrl, {
+            status: req.query.status as SubscriptionStatus,
+            searchInput: req.query.searchInput as string ?? "",
+            orderBy: req.query.orderBy as string ?? "",
+            page: req.query.page ? Number(req.query.page) : 1,
+            size: req.query.size ? Number(req.query.size) : 15,
+        });
+        generateResponse(res, StatusCodes.OK, responseData);
+    } catch (err) {
+        next(err);
+    }
+}
+
+export {
+    updateSubscription,
+    searchSubscription,
+}
\ No newline at end of file
diff --git a/src/cores/app.ts b/src/cores/app.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4a3946038c60fa74f52cb601bcfc754e2712b66e
--- /dev/null
+++ b/src/cores/app.ts
@@ -0,0 +1,43 @@
+import express, { Express } from "express";
+import cookieParser from "cookie-parser";
+import cors from "cors";
+import dotenv from "dotenv";
+import apiRouter from "../routers/api";
+
+dotenv.config();
+
+export const app: Express = express();
+const port: string | undefined = process.env.EXPRESS_PORT;
+
+const allowedOrigin = [
+  'http://localhost:3000',
+  'http://localhost:3001',
+  'http://localhost:5173',
+  'http://localhost:8000',
+  'http://localhost:8888',
+]
+
+app.use(cors({
+  origin: function (origin, callback) {
+    if (!origin) return callback(null, true)
+    if (allowedOrigin.indexOf(origin) === -1) {
+      const msg = 'The CORS policy for this site does not ' +
+        'allow access from the specified Origin.'
+      return callback(new Error(msg), false)
+    }
+    return callback(null, true)
+  },
+  credentials: true
+}));
+
+app.use(express.json());
+
+app.use(cookieParser());
+app.use(apiRouter);
+
+// app.use(express.static(path.join(__dirname, '..', 'storage')));
+app.use(express.static('storage'));
+
+app.listen(port, () => {
+  return console.log(`Express is listening at port ${port}`);
+});
diff --git a/src/cores/db.ts b/src/cores/db.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a4e9782db9353445fc0ab001ca2a7c0c305efd95
--- /dev/null
+++ b/src/cores/db.ts
@@ -0,0 +1,41 @@
+import { PrismaClient } from "@prisma/client";
+import logger from "./logger";
+
+const prismaClient = new PrismaClient({
+  log: [
+    {
+      emit: "event",
+      level: "query",
+    },
+    {
+      emit: "event",
+      level: "info",
+    },
+    {
+      emit: "event",
+      level: "warn",
+    },
+    {
+      emit: "event",
+      level: "error",
+    },
+  ],
+});
+
+prismaClient.$on("query", (e) => {
+  logger.info(e);
+});
+
+prismaClient.$on("info", (e) => {
+  logger.info(e);
+});
+
+prismaClient.$on("warn", (e) => {
+  logger.warn(e);
+});
+
+prismaClient.$on("error", (e) => {
+  logger.error(e);
+});
+
+export default prismaClient;
diff --git a/src/cores/logger.ts b/src/cores/logger.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b280c518fe7cbfe98e29084b35d7738288780832
--- /dev/null
+++ b/src/cores/logger.ts
@@ -0,0 +1,9 @@
+import winston from "winston";
+
+const logger = winston.createLogger({
+  level: "info",
+  format: winston.format.json(),
+  transports: [new winston.transports.Console({})],
+});
+
+export default logger;
diff --git a/src/errors/standard-error.ts b/src/errors/standard-error.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0cbbacd9aaee0574c69ef9efe906bd7d9fc7461c
--- /dev/null
+++ b/src/errors/standard-error.ts
@@ -0,0 +1,122 @@
+import { StatusCodes } from "http-status-codes";
+
+enum ErrorType {
+  USERNAME_ALREADY_EXISTS,
+  USER_NOT_FOUND,
+  WRONG_PASSWORD,
+  PASSWORD_HASH_FAILURE,
+  PASSWORD_VERIFICATION_FAILURE,
+  ACCESS_TOKEN_GENERATION_FAILURE,
+  ACCESS_TOKEN_MISSING,
+  ACCESS_TOKEN_EXPIRED,
+  ACCESS_TOKEN_NOT_ACTIVE,
+  INVALID_SIGNATURE,
+  AUTHORIZATION_HEADER_NOT_SET,
+  FINGERPRINT_MISSING,
+  ALBUM_NOT_FOUND,
+  SONG_NOT_FOUND,
+  INPUT_DATA_NOT_VALID,
+  INVALID_API_KEY,
+  FILE_NOT_VALID,
+}
+
+class StandardError {
+  title: string;
+  status: number;
+
+  constructor(t: ErrorType) {
+    switch (t) {
+
+      case ErrorType.USERNAME_ALREADY_EXISTS:
+        this.title = "Username already exists.";
+        this.status = StatusCodes.BAD_REQUEST;
+        break;
+
+      case ErrorType.USER_NOT_FOUND:
+        this.title = "User not found.";
+        this.status = StatusCodes.BAD_REQUEST;
+        break;
+
+      case ErrorType.WRONG_PASSWORD:
+        this.title = "Wrong password."
+        this.status = StatusCodes.UNAUTHORIZED;
+        break;
+
+      case ErrorType.PASSWORD_HASH_FAILURE:
+        this.title = "Failed to hash password."
+        this.status = StatusCodes.INTERNAL_SERVER_ERROR;
+        break;
+
+      case ErrorType.PASSWORD_VERIFICATION_FAILURE:
+        this.title = "Failed to verify password."
+        this.status = StatusCodes.INTERNAL_SERVER_ERROR;
+        break;
+
+      case ErrorType.ACCESS_TOKEN_GENERATION_FAILURE:
+        this.title = "Failed to generate access token."
+        this.status = StatusCodes.INTERNAL_SERVER_ERROR;
+        break;
+
+      case ErrorType.ACCESS_TOKEN_MISSING:
+        this.title = "Your access token is missing."
+        this.status = StatusCodes.UNAUTHORIZED;
+        break;
+
+      case ErrorType.ACCESS_TOKEN_EXPIRED:
+        this.title = "Your access token is expired."
+        this.status = StatusCodes.UNAUTHORIZED;
+        break;
+
+      case ErrorType.ACCESS_TOKEN_NOT_ACTIVE:
+        this.title = "Your access token is not active yet."
+        this.status = StatusCodes.UNAUTHORIZED;
+        break;
+
+      case ErrorType.INVALID_SIGNATURE:
+        this.title = "Your access token is invalid."
+        this.status = StatusCodes.UNAUTHORIZED;
+        break;
+
+      case ErrorType.AUTHORIZATION_HEADER_NOT_SET:
+        this.title = "Authorization header not set."
+        this.status = StatusCodes.UNAUTHORIZED;
+        break;
+
+      case ErrorType.FINGERPRINT_MISSING:
+        this.title = "Fingerprint is missing."
+        this.status = StatusCodes.UNAUTHORIZED;
+        break;
+
+      case ErrorType.ALBUM_NOT_FOUND:
+        this.title = "Album not found."
+        this.status = StatusCodes.NOT_FOUND;
+        break;
+
+      case ErrorType.SONG_NOT_FOUND:
+        this.title = "Song not found"
+        this.status = StatusCodes.NOT_FOUND;
+        break;
+
+      case ErrorType.INPUT_DATA_NOT_VALID:
+        this.title = "Input data is not valid."
+        this.status = StatusCodes.BAD_REQUEST;
+        break;
+
+      case ErrorType.INVALID_API_KEY:
+        this.title = "Your API key is invalid."
+        this.status = StatusCodes.UNAUTHORIZED;
+        break;
+
+      case ErrorType.FILE_NOT_VALID:
+        this.title = "Your File is invalid."
+        this.status = StatusCodes.BAD_REQUEST;
+        break;
+
+      default:
+        this.title = "Unknown error.";
+        this.status = StatusCodes.INTERNAL_SERVER_ERROR;
+    }
+  }
+}
+
+export { StandardError, ErrorType };
diff --git a/src/middlewares/handle-standard-error.ts b/src/middlewares/handle-standard-error.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fc0b4af0a7118de75e0f05f4fc3add1e1e463be5
--- /dev/null
+++ b/src/middlewares/handle-standard-error.ts
@@ -0,0 +1,18 @@
+import { NextFunction, Request, Response } from "express";
+import { StandardError } from "../errors/standard-error";
+import { generateStandardErrorResponse } from "../utils/response";
+
+const handleStandardError = (
+  err: StandardError | Error,
+  req: Request,
+  res: Response,
+  next: NextFunction,
+): void => {
+  if (!(err instanceof StandardError)) {
+    next(err); // Pass to the default error-handling middleware (provided by Express)
+  } else {
+    generateStandardErrorResponse(res, err);
+  }
+};
+
+export { handleStandardError };
diff --git a/src/middlewares/verify-token.ts b/src/middlewares/verify-token.ts
new file mode 100644
index 0000000000000000000000000000000000000000..68d69e311b83a454c252995c88c361a46ba554c7
--- /dev/null
+++ b/src/middlewares/verify-token.ts
@@ -0,0 +1,74 @@
+import { NextFunction, Request, Response } from "express";
+import jwt, {JsonWebTokenError, NotBeforeError, TokenExpiredError} from "jsonwebtoken";
+import { ErrorType, StandardError } from "../errors/standard-error";
+import { hashFingerprint } from "../utils/token";
+
+interface TonalityPayload {
+  uid: number;
+  usr: string;
+  fgp: string;
+  exp: number;
+  nbf: number;
+  iss: string;
+}
+
+// Compare fingerprint in the token payload with
+// the fingerprint stored in the cookie
+const verifyFingerprint = async (
+  fingerprint: string,
+  d: TonalityPayload,
+): Promise<boolean> => {
+  return (await hashFingerprint(fingerprint)) === d.fgp;
+};
+
+const verifyToken = async (req: Request, res: Response, next: NextFunction) => {
+  try {
+  // Authorization Bearer ${accessToken}
+    const authHeader = req.headers.authorization;
+
+    if (!authHeader) {
+      throw new StandardError(ErrorType.AUTHORIZATION_HEADER_NOT_SET);
+    }
+
+    const accessToken = authHeader.split(" ")[1];
+
+    if (!accessToken) {
+      throw new StandardError(ErrorType.ACCESS_TOKEN_MISSING);
+    }
+
+    const fingerprint = req.cookies["Secure-fingerprint"];
+
+    if (!fingerprint) {
+      throw new StandardError(ErrorType.FINGERPRINT_MISSING);
+    }
+
+    const decodedPayload = jwt.verify(
+      accessToken,
+      process.env.JWT_SHARED_SECRET as string,
+      {
+        algorithms: ["HS256"],
+        issuer: "Tonality REST Service",
+      },
+    );
+
+    // Will generate run-time error when the decoded payload is not of the type TonalityPayload
+    // This is a wanted behavior because we want to validate the payload type
+    await verifyFingerprint(fingerprint, decodedPayload as TonalityPayload);
+
+    next(); // The token is verified, pass to the next middleware
+  } catch (error) {
+    if (error instanceof TokenExpiredError) {
+      next(new StandardError(ErrorType.ACCESS_TOKEN_EXPIRED));
+    } else if (error instanceof NotBeforeError) {
+      next(new StandardError(ErrorType.ACCESS_TOKEN_NOT_ACTIVE));
+    } else if (error instanceof JsonWebTokenError) {
+        next(new StandardError(ErrorType.INVALID_SIGNATURE));
+    } else if (error instanceof StandardError) {
+        next(error);
+    }
+    // unknown error
+    next(error);
+  }
+};
+
+export { verifyToken };
diff --git a/src/routers/api.ts b/src/routers/api.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c6ec41aef6caac6bf2080abb448968fed34c4bbe
--- /dev/null
+++ b/src/routers/api.ts
@@ -0,0 +1,14 @@
+import express from "express";
+import { premiumAlbumRouter } from "./premium-album-router";
+import { authRouter } from "./auth-router";
+import {subscriptionRouter} from "./subscription-router";
+import {premiumSongRouter} from "./premium-song-router";
+
+const apiRouter = express.Router();
+
+apiRouter.use(authRouter);
+apiRouter.use(premiumAlbumRouter);
+apiRouter.use(premiumSongRouter);
+apiRouter.use(subscriptionRouter);
+
+export default apiRouter;
diff --git a/src/routers/auth-router.ts b/src/routers/auth-router.ts
new file mode 100644
index 0000000000000000000000000000000000000000..caead52e784b2ac7fc4e11c6809e5061fd486e0a
--- /dev/null
+++ b/src/routers/auth-router.ts
@@ -0,0 +1,25 @@
+import express, { Router } from "express";
+import * as AuthController from "../controllers/auth-controller";
+import { handleStandardError } from "../middlewares/handle-standard-error";
+
+const authRouter: Router = express.Router();
+
+authRouter.post(
+  "/api/signup",
+  AuthController.signup,
+  handleStandardError
+);
+
+authRouter.post(
+  "/api/login",
+  AuthController.login,
+  handleStandardError
+);
+
+authRouter.post(
+  "/api/username-availability",
+  AuthController.isUsernameAvailable,
+  handleStandardError
+);
+
+export { authRouter };
diff --git a/src/routers/premium-album-router.ts b/src/routers/premium-album-router.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4635ba376cacf0e907b02215d1970475b7356818
--- /dev/null
+++ b/src/routers/premium-album-router.ts
@@ -0,0 +1,54 @@
+import express, { Router } from "express";
+import * as PremiumAlbumController from "../controllers/premium-album-controller";
+import { handleStandardError } from "../middlewares/handle-standard-error";
+import { verifyToken } from "../middlewares/verify-token";
+import {uploadCover} from "../utils/multer";
+
+const premiumAlbumRouter: Router = express.Router();
+
+premiumAlbumRouter.post(
+  "/api/premium-album",
+  verifyToken,
+  uploadCover.single("coverFile"),
+  PremiumAlbumController.createPremiumAlbum,
+  handleStandardError
+);
+
+premiumAlbumRouter.get(
+  "api/premium-album/:premiumAlbumId",
+  verifyToken,
+  PremiumAlbumController.getPremiumAlbumById,
+  handleStandardError,
+);
+
+
+premiumAlbumRouter.get(
+  "/api/premium-album",
+  verifyToken,
+  PremiumAlbumController.searchPremiumAlbum,
+  handleStandardError,
+);
+
+premiumAlbumRouter.get(
+  "api/premium-album-owned",
+  verifyToken,
+  PremiumAlbumController.searchPremiumAlbumOwned,
+  handleStandardError,
+)
+
+premiumAlbumRouter.patch(
+  "/api/premium-album/:premiumAlbumId",
+  verifyToken,
+  uploadCover.any(),
+  PremiumAlbumController.updatePremiumAlbum,
+  handleStandardError,
+);
+
+premiumAlbumRouter.delete(
+  "/api/premium-album/:premiumAlbumId",
+  verifyToken,
+  PremiumAlbumController.deletePremiumAlbum,
+  handleStandardError,
+);
+
+export { premiumAlbumRouter };
diff --git a/src/routers/premium-song-router.ts b/src/routers/premium-song-router.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c31161f478162bc16534cb51965bf126ff966d18
--- /dev/null
+++ b/src/routers/premium-song-router.ts
@@ -0,0 +1,39 @@
+import express, { Router } from "express";
+import * as PremiumSongController from "../controllers/premium-song-controller";
+import { handleStandardError } from "../middlewares/handle-standard-error";
+import { verifyToken } from "../middlewares/verify-token";
+import {uploadSong} from "../utils/multer";
+
+const premiumSongRouter: Router = express.Router();
+
+premiumSongRouter.post(
+    "/api/premium-album/:premiumAlbumId",
+    verifyToken,
+    uploadSong.single("audioFile"),
+    PremiumSongController.addNewSong,
+    handleStandardError,
+);
+
+premiumSongRouter.get(
+    "/api/premium-album/:premiumAlbumId",
+    verifyToken,
+    PremiumSongController.getAllSongFromAlbum,
+    handleStandardError,
+);
+
+premiumSongRouter.patch(
+    "/api/premium-album/:premiumAlbumId/:premiumSongId",
+    verifyToken,
+    uploadSong.any(),
+    PremiumSongController.updatePremiumSong,
+    handleStandardError,
+);
+
+premiumSongRouter.delete(
+    "/api/premium-album/:premiumAlbumId/:premiumSongId",
+    verifyToken,
+    PremiumSongController.deletePremiumSong,
+    handleStandardError,
+);
+
+export { premiumSongRouter };
diff --git a/src/routers/subscription-router.ts b/src/routers/subscription-router.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0cee04ff668ffa4312930128d4eb6650f7450b15
--- /dev/null
+++ b/src/routers/subscription-router.ts
@@ -0,0 +1,22 @@
+import express, { Router } from "express";
+import * as SubscriptionController from "../controllers/subscription-controller";
+import { handleStandardError } from "../middlewares/handle-standard-error";
+import { verifyToken } from "../middlewares/verify-token";
+
+const subscriptionRouter: Router = express.Router();
+
+subscriptionRouter.post(
+    "/api/subscription",
+    verifyToken,
+    SubscriptionController.updateSubscription,
+    handleStandardError,
+);
+
+subscriptionRouter.get(
+    "/api/subscription",
+    verifyToken,
+    SubscriptionController.searchSubscription,
+    handleStandardError,
+);
+
+export { subscriptionRouter };
\ No newline at end of file
diff --git a/src/services/auth-service.ts b/src/services/auth-service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..24c92585b2e2b6308af56a94fa5335f0872d1494
--- /dev/null
+++ b/src/services/auth-service.ts
@@ -0,0 +1,92 @@
+import { Prisma } from "@prisma/client";
+import prismaClient from "../cores/db";
+
+import { ErrorType, StandardError } from "../errors/standard-error";
+import { hashPassword, isPasswordValid } from "../utils/password";
+import { generateAccessTokenAndFingerprint } from "../utils/token";
+import { validate } from "../validation/validation";
+import { loginSchema, signupSchema } from "../validation/auth-validation";
+
+const signup = async (
+  data: Prisma.UserCreateInput,
+): Promise<{ userId: number; username: string }> => {
+  validate(signupSchema, data);
+
+  // If username already exists throw error
+  if (
+    (await prismaClient.user.findUnique({
+      where: {
+        username: data.username,
+      },
+    })) !== null
+  ) {
+    throw new StandardError(ErrorType.USERNAME_ALREADY_EXISTS);
+  }
+
+  try {
+    // Hash the password
+    const hashedPassword: string = await hashPassword(data.password);
+
+    return await prismaClient.user.create({
+      data: {
+        username: data.username,
+        password: hashedPassword,
+      },
+      select: {
+        userId: true,
+        username: true,
+      },
+    });
+  } catch (error) {
+    throw error;
+  }
+};
+
+const login = async (data: { username: string; password: string }) => {
+  validate(loginSchema, data);
+
+  const user = await prismaClient.user.findUnique({
+    where: {
+      username: data.username,
+    },
+  });
+
+  // If username not found, throw error
+  if (user === null) {
+    throw new StandardError(ErrorType.USER_NOT_FOUND);
+  }
+
+  // If wrong password, throw error
+  if (!(await isPasswordValid(user.password, data.password))) {
+    throw new StandardError(ErrorType.WRONG_PASSWORD);
+  }
+
+  try {
+    return await generateAccessTokenAndFingerprint({
+      userId: user.userId,
+      username: user.username,
+    });
+  } catch (error) {
+    throw error;
+  }
+};
+
+const isUsernameAvailable = async (data: { username: string }) => {
+  const user = await prismaClient.user.findUnique({
+    where: {
+      username: data.username,
+    }
+  })
+
+  if (user !== null) {
+    return {
+      usernameAvailable: "false",
+    }
+  } else {
+    return {
+      usernameAvailable: "true",
+    }
+  }
+}
+
+export { signup, login, isUsernameAvailable };
diff --git a/src/services/premium-album-service.ts b/src/services/premium-album-service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2b0701b15601e05699fcb131d682286b514fe6b6
--- /dev/null
+++ b/src/services/premium-album-service.ts
@@ -0,0 +1,234 @@
+import {PremiumAlbum, Prisma} from "@prisma/client";
+import prismaClient from "../cores/db";
+import {ErrorType, StandardError} from "../errors/standard-error";
+import {validate} from "../validation/validation";
+import {
+  createPremiumAlbumSchema,
+  deletePremiumAlbumSchema, searchPremiumAlbumOwnSchema,
+  searchPremiumAlbumSchema,
+  updatePremiumAlbumSchema
+} from "../validation/premium-album-validation";
+
+const createPremiumAlbum = async (
+  data: Prisma.PremiumAlbumCreateInput,
+): Promise<PremiumAlbum> => {
+  validate(createPremiumAlbumSchema, data)
+
+  return prismaClient.premiumAlbum.create({
+    data: {
+      albumName: data.albumName,
+      releaseDate: data.releaseDate,
+      genre: data.genre,
+      artist: data.artist,
+      coverFilename: data.coverFilename,
+    },
+  });
+};
+
+const getPremiumAlbumById = async (
+  premiumAlbumId: number,
+): Promise<{ data: PremiumAlbum, duration: number }> => {
+  const album = await prismaClient.premiumAlbum.findUnique({
+    where: {
+      albumId: premiumAlbumId,
+    },
+  });
+
+  if (!album) {
+    throw new StandardError(ErrorType.ALBUM_NOT_FOUND);
+  }
+
+  const albumDuration = await prismaClient.premiumSong.aggregate({
+    where: {
+      albumId: premiumAlbumId
+    },
+    _sum: {
+      duration: true
+    }
+  })
+
+  return {
+    data : album,
+    duration: albumDuration._sum.duration ?? 0
+  };
+};
+
+const searchPremiumAlbum = async (reqQuery: {
+  size: number | undefined;
+  page: number | undefined;
+  searchQuery: string | undefined;
+}) => {
+  validate(searchPremiumAlbumSchema, reqQuery)
+
+  const skip: number = ((reqQuery.page ?? 1) - 1) * (reqQuery.size ?? 10);
+
+  const filters = [];
+
+  if (reqQuery.searchQuery) {
+    filters.push({
+      OR: [
+        {
+          albumName: {
+            contains: reqQuery.searchQuery,
+          },
+        },
+        {
+          artist: {
+            contains: reqQuery.searchQuery,
+          },
+        },
+      ],
+    });
+  }
+
+  const albums = await prismaClient.premiumAlbum.findMany({
+    where: {
+      AND: filters,
+    },
+    take: reqQuery.size ?? 10,
+    skip: skip,
+  });
+
+  const totalAlbums = await prismaClient.premiumAlbum.count({
+    where: {
+      AND: filters,
+    },
+  });
+
+  return {
+    data: albums,
+    paging: {
+      page: reqQuery.page ?? 1,
+      totalAlbums: totalAlbums,
+      totalPages: Math.ceil(totalAlbums / (reqQuery.size ?? 10)),
+    },
+  };
+};
+
+const searchPremiumAlbumOwned = async (reqQuery: {
+  size: number | undefined;
+  page: number | undefined;
+  searchQuery: string | undefined;
+  premiumAlbumIds : number[]
+}) => {
+  validate(searchPremiumAlbumOwnSchema, reqQuery)
+
+  const skip: number = ((reqQuery.page ?? 1) - 1) * (reqQuery.size ?? 10);
+
+  const filters = [];
+
+  if (reqQuery.searchQuery) {
+    filters.push({
+      OR: [
+        {
+          albumName: {
+            contains: reqQuery.searchQuery,
+          },
+        },
+        {
+          artist: {
+            contains: reqQuery.searchQuery,
+          },
+        },
+      ],
+    });
+  }
+
+
+  const albums = await prismaClient.premiumAlbum.findMany({
+    where: {
+      AND: [
+        {
+          albumId: {
+            in: reqQuery.premiumAlbumIds
+          }
+        },
+        {
+          AND: filters,
+        }
+      ]
+    },
+    take: reqQuery.size ?? 10,
+    skip: skip,
+  });
+
+  const totalAlbums = await prismaClient.premiumAlbum.count({
+    where: {
+        AND: [
+            {
+            albumId: {
+                in: reqQuery.premiumAlbumIds
+            }
+            },
+            {
+            AND: filters,
+            }
+        ]
+    },
+  });
+
+  return {
+    data: albums,
+    paging: {
+      page: reqQuery.page ?? 1,
+      totalAlbums: totalAlbums,
+      totalPages: Math.ceil(totalAlbums / (reqQuery.size ?? 10)),
+    },
+  };
+};
+
+
+const updatePremiumAlbum = async (
+  inputData: Prisma.PremiumAlbumUpdateInput,
+  premiumAlbumId: number,
+): Promise<PremiumAlbum> => {
+  validate(updatePremiumAlbumSchema, { premiumAlbumId, ...inputData})
+
+  const albumCount = await prismaClient.premiumAlbum.count({
+    where: {
+      albumId: premiumAlbumId,
+    },
+  });
+
+  if (albumCount !== 1) {
+    throw new StandardError(ErrorType.ALBUM_NOT_FOUND);
+  }
+
+  return prismaClient.premiumAlbum.update({
+    where: {
+      albumId: premiumAlbumId,
+    },
+    data: inputData,
+  });
+};
+
+const deletePremiumAlbum = async (
+  premiumAlbumId: number,
+): Promise<PremiumAlbum> => {
+  validate(deletePremiumAlbumSchema, { premiumAlbumId })
+
+  const albumCount = await prismaClient.premiumAlbum.count({
+    where: {
+      albumId: premiumAlbumId,
+    },
+  });
+
+  if (albumCount !== 1) {
+    throw new StandardError(ErrorType.ALBUM_NOT_FOUND);
+  }
+
+  return prismaClient.premiumAlbum.delete({
+    where: {
+      albumId: premiumAlbumId,
+    },
+  });
+};
+
+export {
+  createPremiumAlbum,
+  getPremiumAlbumById,
+  searchPremiumAlbum,
+  searchPremiumAlbumOwned,
+  updatePremiumAlbum,
+  deletePremiumAlbum,
+};
diff --git a/src/services/premium-song-services.ts b/src/services/premium-song-services.ts
new file mode 100644
index 0000000000000000000000000000000000000000..41c8e221b4f197c8ebba883920e480a8188f17bc
--- /dev/null
+++ b/src/services/premium-song-services.ts
@@ -0,0 +1,97 @@
+import { PremiumSong, Prisma } from "@prisma/client";
+import prismaClient from "../cores/db";
+import { ErrorType, StandardError } from "../errors/standard-error";
+import {validate} from "../validation/validation";
+import {
+    addNewSongSchema, deletePremiumSongSchema,
+    getAllSongFromAlbumSchema, updatePremiumSongSchema
+} from "../validation/premium-song-validation";
+
+const addNewSong = async (
+    data: Prisma.PremiumSongCreateInput,
+    premiumAlbumId: number
+): Promise<PremiumSong> => {
+    validate(addNewSongSchema, { premiumAlbumId, ...data});
+
+    return prismaClient.premiumSong.create({
+        data: {
+            albumId: premiumAlbumId,
+            title: data.title,
+            artist: data.artist,
+            songNumber: data.songNumber,
+            discNumber: data.discNumber,
+            duration: data.duration,
+            audioFilename: data.audioFilename
+        }
+    })
+};
+
+const getAllSongFromAlbum = async (
+    premiumAlbumId: number
+): Promise<PremiumSong[]> => {
+    validate(getAllSongFromAlbumSchema, { premiumAlbumId })
+    return prismaClient.premiumSong.findMany({
+        where: {
+            albumId: premiumAlbumId
+        }
+    })
+}
+
+const updatePremiumSong = async (
+    inputData: Prisma.PremiumSongUpdateInput,
+    premiumAlbumId: number,
+    premiumSongId: number
+): Promise<PremiumSong> => {
+    validate(updatePremiumSongSchema, { premiumAlbumId, premiumSongId, ...inputData })
+
+    const songCount = await prismaClient.premiumSong.count({
+        where: {
+            songId: premiumSongId,
+            albumId: premiumAlbumId
+        }
+    });
+
+    if (songCount != 1) {
+        throw new StandardError(ErrorType.SONG_NOT_FOUND);
+    }
+
+    return prismaClient.premiumSong.update({
+        where: {
+            songId: premiumSongId,
+            albumId: premiumAlbumId
+        },
+        data: inputData
+    })
+};
+
+const deletePremiumSong = async (
+    premiumAlbumId: number,
+    premiumSongId: number
+): Promise<PremiumSong> => {
+    validate(deletePremiumSongSchema, { premiumAlbumId })
+
+    const songCount = await prismaClient.premiumSong.count({
+        where: {
+            songId: premiumSongId,
+            albumId: premiumAlbumId
+        }
+    })
+
+    if (songCount != 1) {
+        throw new StandardError(ErrorType.SONG_NOT_FOUND);
+    }
+
+    return prismaClient.premiumSong.delete({
+        where: {
+            songId: premiumSongId,
+            albumId: premiumAlbumId
+        }
+    })
+};
+
+export {
+    addNewSong,
+    getAllSongFromAlbum,
+    updatePremiumSong,
+    deletePremiumSong,
+};
diff --git a/src/services/subscription-service.ts b/src/services/subscription-service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f7c579351dd124dd55ffabfd3e7dc4003f53e116
--- /dev/null
+++ b/src/services/subscription-service.ts
@@ -0,0 +1,37 @@
+import {soapClient} from "../clients/soap-client";
+import SubscriptionStatus from "../type/subscription-status";
+import {validate} from "../validation/validation";
+import {searchSubscriptionSchema, updateSubscriptionSchema} from "../validation/subscription-validation";
+
+const updateSubscription = async (
+    url : string,
+    ws_url : string,
+    data : {
+        userId: number;
+        albumId: number;
+        status: string;
+    },
+): Promise<object> => {
+    validate(updateSubscriptionSchema, data)
+    return soapClient(url, ws_url, "updateSubscription", data)
+};
+
+const searchSubscription = async (
+    url : string,
+    ws_url : string,
+    data : {
+        status: SubscriptionStatus;
+        searchInput?: string;
+        orderBy?: string;
+        size?: number;
+        page?: number;
+    },
+): Promise<object> => {
+    validate(searchSubscriptionSchema, data)
+    return soapClient(url, ws_url, "searchSubscription", data)
+}
+
+export {
+    updateSubscription,
+    searchSubscription,
+}
diff --git a/src/type/subscription-status.ts b/src/type/subscription-status.ts
new file mode 100644
index 0000000000000000000000000000000000000000..44eb1178182834e55ab319f10714eb834aee480f
--- /dev/null
+++ b/src/type/subscription-status.ts
@@ -0,0 +1,8 @@
+enum SubscriptionStatus {
+    PENDING = 'PENDING',
+    ACTIVE = 'ACTIVE',
+    CANCELLED = 'CANCELLED',
+    EXPIRED = 'EXPIRED',
+}
+
+export default SubscriptionStatus;
\ No newline at end of file
diff --git a/src/utils/file-processing.ts b/src/utils/file-processing.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a459c42d2d2f86900ebcb0929fdd05220fad7624
--- /dev/null
+++ b/src/utils/file-processing.ts
@@ -0,0 +1,13 @@
+import fs from "fs";
+
+const saveFile = async (file : Express.Multer.File, path : string) => {
+  // save to './storage' folder
+  fs.writeFile("./storage/" + path, file.buffer, (err) => {
+    if (err) {
+      console.log(err);
+      throw err;
+    }
+  });
+}
+
+export default saveFile;
\ No newline at end of file
diff --git a/src/utils/multer.ts b/src/utils/multer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..897cc537addedef78729138a493744817b47f73c
--- /dev/null
+++ b/src/utils/multer.ts
@@ -0,0 +1,38 @@
+import multer from "multer";
+
+// https://github.com/expressjs/multer
+const storage = multer.memoryStorage();
+
+const uploadCover = multer({
+  storage: storage,
+    limits: {
+    fileSize: 1024 * 1024 * 10 // 10MB
+  },
+  fileFilter: function (req, file, cb) {
+    if (file.mimetype === "image/png" || file.mimetype === "image/jpeg" || file.mimetype === "image/jpg") {
+      cb(null, true)
+    } else {
+        cb(null, false)
+      return cb(new Error('Only .png, .jpg and .jpeg format allowed!'))
+    }
+  }
+});
+
+const uploadSong = multer({
+  storage: storage,
+    limits: {
+    fileSize: 1024 * 1024 * 10 // 10MB
+  },
+    fileFilter: function (req, file, cb) {
+        if (file.mimetype === "audio/mpeg") {
+        cb(null, true)
+        } else {
+        return cb(new Error('Only .mp3 format allowed!'))
+        }
+    }
+});
+
+export {
+    uploadCover,
+    uploadSong,
+}
\ No newline at end of file
diff --git a/src/utils/password.ts b/src/utils/password.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6eda1b60ac08cad019de1794028f24a1f224987a
--- /dev/null
+++ b/src/utils/password.ts
@@ -0,0 +1,23 @@
+import * as argon2 from "argon2";
+import { ErrorType, StandardError } from "../errors/standard-error";
+
+const hashPassword = async (plainPassword: string): Promise<string> => {
+  try {
+    return await argon2.hash(plainPassword);
+  } catch (error) {
+    throw new StandardError(ErrorType.PASSWORD_HASH_FAILURE);
+  }
+};
+
+const isPasswordValid = async (
+  hashedPassword: string,
+  plainPassword: string,
+): Promise<boolean> => {
+  try {
+    return argon2.verify(hashedPassword, plainPassword);
+  } catch (error) {
+    throw new StandardError(ErrorType.PASSWORD_VERIFICATION_FAILURE);
+  }
+};
+
+export { hashPassword, isPasswordValid };
diff --git a/src/utils/response.ts b/src/utils/response.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e190bb9b74905d488801f39d798b06e7f607f8b2
--- /dev/null
+++ b/src/utils/response.ts
@@ -0,0 +1,24 @@
+import { Response } from "express";
+import { StatusCodes } from "http-status-codes";
+import { StandardError } from "../errors/standard-error";
+
+const generateResponse = (
+  res: Response,
+  status: StatusCodes,
+  data?: any,
+): void => {
+  if (!data) {
+    res.status(status).json();
+    return;
+  }
+  res.status(status).json(data);
+};
+
+const generateStandardErrorResponse = (
+  res: Response,
+  e: StandardError,
+): void => {
+  res.status(e.status).json(e);
+};
+
+export { generateResponse, generateStandardErrorResponse };
diff --git a/src/utils/token.ts b/src/utils/token.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a6162c1a7e5bd0550077278f84329b0b6d5dee7e
--- /dev/null
+++ b/src/utils/token.ts
@@ -0,0 +1,46 @@
+import jwt from "jsonwebtoken";
+import { ErrorType, StandardError } from "../errors/standard-error";
+import * as crypto from "crypto";
+
+const generateFingerprint = async (numberOfBytes: number): Promise<string> => {
+  const randomBytes: Buffer = crypto.randomBytes(numberOfBytes);
+  return randomBytes.toString("hex");
+};
+
+const hashFingerprint = async (fgp: string): Promise<string> => {
+  const hash: crypto.Hash = crypto.createHash("sha256");
+  hash.update(fgp, "utf8");
+  return hash.digest("hex");
+};
+
+const generateAccessTokenAndFingerprint = async (data: {
+  userId: number,
+  username: string,
+}) => {
+  try {
+    const fingerprint: string = await generateFingerprint(64);
+    const hashedFingerprint: string = await hashFingerprint(fingerprint);
+
+    return {
+      accessToken: jwt.sign(
+        {
+          uid: data.userId,
+          usr: data.username,
+          fgp: hashedFingerprint, // User hashed fingerprint
+        },
+        process.env.JWT_SHARED_SECRET as string,
+        {
+          algorithm: "HS256", // Only use HS256 to generate JWTs
+          expiresIn: "60m", // The token expires 60 minutes
+          notBefore: "0ms", // The token is valid right away
+          issuer: "Tonality REST Service",
+        },
+      ),
+      fingerprint: fingerprint,
+    };
+  } catch (error) {
+    throw new StandardError(ErrorType.ACCESS_TOKEN_GENERATION_FAILURE);
+  }
+};
+
+export { generateAccessTokenAndFingerprint, hashFingerprint };
diff --git a/src/validation/auth-validation.ts b/src/validation/auth-validation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..46900552460aa1eb670c67392c04d9566eadb3ed
--- /dev/null
+++ b/src/validation/auth-validation.ts
@@ -0,0 +1,16 @@
+import { z } from "zod";
+
+const signupSchema = z.object({
+    username: z.string().min(1).max(255),
+    password: z.string().min(1).max(255),
+});
+
+const loginSchema = z.object({
+    username: z.string().min(1).max(255),
+    password: z.string().min(1).max(255),
+});
+
+export {
+    signupSchema,
+    loginSchema,
+}
\ No newline at end of file
diff --git a/src/validation/premium-album-validation.ts b/src/validation/premium-album-validation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..173ac5d8bfb77908e81f1b41643224c0549f9474
--- /dev/null
+++ b/src/validation/premium-album-validation.ts
@@ -0,0 +1,41 @@
+import { z } from "zod";
+
+const createPremiumAlbumSchema = z.object({
+    albumName: z.string().min(1).max(255),
+    releaseDate: z.coerce.date(),
+    genre: z.string().min(1).max(255),
+    artist: z.string().min(1).max(255),
+});
+
+const searchPremiumAlbumSchema = z.object({
+    size: z.optional(z.number().int().min(10).max(100)),
+    page: z.optional(z.number().int().min(1)),
+    searchQuery: z.optional(z.string().min(1).max(255)),
+});
+
+const searchPremiumAlbumOwnSchema = z.object({
+    size: z.optional(z.number().int().min(10).max(100)),
+    page: z.optional(z.number().int().min(1)),
+    searchQuery: z.optional(z.string().min(1).max(255)),
+    premiumAlbumIds : z.array(z.number().int().min(1)),
+});
+
+const updatePremiumAlbumSchema = z.object({
+    premiumAlbumId: z.number().int().min(1),
+    albumName: z.optional(z.string().min(1).max(255)),
+    releaseDate: z.optional(z.coerce.date()),
+    genre: z.optional(z.string().min(1).max(255)),
+    artist: z.optional(z.string().min(1).max(255)),
+});
+
+const deletePremiumAlbumSchema = z.object({
+    premiumAlbumId: z.number().int().min(1),
+});
+
+export {
+    createPremiumAlbumSchema,
+    searchPremiumAlbumSchema,
+    searchPremiumAlbumOwnSchema,
+    updatePremiumAlbumSchema,
+    deletePremiumAlbumSchema,
+}
diff --git a/src/validation/premium-song-validation.ts b/src/validation/premium-song-validation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..53cf28a424a9148fc868404d4f9c0692ff4e2c5e
--- /dev/null
+++ b/src/validation/premium-song-validation.ts
@@ -0,0 +1,36 @@
+import { z } from "zod";
+
+const addNewSongSchema = z.object({
+    premiumAlbumId: z.number().int().min(1),
+    title: z.string().min(1).max(255),
+    artist: z.string().min(1).max(255),
+    discNumber: z.optional(z.number().int().min(1)).nullable(),
+    songNumber: z.optional(z.number().int().min(1)),
+    duration: z.optional(z.number().int().min(1)),
+});
+
+const getAllSongFromAlbumSchema = z.object({
+    premiumAlbumId: z.number().int().min(1),
+});
+
+const updatePremiumSongSchema = z.object({
+    premiumAlbumId: z.number().int().min(1),
+    premiumSongId: z.number().int().min(1),
+    title: z.optional(z.string().min(1).max(255)),
+    artist: z.optional(z.string().min(1).max(255)),
+    discNumber: z.optional(z.number().int().min(1)).nullable(),
+    songNumber: z.optional(z.number().int().min(1)),
+    duration: z.optional(z.number().int().min(1)),
+});
+
+const deletePremiumSongSchema = z.object({
+    premiumAlbumId: z.number().int().min(1),
+    songId: z.number().int().min(1),
+});
+
+export {
+    addNewSongSchema,
+    getAllSongFromAlbumSchema,
+    updatePremiumSongSchema,
+    deletePremiumSongSchema,
+}
\ No newline at end of file
diff --git a/src/validation/subscription-validation.ts b/src/validation/subscription-validation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..20302f5ad82f573e11489b0c444f915803ecc461
--- /dev/null
+++ b/src/validation/subscription-validation.ts
@@ -0,0 +1,20 @@
+import { z } from 'zod';
+
+const updateSubscriptionSchema = z.object({
+    userId: z.number().int().min(1),
+    albumId: z.number().int().min(1),
+    status: z.string().min(1).max(255),
+});
+
+const searchSubscriptionSchema = z.object({
+    status: z.optional(z.string().min(1).max(255)),
+    searchInput: z.optional(z.string()),
+    orderBy: z.optional(z.string()),
+    page: z.number().int().min(1),
+    size: z.number().int().min(1),
+});
+
+export {
+    updateSubscriptionSchema,
+    searchSubscriptionSchema,
+}
\ No newline at end of file
diff --git a/src/validation/validation.ts b/src/validation/validation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1c8d60e9aca6cae4ad226046da3e2e2e82a159b8
--- /dev/null
+++ b/src/validation/validation.ts
@@ -0,0 +1,10 @@
+import {ZodObject} from "zod";
+import {ErrorType, StandardError} from "../errors/standard-error";
+
+export const validate = (schema : ZodObject<any>, data : any) => {
+    const {error} :any = schema.safeParse(data);
+    if (error) {
+        // console.log(error);
+        throw new StandardError(ErrorType.INPUT_DATA_NOT_VALID);
+    }
+}
diff --git a/test/auth.test.ts b/test/auth.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..58d65f40361a3b033ccfdd33ee0f08a18a9459f4
--- /dev/null
+++ b/test/auth.test.ts
@@ -0,0 +1,50 @@
+import supertest from "supertest";
+import { app } from "../src/cores/app";
+import { deleteAllUserTest } from "./test-util";
+
+describe("POST /api/signup", () => {
+    afterEach(async () => {
+        await deleteAllUserTest();
+    });
+
+    it("Should be able to create a new user.", async () => {
+        const result = await supertest(app)
+        .post("/api/signup")
+        .send({
+            username: "test",
+            password: "test",
+        });
+
+        expect(result.status).toEqual(200);
+        expect(result.body.username).toBe("test");
+    });
+});
+
+describe("POST /api/login", () => {
+    beforeEach(async () => {
+        await deleteAllUserTest();
+        await supertest(app)
+        .post("/api/signup")
+        .send({
+            username: "test",
+            password: "test",
+        });
+    });
+
+    afterEach(async () => {
+        await deleteAllUserTest();
+    });
+
+    it("Should be able to login.", async () => {
+        const result = await supertest(app)
+        .post("/api/login")
+        .send({
+            username: "test",
+            password: "test",
+        });
+
+        expect(result.status).toEqual(200);
+        expect(result.body.accessToken).toBeDefined();
+        expect(result.header["set-cookie"][0]).toBeDefined();
+    });
+});
\ No newline at end of file
diff --git a/test/premium-album.test.ts b/test/premium-album.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dc6afe16d554b18201bda30c8d033261f58bb9bc
--- /dev/null
+++ b/test/premium-album.test.ts
@@ -0,0 +1,102 @@
+import supertest from "supertest";
+import { app } from "../src/cores/app";
+import {
+    addManyPremiumAlbumTest,
+    deleteAllPremiumAlbumTest,
+    deleteUsersTest,
+} from "./test-util";
+
+const signup = async () => {
+await supertest(app)
+        .post("/api/signup")
+        .send({
+            username: "test",
+            password: "test",
+        });
+}
+
+describe("POST /api/premium-album", () => {
+    beforeEach(async () => {
+    await signup();
+  });
+
+  afterEach(async () => {
+    await deleteAllPremiumAlbumTest();
+    await deleteUsersTest();
+  });
+
+  it("Should be able to create a new premium album.", async () => {
+    // login first
+      const loginResult = await supertest(app)
+          .post("/api/login")
+          .send({
+              username: "test",
+              password: "test",
+          });
+
+    const token = loginResult.body.accessToken;
+    const fingerprintCookie = loginResult.header["set-cookie"][0];
+
+    // create new premium album
+    const result = await supertest(app)
+        .post("/api/premium-album")
+        .set("Authorization", `Bearer ${token}`)
+        .set("Cookie", fingerprintCookie)
+        .send({
+            albumName: "test",
+            releaseDate: new Date("2021-01-01"),
+            genre: "test",
+            artist: "test",
+            coverFilename: "test",
+        });
+
+
+    expect(result.status).toEqual(200);
+    expect(result.body.albumName).toBe("test");
+    expect(result.body.artist).toBe("test");
+    expect(result.body.releaseDate.toString()).toBe(
+      "2021-01-01T00:00:00.000Z",
+    );
+    expect(result.body.genre).toBe("test");
+    expect(result.body.coverFilename).toBe("test");
+  });
+});
+
+describe("GET /api/premium-albums", () => {
+  beforeEach(async () => {
+    await signup();
+    await deleteAllPremiumAlbumTest();
+    await addManyPremiumAlbumTest();
+  });
+
+  afterEach(async () => {
+    await deleteAllPremiumAlbumTest();
+    await deleteUsersTest();
+  });
+
+  it("Should be able to get all premium albums.", async () => {
+    // login first
+    const loginResult = await supertest(app)
+      .post("/api/login")
+      .send({
+        username: "test",
+        password: "test",
+      });
+
+    const token = loginResult.body.accessToken;
+    const fingerprintCookie = loginResult.header["set-cookie"][0];
+
+    const result = await supertest(app)
+        .get("/api/premium-albums")
+        .set("Authorization", `Bearer ${token}`)
+        .set("Cookie", fingerprintCookie)
+        .query({
+      size: 10,
+      page: 1,
+      searchQuery: "",
+    });
+
+    expect(result.status).toEqual(200);
+    expect(result.body.paging.totalAlbums).toBe(20);
+  });
+});
diff --git a/test/test-util.ts b/test/test-util.ts
new file mode 100644
index 0000000000000000000000000000000000000000..10cc73f2fe66debc11541f7519e7760ea3dbbf38
--- /dev/null
+++ b/test/test-util.ts
@@ -0,0 +1,31 @@
+import prismaClient from "../src/cores/db";
+
+export const deleteAllUserTest = async () => {
+    await prismaClient.user.deleteMany();
+}
+
+export const deleteAllPremiumAlbumTest = async () => {
+    await prismaClient.premiumAlbum.deleteMany();
+}
+
+export const addManyPremiumAlbumTest = async () => {
+    for (let i = 0; i < 20; i++) {
+        await prismaClient.premiumAlbum.create({
+            data: {
+                albumName: `test ${i}`,
+                artist: "test",
+                releaseDate: new Date(`2021-01-0${i+1}`),
+                genre: "test",
+                coverFilename: "test",
+            }
+        });
+    }
+};
+
+export const deleteUsersTest = async () => {
+    await prismaClient.user.delete({
+        where: {
+            username: "test",
+        }
+    })
+}