diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 287d51d..ec40111 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -3,7 +3,7 @@ - + @@ -11,6 +11,15 @@ + + + + + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml index bd8b2bb..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/frontend/openapitools.json b/frontend/openapitools.json new file mode 100644 index 0000000..936223a --- /dev/null +++ b/frontend/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.24.0" + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 252cbc6..7617939 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,13 +16,16 @@ "@angular/material": "^22.0.4", "@angular/platform-browser": "^22.0.0", "@angular/router": "^22.0.0", + "ngx-cookie-service": "^22.0.0", "rxjs": "~7.8.0", - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "uuid": "^14.0.1" }, "devDependencies": { "@angular/build": "^22.0.6", "@angular/cli": "^22.0.6", "@angular/compiler-cli": "^22.0.0", + "@openapitools/openapi-generator-cli": "^2.40.0", "jsdom": "^28.0.0", "prettier": "^3.8.1", "typescript": "~6.0.2", @@ -1040,6 +1043,17 @@ "node": ">=6.9.0" } }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -2206,6 +2220,16 @@ "win32" ] }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", @@ -2675,6 +2699,90 @@ "node": ">= 10" } }, + "node_modules/@nestjs/axios": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-4.0.1.tgz", + "integrity": "sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "axios": "^1.3.1", + "rxjs": "^7.0.0" + } + }, + "node_modules/@nestjs/common": { + "version": "11.1.28", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.28.tgz", + "integrity": "sha512-bRImsxibie+AM7xjdwcrm/gr5YeacI65kSBNzTufa1Ib5iwziaY/lqMtRh9THq6pbV4e1HP9aI2ZxGUumnmaoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "21.3.4", + "iterare": "1.2.1", + "load-esm": "1.0.3", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "class-transformer": ">=0.4.1", + "class-validator": ">=0.13.2", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/core": { + "version": "11.1.28", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.28.tgz", + "integrity": "sha512-06m63xIRj8+l8uOeh/8LnYupGubkyu4f+bPKIadaSui6vK9KpXgoz7HveT1yOVLcEt0M0oCOEW5EuEXZkEmBBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "8.4.2", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "engines": { + "node": ">= 20" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/microservices": "^11.0.0", + "@nestjs/platform-express": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } + } + }, "node_modules/@npmcli/agent": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.2.tgz", @@ -2907,6 +3015,319 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/@nuxtjs/opencollective": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", + "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "consola": "^2.15.0", + "node-fetch": "^2.6.1" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@openapitools/openapi-generator-cli": { + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.40.0.tgz", + "integrity": "sha512-Ws+2vVL11aZjbhlA+UIVHdiCcoO58rG9LaGPsSLQa786l+Uyj+2FgYwhMnNiVL/9Dtdr0+wzNu2AJWKpfR24Gg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@inquirer/select": "1.3.3", + "@nestjs/axios": "4.0.1", + "@nestjs/common": "11.1.28", + "@nestjs/core": "11.1.28", + "@nuxtjs/opencollective": "0.3.2", + "axios": "^1.18.1", + "chalk": "4.1.2", + "commander": "8.3.0", + "compare-versions": "6.1.1", + "concurrently": "10.0.3", + "console.table": "0.10.0", + "fs-extra": "11.3.6", + "glob": "13.0.6", + "proxy-agent": "8.0.2", + "reflect-metadata": "0.2.2", + "rxjs": "7.8.2", + "tslib": "2.8.1" + }, + "bin": { + "openapi-generator-cli": "main.js" + }, + "engines": { + "node": ">=22.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/openapi_generator" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/@inquirer/core": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-6.0.0.tgz", + "integrity": "sha512-fKi63Khkisgda3ohnskNf5uZJj+zXOaBvOllHsOkdsXRA/ubQLJQrZchFFi57NKbZzkTunXiBMdvWOv71alonw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/type": "^1.1.6", + "@types/mute-stream": "^0.0.4", + "@types/node": "^20.10.7", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "cli-spinners": "^2.9.2", + "cli-width": "^4.1.0", + "figures": "^3.2.0", + "mute-stream": "^1.0.0", + "run-async": "^3.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/@inquirer/select": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-1.3.3.tgz", + "integrity": "sha512-RzlRISXWqIKEf83FDC9ZtJ3JvuK1l7aGpretf41BCWYrvla2wU8W8MTRNMiPrPJ+1SIqrRC1nZdZ60hD9hRXLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^6.0.0", + "@inquirer/type": "^1.1.6", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "figures": "^3.2.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.6", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", @@ -3736,6 +4157,31 @@ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", @@ -3785,6 +4231,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitejs/plugin-basic-ssl": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz", @@ -4072,6 +4545,66 @@ "node": ">=12" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -4095,6 +4628,16 @@ "node": ">=6.0.0" } }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/beasties": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.4.2.tgz", @@ -4483,6 +5026,112 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.3.tgz", + "integrity": "sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.4", + "supports-color": "10.2.2", + "tree-kill": "1.2.2", + "yargs": "18.0.0" + }, + "bin": { + "conc": "dist/bin/index.js", + "concurrently": "dist/bin/index.js" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/console.table": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/console.table/-/console.table-0.10.0.tgz", + "integrity": "sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "easy-table": "1.1.0" + }, + "engines": { + "node": "> 0.10" + } + }, "node_modules/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -4637,6 +5286,16 @@ "node": "20 || >=22" } }, + "node_modules/data-uri-to-buffer": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-8.0.0.tgz", + "integrity": "sha512-6UHfyCux51b8PTGDgveqtz1tvphBku5DrMKKJbFAZAJOI2zsjDpDoYE1+QGj7FOMS4BdTFNJsJiR3zEB0xH0yQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, "node_modules/data-urls": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", @@ -4676,6 +5335,48 @@ "dev": true, "license": "MIT" }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/degenerator": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-7.0.1.tgz", + "integrity": "sha512-ABErK0IefDSyHjlPH7WUEenIAX2rPPnrDcDM+TS3z3+zu9TfyKKi07BQM+8rmxpdE2y1v5fjjdoAS/x4D2U60w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "quickjs-wasi": "^2.2.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4771,6 +5472,16 @@ "node": ">= 0.4" } }, + "node_modules/easy-table": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz", + "integrity": "sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "wcwidth": ">=1.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4878,6 +5589,22 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", @@ -4937,6 +5664,73 @@ "dev": true, "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -4947,6 +5741,16 @@ "@types/estree": "^1.0.0" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -5074,6 +5878,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-string-truncated-width": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", @@ -5136,6 +5947,41 @@ } } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -5158,6 +6004,67 @@ "url": "https://opencollective.com/express" } }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5178,6 +6085,21 @@ "node": ">= 0.8" } }, + "node_modules/fs-extra": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fs-minipass": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", @@ -5288,6 +6210,21 @@ "node": ">= 0.4" } }, + "node_modules/get-uri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-8.0.1.tgz", + "integrity": "sha512-/5N/P4Lrh0p/mDwlDRi7Y1+P2o/OyzZI3l6Iz1Ov6XXwwm1y3RlZLuo3gVgML99djrEDtV980bBxSuOeHLk8ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.3.1", + "data-uri-to-buffer": "8.0.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -5333,6 +6270,16 @@ "dev": true, "license": "ISC" }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -5346,6 +6293,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -5521,6 +6484,27 @@ "url": "https://opencollective.com/express" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore-walk": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", @@ -5666,6 +6650,16 @@ "dev": true, "license": "ISC" }, + "node_modules/iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=6" + } + }, "node_modules/jose": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", @@ -5805,6 +6799,19 @@ "dev": true, "license": "MIT" }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -5861,6 +6868,26 @@ "@lmdb/lmdb-win32-x64": "3.5.4" } }, + "node_modules/load-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz", + "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "engines": { + "node": ">=13.2.0" + } + }, "node_modules/log-symbols": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", @@ -6314,6 +7341,29 @@ "node": ">= 0.6" } }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/ngx-cookie-service": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-22.0.0.tgz", + "integrity": "sha512-7V8am8MqTr7R1xCePfo3w+AfbunNQl+vaWPAeLtIruhsAqBXp0uunh4mYv+ikrYrdPg9pqBjckCX3ubNv8HX7w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + }, + "peerDependencies": { + "@angular/common": "^22.0.0", + "@angular/core": "^22.0.0" + } + }, "node_modules/node-addon-api": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", @@ -6322,6 +7372,52 @@ "license": "MIT", "optional": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-gyp": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", @@ -6660,6 +7756,88 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pac-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-9.1.0.tgz", + "integrity": "sha512-1aU+1mpj3DrQPfo3gh+3Gap3G5x+axnMx1P/y0ZF2ch7kb2meyOCAH8K2k9d27ROsTE7TnAerzxqF9aon2jqnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "get-uri": "8.0.1", + "http-proxy-agent": "9.1.0", + "https-proxy-agent": "9.1.0", + "pac-resolver": "9.0.1", + "quickjs-wasi": "^2.2.0", + "socks-proxy-agent": "10.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", + "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/pac-proxy-agent/node_modules/socks-proxy-agent": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-10.1.0.tgz", + "integrity": "sha512-WlMj/67cEJ6MDI1OcsnjuYKDNDoyPCCYZ249kuuXPiMDw9F8PXkVaQ7YWu3siTydfQ/4BEZcvGzu+aYvz7dDCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/pac-resolver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-9.0.1.tgz", + "integrity": "sha512-lJbS008tmkj08VhoM8Hzuv/VE5tK9MS0OIQ/7+s0lIF+BYhiQWFYzkSpML7lXs9iBu2jfmzBTLzhe9n6BX+dYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "7.0.1", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "quickjs-wasi": "^2.2.0" + } + }, "node_modules/pacote": { "version": "21.5.1", "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.1.tgz", @@ -6968,6 +8146,109 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-8.0.2.tgz", + "integrity": "sha512-idLLRewuemWd7GH/BDJzGiB0dWGfT2SQs3jy6NtZtGWU9uPTTSdeC1/cdbqLwgzhfv027daGFuXX426e2Eg20A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "http-proxy-agent": "9.1.0", + "https-proxy-agent": "9.1.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "9.1.0", + "proxy-from-env": "^2.0.0", + "socks-proxy-agent": "10.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/proxy-agent-negotiate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", + "integrity": "sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "kerberos": "^2.0.0" + }, + "peerDependenciesMeta": { + "kerberos": { + "optional": true + } + } + }, + "node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", + "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-agent/node_modules/socks-proxy-agent": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-10.1.0.tgz", + "integrity": "sha512-WlMj/67cEJ6MDI1OcsnjuYKDNDoyPCCYZ249kuuXPiMDw9F8PXkVaQ7YWu3siTydfQ/4BEZcvGzu+aYvz7dDCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6995,6 +8276,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quickjs-wasi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-2.2.0.tgz", + "integrity": "sha512-zQxXmQMrEoD3S+jQdYsloq4qAuaxKFHZj6hHqOYGwB2iQZH+q9e/lf5zQPXCKOk0WJuAjzRFbO4KwHIp2D05Iw==", + "dev": true, + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", @@ -7142,6 +8430,16 @@ "node": ">= 18" } }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -7312,6 +8610,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", + "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", @@ -7643,6 +8954,36 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -7751,6 +9092,25 @@ "node": ">=0.6" } }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/tough-cookie": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", @@ -7777,6 +9137,16 @@ "node": ">=20" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -7798,6 +9168,19 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", @@ -7845,6 +9228,32 @@ "node": ">=14.17" } }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/undici": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", @@ -7855,6 +9264,23 @@ "node": ">=20.18.1" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -7896,6 +9322,19 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uuid": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/validate-npm-package-name": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", @@ -8592,6 +10031,17 @@ "node": ">=10.13.0" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/weak-lru-cache": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 165709d..4be0ddc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --host 0.0.0.0 --port 4200", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test" @@ -19,13 +19,16 @@ "@angular/material": "^22.0.4", "@angular/platform-browser": "^22.0.0", "@angular/router": "^22.0.0", + "ngx-cookie-service": "^22.0.0", "rxjs": "~7.8.0", - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "uuid": "^14.0.1" }, "devDependencies": { "@angular/build": "^22.0.6", "@angular/cli": "^22.0.6", "@angular/compiler-cli": "^22.0.0", + "@openapitools/openapi-generator-cli": "^2.40.0", "jsdom": "^28.0.0", "prettier": "^3.8.1", "typescript": "~6.0.2", diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index cb1270e..2b63ee8 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -2,10 +2,8 @@ import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/ import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; +import { CookieService } from 'ngx-cookie-service'; export const appConfig: ApplicationConfig = { - providers: [ - provideBrowserGlobalErrorListeners(), - provideRouter(routes) - ] + providers: [provideBrowserGlobalErrorListeners(), provideRouter(routes), CookieService] }; diff --git a/frontend/src/app/app.css b/frontend/src/app/app.css index e69de29..9633d15 100644 --- a/frontend/src/app/app.css +++ b/frontend/src/app/app.css @@ -0,0 +1,3 @@ +.example-spacer { + flex: 1 1 auto; +} diff --git a/frontend/src/app/app.html b/frontend/src/app/app.html index a1c4296..284d57c 100644 --- a/frontend/src/app/app.html +++ b/frontend/src/app/app.html @@ -1,344 +1,5 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title() }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'}, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - - + + Content 1 + Content 2 + Content 3 + diff --git a/frontend/src/app/app.ts b/frontend/src/app/app.ts index d7f2238..27b6d2d 100644 --- a/frontend/src/app/app.ts +++ b/frontend/src/app/app.ts @@ -1,16 +1,65 @@ -import { Component, signal } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import { Component, OnInit, signal } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; - - +import { MatTabsModule } from '@angular/material/tabs'; +import { HttpClient } from '@angular/common/http'; +import { CookieService } from 'ngx-cookie-service'; +import { v4 as uuidv4 } from 'uuid'; +import { YoutubeSearchResult } from './youtube-search-result/youtube-search-result'; +import { Session, SessionControllerService } from './core/api'; @Component({ + standalone: true, selector: 'app-root', - imports: [RouterOutlet, MatButtonModule, MatIconModule], - templateUrl: './app.html', + imports: [MatButtonModule, MatIconModule, MatTabsModule, YoutubeSearchResult], + template: ` + + + + + + + + + + + + + `, styleUrl: './app.css', }) -export class App { - protected readonly title = signal('frontend'); +export class App implements OnInit { + private _cookieService; + private _sessionService; + private userIdCookieName = 'userId'; + + + constructor( + private http: HttpClient, + private cookieService: CookieService, + private sessionControllerService: SessionControllerService, + ) { + this._cookieService = cookieService; + this._sessionService = sessionControllerService; + } + + ngOnInit(): void { + let name = this._cookieService.get(this.userIdCookieName); + console.log(name); + if (name == null || name == '') { + this._cookieService.set(this.userIdCookieName, uuidv4(), 365); + } + this._sessionService.getSession().subscribe({ + next: (session: Session) => { + // Here is your Session variable! + console.log(session); + console.log(session.sessionId); + }, + error: (error) => { + console.error('Request failed', error); + }, + }); + + + } } diff --git a/frontend/src/app/core/api/.gitignore b/frontend/src/app/core/api/.gitignore new file mode 100644 index 0000000..149b576 --- /dev/null +++ b/frontend/src/app/core/api/.gitignore @@ -0,0 +1,4 @@ +wwwroot/*.js +node_modules +typings +dist diff --git a/frontend/src/app/core/api/.openapi-generator-ignore b/frontend/src/app/core/api/.openapi-generator-ignore new file mode 100644 index 0000000..7484ee5 --- /dev/null +++ b/frontend/src/app/core/api/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/frontend/src/app/core/api/.openapi-generator/FILES b/frontend/src/app/core/api/.openapi-generator/FILES new file mode 100644 index 0000000..56532a0 --- /dev/null +++ b/frontend/src/app/core/api/.openapi-generator/FILES @@ -0,0 +1,31 @@ +.gitignore +README.md +api.base.service.ts +api.module.ts +api/api.ts +api/clientController.service.ts +api/healthController.service.ts +api/playerController.service.ts +api/sessionController.service.ts +api/userController.service.ts +api/youtubeController.service.ts +configuration.ts +encoder.ts +git_push.sh +index.ts +model/album.ts +model/artist.ts +model/client.ts +model/clientRegistrationRequestDto.ts +model/createUserRequest.ts +model/models.ts +model/musicRecord.ts +model/queryDto.ts +model/session.ts +model/sessionQueueElement.ts +model/thumbnails.ts +model/user.ts +param.ts +provide-api.ts +query.params.ts +variables.ts diff --git a/frontend/src/app/core/api/.openapi-generator/VERSION b/frontend/src/app/core/api/.openapi-generator/VERSION new file mode 100644 index 0000000..0783219 --- /dev/null +++ b/frontend/src/app/core/api/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.24.0 diff --git a/frontend/src/app/core/api/README.md b/frontend/src/app/core/api/README.md new file mode 100644 index 0000000..3a1964b --- /dev/null +++ b/frontend/src/app/core/api/README.md @@ -0,0 +1,185 @@ +# @ + +No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + +The version of the OpenAPI document: v0 + +## Building + +To install the required dependencies and to build the typescript sources run: + +```console +npm install +npm run build +``` + +## Publishing + +First build the package then run `npm publish dist` (don't forget to specify the `dist` folder!) + +## Consuming + +Navigate to the folder of your consuming project and run one of next commands. + +_published:_ + +```console +npm install @ --save +``` + +_without publishing (not recommended):_ + +```console +npm install PATH_TO_GENERATED_PACKAGE/dist.tgz --save +``` + +_It's important to take the tgz file, otherwise you'll get trouble with links on windows_ + +_using `npm link`:_ + +In PATH_TO_GENERATED_PACKAGE/dist: + +```console +npm link +``` + +In your project: + +```console +npm link +``` + +__Note for Windows users:__ The Angular CLI has troubles to use linked npm packages. +Please refer to this issue for a solution / workaround. +Published packages are not effected by this issue. + +### General usage + +In your Angular project: + +```typescript + +import { ApplicationConfig } from '@angular/core'; +import { provideHttpClient } from '@angular/common/http'; +import { provideApi } from ''; + +export const appConfig: ApplicationConfig = { + providers: [ + // ... + provideHttpClient(), + provideApi() + ], +}; +``` + +**NOTE** +If you're still using `AppModule` and haven't [migrated](https://angular.dev/reference/migrations/standalone) yet, you can still import an Angular module: +```typescript +import { ApiModule } from ''; +``` + +If different from the generated base path, during app bootstrap, you can provide the base path to your service. + +```typescript +import { ApplicationConfig } from '@angular/core'; +import { provideHttpClient } from '@angular/common/http'; +import { provideApi } from ''; + +export const appConfig: ApplicationConfig = { + providers: [ + // ... + provideHttpClient(), + provideApi('http://localhost:9999') + ], +}; +``` + +```typescript +// with a custom configuration +import { ApplicationConfig } from '@angular/core'; +import { provideHttpClient } from '@angular/common/http'; +import { provideApi } from ''; + +export const appConfig: ApplicationConfig = { + providers: [ + // ... + provideHttpClient(), + provideApi({ + withCredentials: true, + username: 'user', + password: 'password' + }) + ], +}; +``` + +```typescript +// with factory building a custom configuration +import { ApplicationConfig } from '@angular/core'; +import { provideHttpClient } from '@angular/common/http'; +import { provideApi, Configuration } from ''; + +export const appConfig: ApplicationConfig = { + providers: [ + // ... + provideHttpClient(), + { + provide: Configuration, + useFactory: (authService: AuthService) => new Configuration({ + basePath: 'http://localhost:9999', + withCredentials: true, + username: authService.getUsername(), + password: authService.getPassword(), + }), + deps: [AuthService], + multi: false + } + ], +}; +``` + +### Using multiple OpenAPI files / APIs + +In order to use multiple APIs generated from different OpenAPI files, +you can create an alias name when importing the modules +in order to avoid naming conflicts: + +```typescript +import { provideApi as provideUserApi } from 'my-user-api-path'; +import { provideApi as provideAdminApi } from 'my-admin-api-path'; +import { HttpClientModule } from '@angular/common/http'; +import { environment } from '../environments/environment'; + +export const appConfig: ApplicationConfig = { + providers: [ + // ... + provideHttpClient(), + provideUserApi(environment.basePath), + provideAdminApi(environment.basePath), + ], +}; +``` + +### Customizing path parameter encoding + +Without further customization, only [path-parameters][parameter-locations-url] of [style][style-values-url] 'simple' +and Dates for format 'date-time' are encoded correctly. + +Other styles (e.g. "matrix") are not that easy to encode +and thus are best delegated to other libraries (e.g.: [@honoluluhenk/http-param-expander]). + +To implement your own parameter encoding (or call another library), +pass an arrow-function or method-reference to the `encodeParam` property of the Configuration-object +(see [General Usage](#general-usage) above). + +Example value for use in your Configuration-Provider: + +```typescript +new Configuration({ + encodeParam: (param: Param) => myFancyParamEncoder(param), +}) +``` + +[parameter-locations-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-locations +[style-values-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-values +[@honoluluhenk/http-param-expander]: https://www.npmjs.com/package/@honoluluhenk/http-param-expander diff --git a/frontend/src/app/core/api/api.base.service.ts b/frontend/src/app/core/api/api.base.service.ts new file mode 100644 index 0000000..9267aac --- /dev/null +++ b/frontend/src/app/core/api/api.base.service.ts @@ -0,0 +1,96 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { HttpHeaders, HttpParams, HttpParameterCodec } from '@angular/common/http'; +import { CustomHttpParameterCodec } from './encoder'; +import { Configuration } from './configuration'; +import { OpenApiHttpParams, QueryParamStyle, concatHttpParamsObject} from './query.params'; + +export class BaseService { + protected basePath = 'http://localhost:8080'; + public defaultHeaders = new HttpHeaders(); + public configuration: Configuration; + public encoder: HttpParameterCodec; + + constructor(basePath?: string|string[], configuration?: Configuration) { + this.configuration = configuration || new Configuration(); + if (typeof this.configuration.basePath !== 'string') { + const firstBasePath = Array.isArray(basePath) ? basePath[0] : undefined; + if (firstBasePath != undefined) { + basePath = firstBasePath; + } + + if (typeof basePath !== 'string') { + basePath = this.basePath; + } + this.configuration.basePath = basePath; + } + this.encoder = this.configuration.encoder || new CustomHttpParameterCodec(); + } + + protected canConsumeForm(consumes: string[]): boolean { + return consumes.indexOf('multipart/form-data') !== -1; + } + + protected addToHttpParams(httpParams: OpenApiHttpParams, key: string, value: any | null | undefined, paramStyle: QueryParamStyle, explode: boolean): OpenApiHttpParams { + if (value === null || value === undefined) { + return httpParams; + } + + if (paramStyle === QueryParamStyle.DeepObject) { + if (typeof value !== 'object') { + throw Error(`An object must be provided for key ${key} as it is a deep object`); + } + + return Object.keys(value as Record).reduce( + (hp, k) => hp.append(`${key}[${k}]`, value[k]), + httpParams, + ); + } else if (paramStyle === QueryParamStyle.Json) { + return httpParams.append(key, JSON.stringify(value)); + } else { + // Form-style, SpaceDelimited or PipeDelimited + + if (Object(value) !== value) { + // If it is a primitive type, add its string representation + return httpParams.append(key, value.toString()); + } else if (value instanceof Date) { + return httpParams.append(key, value.toISOString()); + } else if (Array.isArray(value) || value instanceof Set) { + // Otherwise, if it's an array or set, add each element. + const array = Array.isArray(value) ? value : Array.from(value); + if (paramStyle === QueryParamStyle.Form) { + return httpParams.set(key, array, {explode: explode, delimiter: ','}); + } else if (paramStyle === QueryParamStyle.SpaceDelimited) { + return httpParams.set(key, array, {explode: explode, delimiter: ' '}); + } else { + // PipeDelimited + return httpParams.set(key, array, {explode: explode, delimiter: '|'}); + } + } else { + // Otherwise, if it's an object, add each field. + if (paramStyle === QueryParamStyle.Form) { + if (explode) { + Object.keys(value).forEach(k => { + httpParams = this.addToHttpParams(httpParams, k, value[k], paramStyle, explode); + }); + return httpParams; + } else { + return concatHttpParamsObject(httpParams, key, value, ','); + } + } else if (paramStyle === QueryParamStyle.SpaceDelimited) { + return concatHttpParamsObject(httpParams, key, value, ' '); + } else { + // PipeDelimited + return concatHttpParamsObject(httpParams, key, value, '|'); + } + } + } + } +} diff --git a/frontend/src/app/core/api/api.module.ts b/frontend/src/app/core/api/api.module.ts new file mode 100644 index 0000000..58d341f --- /dev/null +++ b/frontend/src/app/core/api/api.module.ts @@ -0,0 +1,30 @@ +import { NgModule, ModuleWithProviders, SkipSelf, Optional } from '@angular/core'; +import { Configuration } from './configuration'; +import { HttpClient } from '@angular/common/http'; + + +@NgModule({ + imports: [], + declarations: [], + exports: [], + providers: [] +}) +export class ApiModule { + public static forRoot(configurationFactory: () => Configuration): ModuleWithProviders { + return { + ngModule: ApiModule, + providers: [ { provide: Configuration, useFactory: configurationFactory } ] + }; + } + + constructor( @Optional() @SkipSelf() parentModule: ApiModule, + @Optional() http: HttpClient) { + if (parentModule) { + throw new Error('ApiModule is already loaded. Import in your base AppModule only.'); + } + if (!http) { + throw new Error('You need to import the HttpClientModule in your AppModule! \n' + + 'See also https://github.com/angular/angular/issues/20575'); + } + } +} diff --git a/frontend/src/app/core/api/api/api.ts b/frontend/src/app/core/api/api/api.ts new file mode 100644 index 0000000..a1df111 --- /dev/null +++ b/frontend/src/app/core/api/api/api.ts @@ -0,0 +1,13 @@ +export * from './clientController.service'; +import { ClientControllerService } from './clientController.service'; +export * from './healthController.service'; +import { HealthControllerService } from './healthController.service'; +export * from './playerController.service'; +import { PlayerControllerService } from './playerController.service'; +export * from './sessionController.service'; +import { SessionControllerService } from './sessionController.service'; +export * from './userController.service'; +import { UserControllerService } from './userController.service'; +export * from './youtubeController.service'; +import { YoutubeControllerService } from './youtubeController.service'; +export const APIS = [ClientControllerService, HealthControllerService, PlayerControllerService, SessionControllerService, UserControllerService, YoutubeControllerService]; diff --git a/frontend/src/app/core/api/api/clientController.service.ts b/frontend/src/app/core/api/api/clientController.service.ts new file mode 100644 index 0000000..58caf82 --- /dev/null +++ b/frontend/src/app/core/api/api/clientController.service.ts @@ -0,0 +1,105 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpContext + } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; + +// @ts-ignore +import { Client } from '../model/client'; +// @ts-ignore +import { ClientRegistrationRequestDto } from '../model/clientRegistrationRequestDto'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; +import { BaseService } from '../api.base.service'; + + + +@Injectable({ + providedIn: 'root' +}) +export class ClientControllerService extends BaseService { + + constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string|string[], @Optional() configuration?: Configuration) { + super(basePath, configuration); + } + + /** + * @endpoint post /api/client/register + * @param clientRegistrationRequestDto + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public registerClient(clientRegistrationRequestDto: ClientRegistrationRequestDto, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable; + public registerClient(clientRegistrationRequestDto: ClientRegistrationRequestDto, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public registerClient(clientRegistrationRequestDto: ClientRegistrationRequestDto, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public registerClient(clientRegistrationRequestDto: ClientRegistrationRequestDto, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable { + if (clientRegistrationRequestDto === null || clientRegistrationRequestDto === undefined) { + throw new Error('Required parameter clientRegistrationRequestDto was null or undefined when calling registerClient.'); + } + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + '*/*' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/client/register`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('post', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: clientRegistrationRequestDto, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + +} diff --git a/frontend/src/app/core/api/api/healthController.service.ts b/frontend/src/app/core/api/api/healthController.service.ts new file mode 100644 index 0000000..1a0a591 --- /dev/null +++ b/frontend/src/app/core/api/api/healthController.service.ts @@ -0,0 +1,87 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpContext + } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; + + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; +import { BaseService } from '../api.base.service'; + + + +@Injectable({ + providedIn: 'root' +}) +export class HealthControllerService extends BaseService { + + constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string|string[], @Optional() configuration?: Configuration) { + super(basePath, configuration); + } + + /** + * @endpoint get /health/ping + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public ping(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable; + public ping(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public ping(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public ping(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + '*/*' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/health/ping`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + +} diff --git a/frontend/src/app/core/api/api/playerController.service.ts b/frontend/src/app/core/api/api/playerController.service.ts new file mode 100644 index 0000000..66c1d1b --- /dev/null +++ b/frontend/src/app/core/api/api/playerController.service.ts @@ -0,0 +1,91 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpContext + } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; + + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; +import { BaseService } from '../api.base.service'; + + + +@Injectable({ + providedIn: 'root' +}) +export class PlayerControllerService extends BaseService { + + constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string|string[], @Optional() configuration?: Configuration) { + super(basePath, configuration); + } + + /** + * @endpoint post /api/player/play/{id} + * @param id + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public play(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable; + public play(id: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public play(id: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public play(id: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable { + if (id === null || id === undefined) { + throw new Error('Required parameter id was null or undefined when calling play.'); + } + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + '*/*' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/player/play/${this.configuration.encodeParam({name: "id", value: id, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('post', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + +} diff --git a/frontend/src/app/core/api/api/sessionController.service.ts b/frontend/src/app/core/api/api/sessionController.service.ts new file mode 100644 index 0000000..3f31c77 --- /dev/null +++ b/frontend/src/app/core/api/api/sessionController.service.ts @@ -0,0 +1,256 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpContext + } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; + +// @ts-ignore +import { Session } from '../model/session'; +// @ts-ignore +import { User } from '../model/user'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; +import { BaseService } from '../api.base.service'; + + + +@Injectable({ + providedIn: 'root' +}) +export class SessionControllerService extends BaseService { + + constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string|string[], @Optional() configuration?: Configuration) { + super(basePath, configuration); + } + + /** + * @endpoint post /session/music/add/{musicId} + * @param authorization + * @param musicId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public addMusic(authorization: string, musicId: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable; + public addMusic(authorization: string, musicId: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public addMusic(authorization: string, musicId: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public addMusic(authorization: string, musicId: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable { + if (authorization === null || authorization === undefined) { + throw new Error('Required parameter authorization was null or undefined when calling addMusic.'); + } + if (musicId === null || musicId === undefined) { + throw new Error('Required parameter musicId was null or undefined when calling addMusic.'); + } + + let localVarHeaders = this.defaultHeaders; + if (authorization !== undefined && authorization !== null) { + localVarHeaders = localVarHeaders.set('Authorization', String(authorization)); + } + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + '*/*' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/session/music/add/${this.configuration.encodeParam({name: "musicId", value: musicId, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('post', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * @endpoint get /session + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public getSession(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public getSession(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getSession(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getSession(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'application/json' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + console.log(responseType_) + + let localVarPath = `/session`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * @endpoint get /session/testing/next/user + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public nextUser(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable; + public nextUser(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public nextUser(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public nextUser(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + '*/*' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/session/testing/next/user`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * @endpoint get /session/testing/play/next + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public playNext(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable; + public playNext(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public playNext(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public playNext(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + '*/*' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/session/testing/play/next`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + +} diff --git a/frontend/src/app/core/api/api/userController.service.ts b/frontend/src/app/core/api/api/userController.service.ts new file mode 100644 index 0000000..98b11ea --- /dev/null +++ b/frontend/src/app/core/api/api/userController.service.ts @@ -0,0 +1,105 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpContext + } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; + +// @ts-ignore +import { CreateUserRequest } from '../model/createUserRequest'; +// @ts-ignore +import { User } from '../model/user'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; +import { BaseService } from '../api.base.service'; + + + +@Injectable({ + providedIn: 'root' +}) +export class UserControllerService extends BaseService { + + constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string|string[], @Optional() configuration?: Configuration) { + super(basePath, configuration); + } + + /** + * @endpoint post /api/user + * @param createUserRequest + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public createUser(createUserRequest: CreateUserRequest, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public createUser(createUserRequest: CreateUserRequest, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>>; + public createUser(createUserRequest: CreateUserRequest, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>>; + public createUser(createUserRequest: CreateUserRequest, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable { + if (createUserRequest === null || createUserRequest === undefined) { + throw new Error('Required parameter createUserRequest was null or undefined when calling createUser.'); + } + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + '*/*' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/user`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request>('post', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: createUserRequest, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + +} diff --git a/frontend/src/app/core/api/api/youtubeController.service.ts b/frontend/src/app/core/api/api/youtubeController.service.ts new file mode 100644 index 0000000..47dedce --- /dev/null +++ b/frontend/src/app/core/api/api/youtubeController.service.ts @@ -0,0 +1,105 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpContext + } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; + +// @ts-ignore +import { MusicRecord } from '../model/musicRecord'; +// @ts-ignore +import { QueryDto } from '../model/queryDto'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; +import { BaseService } from '../api.base.service'; + + + +@Injectable({ + providedIn: 'root' +}) +export class YoutubeControllerService extends BaseService { + + constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string|string[], @Optional() configuration?: Configuration) { + super(basePath, configuration); + } + + /** + * @endpoint post /api/youtube/query + * @param queryDto + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public createProduct(queryDto: QueryDto, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>; + public createProduct(queryDto: QueryDto, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>>; + public createProduct(queryDto: QueryDto, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable>>; + public createProduct(queryDto: QueryDto, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: '*/*', context?: HttpContext, transferCache?: boolean}): Observable { + if (queryDto === null || queryDto === undefined) { + throw new Error('Required parameter queryDto was null or undefined when calling createProduct.'); + } + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + '*/*' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/youtube/query`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request>('post', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: queryDto, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + +} diff --git a/frontend/src/app/core/api/configuration.ts b/frontend/src/app/core/api/configuration.ts new file mode 100644 index 0000000..eb876c1 --- /dev/null +++ b/frontend/src/app/core/api/configuration.ts @@ -0,0 +1,185 @@ +import { HttpHeaders, HttpParameterCodec } from '@angular/common/http'; +import { Param } from './param'; +import { OpenApiHttpParams } from './query.params'; + +export interface ConfigurationParameters { + /** + * @deprecated Since 5.0. Use credentials instead + */ + apiKeys?: {[ key: string ]: string}; + username?: string; + password?: string; + /** + * @deprecated Since 5.0. Use credentials instead + */ + accessToken?: string | (() => string); + basePath?: string; + withCredentials?: boolean; + /** + * Takes care of encoding query- and form-parameters. + */ + encoder?: HttpParameterCodec; + /** + * Override the default method for encoding path parameters in various + * styles. + *

+ * See {@link README.md} for more details + *

+ */ + encodeParam?: (param: Param) => string; + /** + * The keys are the names in the securitySchemes section of the OpenAPI + * document. They should map to the value used for authentication + * minus any standard prefixes such as 'Basic' or 'Bearer'. + */ + credentials?: {[ key: string ]: string | (() => string | undefined)}; +} + +export class Configuration { + /** + * @deprecated Since 5.0. Use credentials instead + */ + apiKeys?: {[ key: string ]: string}; + username?: string; + password?: string; + /** + * @deprecated Since 5.0. Use credentials instead + */ + accessToken?: string | (() => string); + basePath?: string; + withCredentials?: boolean; + /** + * Takes care of encoding query- and form-parameters. + */ + encoder?: HttpParameterCodec; + /** + * Encoding of various path parameter + * styles. + *

+ * See {@link README.md} for more details + *

+ */ + encodeParam: (param: Param) => string; + /** + * The keys are the names in the securitySchemes section of the OpenAPI + * document. They should map to the value used for authentication + * minus any standard prefixes such as 'Basic' or 'Bearer'. + */ + credentials: {[ key: string ]: string | (() => string | undefined)}; + +constructor({ accessToken, apiKeys, basePath, credentials, encodeParam, encoder, password, username, withCredentials }: ConfigurationParameters = {}) { + if (apiKeys) { + this.apiKeys = apiKeys; + } + if (username !== undefined) { + this.username = username; + } + if (password !== undefined) { + this.password = password; + } + if (accessToken !== undefined) { + this.accessToken = accessToken; + } + if (basePath !== undefined) { + this.basePath = basePath; + } + if (withCredentials !== undefined) { + this.withCredentials = withCredentials; + } + if (encoder) { + this.encoder = encoder; + } + this.encodeParam = encodeParam ?? (param => this.defaultEncodeParam(param)); + this.credentials = credentials ?? {}; + } + + /** + * Select the correct content-type to use for a request. + * Uses {@link Configuration#isJsonMime} to determine the correct content-type. + * If no content type is found return the first found type if the contentTypes is not empty + * @param contentTypes - the array of content types that are available for selection + * @returns the selected content-type or undefined if no selection could be made. + */ + public selectHeaderContentType (contentTypes: string[]): string | undefined { + if (contentTypes.length === 0) { + return undefined; + } + + const type = contentTypes.find((x: string) => this.isJsonMime(x)); + if (type === undefined) { + return contentTypes[0]; + } + return type; + } + + /** + * Select the correct accept content-type to use for a request. + * Uses {@link Configuration#isJsonMime} to determine the correct accept content-type. + * If no content type is found return the first found type if the contentTypes is not empty + * @param accepts - the array of content types that are available for selection. + * @returns the selected content-type or undefined if no selection could be made. + */ + public selectHeaderAccept(accepts: string[]): string | undefined { + if (accepts.length === 0) { + return undefined; + } + + const type = accepts.find((x: string) => this.isJsonMime(x)); + if (type === undefined) { + return accepts[0]; + } + return type; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = /^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$/i; + return mime !== null && jsonMime.test(mime); + } + + public lookupCredential(key: string): string | undefined { + const value = this.credentials[key]; + return typeof value === 'function' + ? value() + : value; + } + + public addCredentialToHeaders(credentialKey: string, headerName: string, headers: HttpHeaders, prefix?: string): HttpHeaders { + const value = this.lookupCredential(credentialKey); + return value + ? headers.set(headerName, (prefix ?? '') + value) + : headers; + } + + public addCredentialToQuery(credentialKey: string, paramName: string, query: OpenApiHttpParams): OpenApiHttpParams { + const value = this.lookupCredential(credentialKey); + return value + ? query.set(paramName, value) + : query; + } + + private defaultEncodeParam(param: Param): string { + // This implementation exists as fallback for missing configuration + // and for backwards compatibility to older typescript-angular generator versions. + // It only works for the 'simple' parameter style. + // Date-handling only works for the 'date-time' format. + // All other styles and Date-formats are probably handled incorrectly. + // + // But: if that's all you need (i.e.: the most common use-case): no need for customization! + + const value = param.dataFormat === 'date-time' && param.value instanceof Date + ? (param.value as Date).toISOString() + : param.value; + + return encodeURIComponent(String(value)); + } +} diff --git a/frontend/src/app/core/api/encoder.ts b/frontend/src/app/core/api/encoder.ts new file mode 100644 index 0000000..af45235 --- /dev/null +++ b/frontend/src/app/core/api/encoder.ts @@ -0,0 +1,35 @@ +import { HttpParameterCodec } from '@angular/common/http'; + +/** + * Custom HttpParameterCodec + * Workaround for https://github.com/angular/angular/issues/18261 + */ +export class CustomHttpParameterCodec implements HttpParameterCodec { + encodeKey(k: string): string { + return encodeURIComponent(k); + } + encodeValue(v: string): string { + return encodeURIComponent(v); + } + decodeKey(k: string): string { + return decodeURIComponent(k); + } + decodeValue(v: string): string { + return decodeURIComponent(v); + } +} + +export class IdentityHttpParameterCodec implements HttpParameterCodec { + encodeKey(k: string): string { + return k; + } + encodeValue(v: string): string { + return v; + } + decodeKey(k: string): string { + return k; + } + decodeValue(v: string): string { + return v; + } +} diff --git a/frontend/src/app/core/api/git_push.sh b/frontend/src/app/core/api/git_push.sh new file mode 100644 index 0000000..f53a75d --- /dev/null +++ b/frontend/src/app/core/api/git_push.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="github.com" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/frontend/src/app/core/api/index.ts b/frontend/src/app/core/api/index.ts new file mode 100644 index 0000000..02cb7d4 --- /dev/null +++ b/frontend/src/app/core/api/index.ts @@ -0,0 +1,7 @@ +export * from './api/api'; +export * from './model/models'; +export * from './variables'; +export * from './configuration'; +export * from './api.module'; +export * from './provide-api'; +export * from './param'; diff --git a/frontend/src/app/core/api/model/album.ts b/frontend/src/app/core/api/model/album.ts new file mode 100644 index 0000000..18131ec --- /dev/null +++ b/frontend/src/app/core/api/model/album.ts @@ -0,0 +1,16 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface Album { + id?: string; + name?: string; +} + diff --git a/frontend/src/app/core/api/model/artist.ts b/frontend/src/app/core/api/model/artist.ts new file mode 100644 index 0000000..4ae6979 --- /dev/null +++ b/frontend/src/app/core/api/model/artist.ts @@ -0,0 +1,16 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface Artist { + id?: string; + name?: string; +} + diff --git a/frontend/src/app/core/api/model/client.ts b/frontend/src/app/core/api/model/client.ts new file mode 100644 index 0000000..b2e947a --- /dev/null +++ b/frontend/src/app/core/api/model/client.ts @@ -0,0 +1,16 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface Client { + id?: string; + host?: string; +} + diff --git a/frontend/src/app/core/api/model/clientRegistrationRequestDto.ts b/frontend/src/app/core/api/model/clientRegistrationRequestDto.ts new file mode 100644 index 0000000..4626688 --- /dev/null +++ b/frontend/src/app/core/api/model/clientRegistrationRequestDto.ts @@ -0,0 +1,15 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ClientRegistrationRequestDto { + host?: string; +} + diff --git a/frontend/src/app/core/api/model/createUserRequest.ts b/frontend/src/app/core/api/model/createUserRequest.ts new file mode 100644 index 0000000..427a03a --- /dev/null +++ b/frontend/src/app/core/api/model/createUserRequest.ts @@ -0,0 +1,16 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface CreateUserRequest { + userId?: string; + nickName?: string; +} + diff --git a/frontend/src/app/core/api/model/models.ts b/frontend/src/app/core/api/model/models.ts new file mode 100644 index 0000000..6418a76 --- /dev/null +++ b/frontend/src/app/core/api/model/models.ts @@ -0,0 +1,11 @@ +export * from './album'; +export * from './artist'; +export * from './client'; +export * from './clientRegistrationRequestDto'; +export * from './createUserRequest'; +export * from './musicRecord'; +export * from './queryDto'; +export * from './session'; +export * from './sessionQueueElement'; +export * from './thumbnails'; +export * from './user'; diff --git a/frontend/src/app/core/api/model/musicRecord.ts b/frontend/src/app/core/api/model/musicRecord.ts new file mode 100644 index 0000000..f4cea89 --- /dev/null +++ b/frontend/src/app/core/api/model/musicRecord.ts @@ -0,0 +1,28 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { Artist } from './artist'; +import { Album } from './album'; +import { Thumbnails } from './thumbnails'; + + +export interface MusicRecord { + category?: string; + resultType?: string; + title?: string; + videoId?: string; + duration_seconds?: string; + duration?: string; + year?: string; + views?: string; + album?: Album; + artists?: Array; + thumbnails?: Array; +} + diff --git a/frontend/src/app/core/api/model/queryDto.ts b/frontend/src/app/core/api/model/queryDto.ts new file mode 100644 index 0000000..969c9a7 --- /dev/null +++ b/frontend/src/app/core/api/model/queryDto.ts @@ -0,0 +1,34 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface QueryDto { + queryString?: string; + filter?: QueryDto.FilterEnum; + limit?: number; +} +export namespace QueryDto { + export const FilterEnum = { + Albums: 'ALBUMS', + Artists: 'ARTISTS', + Playlists: 'PLAYLISTS', + CommunityPlaylists: 'COMMUNITY_PLAYLISTS', + FeaturedPlalists: 'FEATURED_PLALISTS', + Videos: 'VIDEOS', + Profiles: 'PROFILES', + Podcasts: 'PODCASTS', + Episodes: 'EPISODES', + Songs: 'SONGS', + None: 'NONE' + } as const; + export type FilterEnum = typeof FilterEnum[keyof typeof FilterEnum]; +} + + diff --git a/frontend/src/app/core/api/model/session.ts b/frontend/src/app/core/api/model/session.ts new file mode 100644 index 0000000..1eac634 --- /dev/null +++ b/frontend/src/app/core/api/model/session.ts @@ -0,0 +1,22 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { MusicRecord } from './musicRecord'; +import { User } from './user'; +import { SessionQueueElement } from './sessionQueueElement'; + + +export interface Session { + sessionId?: string; + currentUserIndex?: number; + currentMusicRecord?: MusicRecord; + sessionQueue?: Array; + users?: Array; +} + diff --git a/frontend/src/app/core/api/model/sessionQueueElement.ts b/frontend/src/app/core/api/model/sessionQueueElement.ts new file mode 100644 index 0000000..1e8e304 --- /dev/null +++ b/frontend/src/app/core/api/model/sessionQueueElement.ts @@ -0,0 +1,17 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { MusicRecord } from './musicRecord'; + + +export interface SessionQueueElement { + userName?: string; + musicRecord?: MusicRecord; +} + diff --git a/frontend/src/app/core/api/model/thumbnails.ts b/frontend/src/app/core/api/model/thumbnails.ts new file mode 100644 index 0000000..c42bb8e --- /dev/null +++ b/frontend/src/app/core/api/model/thumbnails.ts @@ -0,0 +1,17 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface Thumbnails { + url?: string; + width?: number; + height?: number; +} + diff --git a/frontend/src/app/core/api/model/user.ts b/frontend/src/app/core/api/model/user.ts new file mode 100644 index 0000000..720dabe --- /dev/null +++ b/frontend/src/app/core/api/model/user.ts @@ -0,0 +1,19 @@ +/** + * OpenAPI definition + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { MusicRecord } from './musicRecord'; + + +export interface User { + userId?: string; + nickName?: string; + popped?: Array; + musicQueue?: Array; +} + diff --git a/frontend/src/app/core/api/param.ts b/frontend/src/app/core/api/param.ts new file mode 100644 index 0000000..78a2d20 --- /dev/null +++ b/frontend/src/app/core/api/param.ts @@ -0,0 +1,69 @@ +/** + * Standard parameter styles defined by OpenAPI spec + */ +export type StandardParamStyle = + | 'matrix' + | 'label' + | 'form' + | 'simple' + | 'spaceDelimited' + | 'pipeDelimited' + | 'deepObject' + ; + +/** + * The OpenAPI standard {@link StandardParamStyle}s may be extended by custom styles by the user. + */ +export type ParamStyle = StandardParamStyle | string; + +/** + * Standard parameter locations defined by OpenAPI spec + */ +export type ParamLocation = 'query' | 'header' | 'path' | 'cookie'; + +/** + * Standard types as defined in OpenAPI Specification: Data Types + */ +export type StandardDataType = + | "integer" + | "number" + | "boolean" + | "string" + | "object" + | "array" + ; + +/** + * Standard {@link DataType}s plus your own types/classes. + */ +export type DataType = StandardDataType | string; + +/** + * Standard formats as defined in OpenAPI Specification: Data Types + */ +export type StandardDataFormat = + | "int32" + | "int64" + | "float" + | "double" + | "byte" + | "binary" + | "date" + | "date-time" + | "password" + ; + +export type DataFormat = StandardDataFormat | string; + +/** + * The parameter to encode. + */ +export interface Param { + name: string; + value: unknown; + in: ParamLocation; + style: ParamStyle, + explode: boolean; + dataType: DataType; + dataFormat: DataFormat | undefined; +} diff --git a/frontend/src/app/core/api/provide-api.ts b/frontend/src/app/core/api/provide-api.ts new file mode 100644 index 0000000..19c762a --- /dev/null +++ b/frontend/src/app/core/api/provide-api.ts @@ -0,0 +1,15 @@ +import { EnvironmentProviders, makeEnvironmentProviders } from "@angular/core"; +import { Configuration, ConfigurationParameters } from './configuration'; +import { BASE_PATH } from './variables'; + +// Returns the service class providers, to be used in the [ApplicationConfig](https://angular.dev/api/core/ApplicationConfig). +export function provideApi(configOrBasePath: string | ConfigurationParameters): EnvironmentProviders { + return makeEnvironmentProviders([ + typeof configOrBasePath === "string" + ? { provide: BASE_PATH, useValue: configOrBasePath } + : { + provide: Configuration, + useValue: new Configuration({ ...configOrBasePath }), + }, + ]); +} \ No newline at end of file diff --git a/frontend/src/app/core/api/query.params.ts b/frontend/src/app/core/api/query.params.ts new file mode 100644 index 0000000..4076c11 --- /dev/null +++ b/frontend/src/app/core/api/query.params.ts @@ -0,0 +1,160 @@ +import { HttpParams, HttpParameterCodec } from '@angular/common/http'; +import { CustomHttpParameterCodec, IdentityHttpParameterCodec } from './encoder'; + +export enum QueryParamStyle { + Json, + Form, + DeepObject, + SpaceDelimited, + PipeDelimited, +} + +export type Delimiter = "," | " " | "|" | "\t"; + +export interface ParamOptions { + /** When true, serialized as multiple repeated key=value pairs. When false, serialized as a single key with joined values using `delimiter`. */ + explode?: boolean; + /** Delimiter used when explode=false. The delimiter itself is inserted unencoded between encoded values. */ + delimiter?: Delimiter; +} + +interface ParamEntry { + values: string[]; + options: Required; +} + +export class OpenApiHttpParams { + private params: Map = new Map(); + private defaults: Required; + private encoder: HttpParameterCodec; + + /** + * @param encoder Parameter serializer + * @param defaults Global defaults used when a specific parameter has no explicit options. + * By OpenAPI default, explode is true for query params with style=form. + */ + constructor(encoder?: HttpParameterCodec, defaults?: { explode?: boolean; delimiter?: Delimiter }) { + this.encoder = encoder || new CustomHttpParameterCodec(); + this.defaults = { + explode: defaults?.explode ?? true, + delimiter: defaults?.delimiter ?? ",", + }; + } + + private resolveOptions(local?: ParamOptions): Required { + return { + explode: local?.explode ?? this.defaults.explode, + delimiter: local?.delimiter ?? this.defaults.delimiter, + }; + } + + /** + * Replace the parameter's values and (optionally) its options. + * Options are stored per-parameter (not global). + */ + set(key: string, values: string[] | string, options?: ParamOptions): this { + const arr = Array.isArray(values) ? values.slice() : [values]; + const opts = this.resolveOptions(options); + this.params.set(key, {values: arr, options: opts}); + return this; + } + + /** + * Append a single value to the parameter. If the parameter didn't exist it will be created + * and use resolved options (global defaults merged with any provided options). + */ + append(key: string, value: string, options?: ParamOptions): this { + const entry = this.params.get(key); + if (entry) { + // If new options provided, override the stored options for subsequent serialization + if (options) { + entry.options = this.resolveOptions({...entry.options, ...options}); + } + entry.values.push(value); + } else { + this.set(key, [value], options); + } + return this; + } + + /** + * Serialize to a query string according to per-parameter OpenAPI options. + * - If explode=true for that parameter → repeated key=value pairs (each value encoded). + * - If explode=false for that parameter → single key=value where values are individually encoded + * and joined using the configured delimiter. The delimiter character is inserted AS-IS + * (not percent-encoded). + */ + toString(): string { + const records = this.toRecord(); + const parts: string[] = []; + + for (const key in records) { + parts.push(`${key}=${records[key]}`); + } + + return parts.join("&"); + } + + /** + * Return parameters as a plain record. + * - If a parameter has exactly one value, returns that value directly. + * - If a parameter has multiple values, returns a readonly array of values. + */ + toRecord(): Record> { + const parts: Record> = {}; + + for (const [key, entry] of this.params.entries()) { + const encodedKey = this.encoder.encodeKey(key); + + if (entry.options.explode) { + parts[encodedKey] = entry.values.map((v) => this.encoder.encodeValue(v)); + } else { + const encodedValues = entry.values.map((v) => this.encoder.encodeValue(v)); + + // join with the delimiter *unencoded* + parts[encodedKey] = encodedValues.join(entry.options.delimiter); + } + } + + return parts; + } + + /** + * Return an Angular's HttpParams with an identity parameter codec as the parameters are already encoded. + */ + toHttpParams(): HttpParams { + const records = this.toRecord(); + + let httpParams = new HttpParams({encoder: new IdentityHttpParameterCodec()}); + + return httpParams.appendAll(records); + } +} + +export function concatHttpParamsObject(httpParams: OpenApiHttpParams, key: string, item: { + [index: string]: any +}, delimiter: Delimiter): OpenApiHttpParams { + let keyAndValues: string[] = []; + + for (const k in item) { + keyAndValues.push(k); + + const value = item[k]; + + if (Array.isArray(value)) { + keyAndValues.push(...value.map(convertToString)); + } else { + keyAndValues.push(convertToString(value)); + } + } + + return httpParams.set(key, keyAndValues, {explode: false, delimiter: delimiter}); +} + +function convertToString(value: any): string { + if (value instanceof Date) { + return value.toISOString(); + } else { + return value.toString(); + } +} diff --git a/frontend/src/app/core/api/variables.ts b/frontend/src/app/core/api/variables.ts new file mode 100644 index 0000000..6fe5854 --- /dev/null +++ b/frontend/src/app/core/api/variables.ts @@ -0,0 +1,9 @@ +import { InjectionToken } from '@angular/core'; + +export const BASE_PATH = new InjectionToken('basePath'); +export const COLLECTION_FORMATS = { + 'csv': ',', + 'tsv': ' ', + 'ssv': ' ', + 'pipes': '|' +} diff --git a/frontend/src/app/user.component/user.component.css b/frontend/src/app/user.component/user.component.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/user.component/user.component.html b/frontend/src/app/user.component/user.component.html new file mode 100644 index 0000000..eb53f78 --- /dev/null +++ b/frontend/src/app/user.component/user.component.html @@ -0,0 +1,27 @@ + + @for (link of fragments; track link) { + {{link | titlecase}} + } + +

List with icons

+ @for (link of fragments; track link) { + + folder + {{ link | titlecase}} +
+ {{link}} +
+
+ } +
diff --git a/frontend/src/app/user.component/user.component.spec.ts b/frontend/src/app/user.component/user.component.spec.ts new file mode 100644 index 0000000..909e6eb --- /dev/null +++ b/frontend/src/app/user.component/user.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserComponent } from './user.component'; + +describe('UserComponent', () => { + let component: UserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UserComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(UserComponent); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/user.component/user.component.ts b/frontend/src/app/user.component/user.component.ts new file mode 100644 index 0000000..9e23f7c --- /dev/null +++ b/frontend/src/app/user.component/user.component.ts @@ -0,0 +1,15 @@ +import { TitleCasePipe } from '@angular/common'; +import { Component, signal } from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from '@angular/material/list'; + +@Component({ + selector: 'app-user', + imports: [MatListModule, MatIconModule, TitleCasePipe], + templateUrl: './user.component.html', + styleUrl: './user.component.css', +}) +export class UserComponent { + fragments = ['inbox', 'outbox', 'drafts']; + activeLink = signal(null); +} diff --git a/frontend/src/app/youtube-search-result/youtube-search-result.css b/frontend/src/app/youtube-search-result/youtube-search-result.css new file mode 100644 index 0000000..3ccb5cf --- /dev/null +++ b/frontend/src/app/youtube-search-result/youtube-search-result.css @@ -0,0 +1,36 @@ +.image-text-container { + display: flex; /* Aligns child elements side-by-side */ + align-items: center; /* Centers the text vertically with the image */ + gap: 20px; /* Creates a 20-pixel gap between the image and text */ + +} + +.image-text-container-description { + display: flex; + flex-direction: column; +} + +/* Optional: constrain image size so it doesn't get too large */ +.image-text-container img { + width: 150px; + height: auto; +} + +.yt-result { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + padding: 10px; +} + +.yt-result_metadata { +font-size: small; +} + + +.yt-result-title { + font-weight: bold; + font-size: xx-large; + padding-bottom: 10px; +} diff --git a/frontend/src/app/youtube-search-result/youtube-search-result.html b/frontend/src/app/youtube-search-result/youtube-search-result.html new file mode 100644 index 0000000..6a8c652 --- /dev/null +++ b/frontend/src/app/youtube-search-result/youtube-search-result.html @@ -0,0 +1,14 @@ +
+
+ + +
+
Abracadabra
+ + + + +
+
+
diff --git a/frontend/src/app/youtube-search-result/youtube-search-result.spec.ts b/frontend/src/app/youtube-search-result/youtube-search-result.spec.ts new file mode 100644 index 0000000..d234001 --- /dev/null +++ b/frontend/src/app/youtube-search-result/youtube-search-result.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { YoutubeSearchResult } from './youtube-search-result'; + +describe('YoutubeSearchResult', () => { + let component: YoutubeSearchResult; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [YoutubeSearchResult], + }).compileComponents(); + + fixture = TestBed.createComponent(YoutubeSearchResult); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/youtube-search-result/youtube-search-result.ts b/frontend/src/app/youtube-search-result/youtube-search-result.ts new file mode 100644 index 0000000..9342f08 --- /dev/null +++ b/frontend/src/app/youtube-search-result/youtube-search-result.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-youtube-search-result', + imports: [], + templateUrl: './youtube-search-result.html', + styleUrl: './youtube-search-result.css', +}) +export class YoutubeSearchResult {} diff --git a/mpv-client/Dockerfile b/mpv-client/Dockerfile index a607794..39c87b8 100644 --- a/mpv-client/Dockerfile +++ b/mpv-client/Dockerfile @@ -1,5 +1,4 @@ FROM maven:3.9.16-amazoncorretto-25 AS builder -COPY settings.xml /usr/share/maven/conf/settings.xml WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline -B diff --git a/musix/Dockerfile b/musix/Dockerfile index 42fb26d..b6384c0 100644 --- a/musix/Dockerfile +++ b/musix/Dockerfile @@ -1,5 +1,4 @@ FROM maven:3.9.16-amazoncorretto-25 AS builder -COPY settings.xml /usr/share/maven/conf/settings.xml WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline -B diff --git a/musix/pom.xml b/musix/pom.xml index c478c9e..13ad916 100644 --- a/musix/pom.xml +++ b/musix/pom.xml @@ -59,6 +59,35 @@ 2.14.0 compile + + org.testcontainers + testcontainers + test + 2.0.5 + + + org.testcontainers + junit-jupiter + test + 1.21.4 + + + com.github.karsaig + approvalcrest-junit-jupiter + 1.4.1 + test + + + org.json + json + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.8.5 + @@ -112,6 +141,30 @@ + + cz.habarta.typescript-generator + typescript-generator-maven-plugin + 4.1.1 + + + generate-typescript + + generate + + process-classes + + + + jackson2 + module + implementationFile + asInterfaces + + zv.musix.entity.Session + + ../frontend/src/app/dto/api-types.ts + + diff --git a/musix/src/main/java/zv/musix/config/WebConfig.java b/musix/src/main/java/zv/musix/config/WebConfig.java new file mode 100644 index 0000000..18f3c7a --- /dev/null +++ b/musix/src/main/java/zv/musix/config/WebConfig.java @@ -0,0 +1,23 @@ +package zv.musix.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*"); + } + }; + } +} \ No newline at end of file diff --git a/musix/src/main/java/zv/musix/controller/ClientController.java b/musix/src/main/java/zv/musix/controller/ClientController.java index 5ef6bfc..020e8e0 100644 --- a/musix/src/main/java/zv/musix/controller/ClientController.java +++ b/musix/src/main/java/zv/musix/controller/ClientController.java @@ -1,6 +1,7 @@ package zv.musix.controller; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -11,7 +12,7 @@ import zv.musix.entity.Client; import zv.musix.service.ClientService; @RestController -@RequestMapping("/api/client") +@RequestMapping(value = "/api/client", produces = MediaType.APPLICATION_JSON_VALUE) public class ClientController { @Autowired diff --git a/musix/src/main/java/zv/musix/controller/HealthController.java b/musix/src/main/java/zv/musix/controller/HealthController.java index 882e04e..58e08cb 100644 --- a/musix/src/main/java/zv/musix/controller/HealthController.java +++ b/musix/src/main/java/zv/musix/controller/HealthController.java @@ -1,11 +1,12 @@ package zv.musix.controller; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/health") +@RequestMapping(value = "/health", produces = MediaType.APPLICATION_JSON_VALUE) public class HealthController { @GetMapping("/ping") diff --git a/musix/src/main/java/zv/musix/controller/PlayerController.java b/musix/src/main/java/zv/musix/controller/PlayerController.java index 727ee49..b723dd8 100644 --- a/musix/src/main/java/zv/musix/controller/PlayerController.java +++ b/musix/src/main/java/zv/musix/controller/PlayerController.java @@ -1,6 +1,7 @@ package zv.musix.controller; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -10,7 +11,7 @@ import zv.musix.service.ClientService; import zv.musix.service.PlayerService; @RestController -@RequestMapping("/api/player") +@RequestMapping(value = "/api/player", produces = MediaType.APPLICATION_JSON_VALUE) public class PlayerController { @Autowired diff --git a/musix/src/main/java/zv/musix/controller/SessionController.java b/musix/src/main/java/zv/musix/controller/SessionController.java index 833e488..95e7b32 100644 --- a/musix/src/main/java/zv/musix/controller/SessionController.java +++ b/musix/src/main/java/zv/musix/controller/SessionController.java @@ -1,6 +1,7 @@ package zv.musix.controller; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import zv.musix.entity.Session; @@ -8,7 +9,7 @@ import zv.musix.entity.User; import zv.musix.service.SessionService; @RestController -@RequestMapping("/session") +@RequestMapping(value = "/session", produces = MediaType.APPLICATION_JSON_VALUE) public class SessionController { @Autowired @@ -20,8 +21,8 @@ public class SessionController { return ResponseEntity.ok(sessionService.getSession()); } - @PostMapping("/music/add/{userName}/{musicId}") - public ResponseEntity addMusic(@PathVariable String userName, @PathVariable String musicId){ + @PostMapping("/music/add/{musicId}") + public ResponseEntity addMusic(@RequestHeader("Authorization") String userName, @PathVariable String musicId){ return ResponseEntity.ok(sessionService.addMusicToQueue(userName,musicId)); } @@ -30,4 +31,11 @@ public class SessionController { return ResponseEntity.ok(sessionService.nextUser()); } + @GetMapping("testing/play/next") + public ResponseEntity playNext(){ + return ResponseEntity.ok(sessionService.playNext()); + } + + + } diff --git a/musix/src/main/java/zv/musix/controller/UserController.java b/musix/src/main/java/zv/musix/controller/UserController.java index 9734f30..b7de70e 100644 --- a/musix/src/main/java/zv/musix/controller/UserController.java +++ b/musix/src/main/java/zv/musix/controller/UserController.java @@ -1,6 +1,7 @@ package zv.musix.controller; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import zv.musix.dto.user.CreateUserRequest; @@ -10,7 +11,7 @@ import zv.musix.service.SessionService; import java.util.List; @RestController -@RequestMapping("/api/user") +@RequestMapping(value = "/api/user", produces = MediaType.APPLICATION_JSON_VALUE) public class UserController { @Autowired diff --git a/musix/src/main/java/zv/musix/controller/YoutubeController.java b/musix/src/main/java/zv/musix/controller/YoutubeController.java index 3feaf87..3eb7b68 100644 --- a/musix/src/main/java/zv/musix/controller/YoutubeController.java +++ b/musix/src/main/java/zv/musix/controller/YoutubeController.java @@ -1,6 +1,7 @@ package zv.musix.controller; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -14,7 +15,7 @@ import zv.musix.service.YtService; import java.util.List; @RestController -@RequestMapping("/api/youtube") +@RequestMapping(value = "/api/youtube", produces = MediaType.APPLICATION_JSON_VALUE) public class YoutubeController { @Autowired diff --git a/musix/src/main/java/zv/musix/dto/SessionQueueElement.java b/musix/src/main/java/zv/musix/dto/SessionQueueElement.java new file mode 100644 index 0000000..00b2797 --- /dev/null +++ b/musix/src/main/java/zv/musix/dto/SessionQueueElement.java @@ -0,0 +1,16 @@ +package zv.musix.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import zv.musix.python.json.MusicRecord; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SessionQueueElement { + + String userName; + MusicRecord musicRecord; + +} diff --git a/musix/src/main/java/zv/musix/dto/user/CreateUserRequest.java b/musix/src/main/java/zv/musix/dto/user/CreateUserRequest.java index a815253..d6e5d6f 100644 --- a/musix/src/main/java/zv/musix/dto/user/CreateUserRequest.java +++ b/musix/src/main/java/zv/musix/dto/user/CreateUserRequest.java @@ -1,9 +1,19 @@ package zv.musix.dto.user; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; + @Data +@AllArgsConstructor +@NoArgsConstructor public class CreateUserRequest { - String userName; + String userId; + String nickName; + + public CreateUserRequest(String userId){ + this.userId = userId; + } } diff --git a/musix/src/main/java/zv/musix/entity/Session.java b/musix/src/main/java/zv/musix/entity/Session.java index 4237769..c80ead8 100644 --- a/musix/src/main/java/zv/musix/entity/Session.java +++ b/musix/src/main/java/zv/musix/entity/Session.java @@ -1,15 +1,11 @@ package zv.musix.entity; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.AccessLevel; import lombok.Data; -import lombok.Getter; -import lombok.Setter; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; import org.springframework.data.redis.core.RedisHash; +import zv.musix.dto.SessionQueueElement; import zv.musix.python.json.MusicRecord; -import zv.musix.repository.SessionRepository; import java.util.ArrayList; import java.util.List; @@ -20,11 +16,11 @@ public class Session { @Id private String sessionId = "SESSION"; + private Integer currentUserIndex; + private MusicRecord currentMusicRecord; - private Integer userIndex; + private List sessionQueue = new ArrayList<>(); - @Getter(AccessLevel.NONE) - @Setter(AccessLevel.NONE) private List users = new ArrayList<>(); @JsonIgnore @@ -33,45 +29,20 @@ public class Session { throw new IllegalStateException("No users in session"); } - if (userIndex == null) { + if (currentUserIndex == null) { return 0; } - userIndex = (userIndex + 1) % users.size(); - return userIndex; + currentUserIndex = (currentUserIndex + 1) % users.size(); + return currentUserIndex; } - @JsonIgnore - public List addUser(User user) { - //users.stream().filter(e -> e.getUsername().equalsIgnoreCase(user.getUsername())).findAny().orElseGet(()->{users.add(user); return user;}); - boolean alreadyInList = false; - for (User u : users) { - if (u.getUsername().equalsIgnoreCase(user.getUsername())) { - alreadyInList = true; - break; - } - } - if (!alreadyInList) { - users.add(user); - } - - return new ArrayList<>(users); - } - - - public List getUsers() { - return new ArrayList<>(users); - } public User getUserByName(String userName){ - return users.stream().filter(u -> u.getUsername().equalsIgnoreCase(userName)).findFirst().orElseThrow(()->new IllegalArgumentException("userName not found in session")); + return users.stream().filter(u -> u.getUserId().equalsIgnoreCase(userName)).findFirst().orElseThrow(()->new IllegalArgumentException("userName not found in session")); } - public User addMusicToQueue(String userName, MusicRecord musicRecord){ - User user = getUserByName(userName); - user.addToMusicQueue(musicRecord); - return user; - } + } diff --git a/musix/src/main/java/zv/musix/entity/User.java b/musix/src/main/java/zv/musix/entity/User.java index 301387c..3ec9db2 100644 --- a/musix/src/main/java/zv/musix/entity/User.java +++ b/musix/src/main/java/zv/musix/entity/User.java @@ -1,24 +1,22 @@ package zv.musix.entity; -import java.io.Serializable; import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Builder; import lombok.Data; -import lombok.experimental.Delegate; import org.springframework.data.annotation.Id; import org.springframework.data.redis.core.RedisHash; import zv.musix.python.json.MusicRecord; -import zv.musix.repository.MusicRepository; @RedisHash("User") @Data public class User { @Id - private String username; + private String userId; + private String nickName; + List popped = new ArrayList<>(); List musicQueue = new ArrayList<>(); @JsonIgnore @@ -27,6 +25,16 @@ public class User { return new ArrayList<>(musicQueue); } + @JsonIgnore + public MusicRecord popMusic(){ + MusicRecord musicRecord = musicQueue.getFirst(); + popped.add(musicRecord); + musicQueue.removeFirst(); + return musicRecord; + } + + + } \ No newline at end of file diff --git a/musix/src/main/java/zv/musix/python/json/MusicRecord.java b/musix/src/main/java/zv/musix/python/json/MusicRecord.java index 311d9c9..9c19b70 100644 --- a/musix/src/main/java/zv/musix/python/json/MusicRecord.java +++ b/musix/src/main/java/zv/musix/python/json/MusicRecord.java @@ -18,10 +18,11 @@ public class MusicRecord { @Id String videoId; String duration_seconds; + String duration; String year; String views; Album album; - List artistList; + List artists; List thumbnails; @TimeToLive diff --git a/musix/src/main/java/zv/musix/repository/MusicRepository.java b/musix/src/main/java/zv/musix/repository/MusicRepository.java index fa0e978..d1fe527 100644 --- a/musix/src/main/java/zv/musix/repository/MusicRepository.java +++ b/musix/src/main/java/zv/musix/repository/MusicRepository.java @@ -11,4 +11,9 @@ import java.util.Optional; public interface MusicRepository extends CrudRepository { Optional findByVideoId(String videoId); + + default Optional findById(String id) { + throw new UnsupportedOperationException("This method is not supported in ChildInterface."); + } + } diff --git a/musix/src/main/java/zv/musix/service/SessionService.java b/musix/src/main/java/zv/musix/service/SessionService.java index b865074..bd4bef6 100644 --- a/musix/src/main/java/zv/musix/service/SessionService.java +++ b/musix/src/main/java/zv/musix/service/SessionService.java @@ -1,8 +1,9 @@ package zv.musix.service; import jakarta.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; +import zv.musix.dto.SessionQueueElement; import zv.musix.dto.user.CreateUserRequest; import zv.musix.entity.Session; import zv.musix.entity.User; @@ -10,16 +11,16 @@ import zv.musix.python.json.MusicRecord; import zv.musix.repository.MusicRepository; import zv.musix.repository.SessionRepository; +import java.util.ArrayList; import java.util.List; @Service +@AllArgsConstructor public class SessionService { - @Autowired - private SessionRepository sessionRepository; - @Autowired - private MusicRepository musicRepository; + private final SessionRepository sessionRepository; + private final MusicRepository musicRepository; public final static String SESSION_ID = "SESSION"; @@ -33,30 +34,118 @@ public class SessionService { } public List addUser(CreateUserRequest userRequest){ - User user = new User(); - user.setUsername(userRequest.getUserName()); + User newUser = new User(); + newUser.setUserId(userRequest.getUserId()); + newUser.setNickName(userRequest.getNickName()); Session session = getSession(); - session.addUser(user); - session = sessionRepository.save(session); + boolean alreadyInList = false; + List users = session.getUsers(); + for (User u : users) { + if (u.getUserId().equalsIgnoreCase(newUser.getUserId())) { + alreadyInList = true; + break; + } + } + if (!alreadyInList) { + users.add(newUser); + session = sessionRepository.save(session); + } + return session.getUsers(); } public Session nextUser(){ Session session = getSession(); - session.setUserIndex(session.getNextUserIndex()); - User u = session.getUsers().get(session.getUserIndex()); + session.setCurrentUserIndex(getNextUserIndex(session)); + User u = session.getUsers().get(session.getCurrentUserIndex()); session = sessionRepository.save(session); return session; } + private Integer getNextUserIndex(Session session) { + List users = session.getUsers(); + if (users.isEmpty()) { + throw new IllegalStateException("No users in session"); + } + return getNextUserIndex(session.getCurrentUserIndex(), session.getUsers().size()); + } + + private Integer getNextUserIndex(Integer currentUserIndex, int size){ + if (currentUserIndex == null) { + return 0; + } + return (currentUserIndex + 1) % size; + } public User addMusicToQueue(String userId, String musicId){ - MusicRecord music = musicRepository.findByVideoId(musicId).orElseThrow(() -> new IllegalArgumentException("Music not found in database")); + MusicRecord music = musicRepository.findByVideoId(musicId).orElseThrow(() -> new IllegalArgumentException("Music not found in database: "+ musicId)); Session session = getSession(); - User user = session.addMusicToQueue(userId, music); + User user = session.getUserByName(userId); + user.getMusicQueue().add(music); sessionRepository.save(session); + session.setSessionQueue(generateSessionQueue(getSession())); + session = sessionRepository.save(session); return user; } + + public List generateSessionQueue(Session session){ + List newUsers = new ArrayList<>(); + newUsers.addAll(session.getUsers()); + session.setSessionQueue(new ArrayList<>()); + Integer currentUserIndex = session.getCurrentUserIndex(); + while(!newUsers.isEmpty()){ + currentUserIndex = getNextUserIndex(currentUserIndex, newUsers.size()); + User user = newUsers.get(currentUserIndex); + List musicQueue = user.getMusicQueue(); + if(!musicQueue.isEmpty()){ + MusicRecord musicRecord = user.popMusic(); + session.getSessionQueue().add(new SessionQueueElement(user.getUserId(),musicRecord)); + } else { + newUsers.remove(user); + } + } + + + + return session.getSessionQueue(); + } + + + + public Session playNext() { + /* Get next user index + Megnezzuk hogy ennek a usernek van e music a queueban + Ha van akkor megvan a next user + Ha nincs akkor addig keressuk a kovetkezo usert amig nem talalunk valakit akinek van music a queueban. + Ha korbe ertunk akkor errort dobunk mondvan nincs senkinek zeneje. + Onnen tudjuk hogy korbe ertunk hogy az elejen lementjuk az elso next usernek az indexjet, es mindent "nextnel" megnezzuk hogy egyenlo e a lementett indexel + ha igen akkor error + + Ha meg van a next userunk. akkor pop-oljuk a queue jat, beallitjuk az indexet a sessionben + beallitjuk a current songot + e + */ + + + + + Session session = getSession(); + + MusicRecord nextMusic = null; + Integer prevIndex = session.getCurrentUserIndex(); + session = nextUser(); + Integer currentIndex = session.getCurrentUserIndex(); + while(currentIndex != prevIndex){ + User currentUser = session.getUsers().get(currentIndex); + if(currentUser.getMusicQueue().isEmpty()){ + session = nextUser(); + currentIndex = session.getCurrentUserIndex(); + } + } + nextMusic = session.getUsers().get(currentIndex).popMusic(); + session = sessionRepository.save(session); + return session; + } } diff --git a/musix/src/main/resources/application.properties b/musix/src/main/resources/application.properties index edd0538..ef4c505 100644 --- a/musix/src/main/resources/application.properties +++ b/musix/src/main/resources/application.properties @@ -2,8 +2,12 @@ spring.application.name=musix spring.data.redis.host=localhost spring.data.redis.port=6379 +springdoc.api-docs.path=/openapi +springdoc.swagger-ui.path=/docs + + logging.level.zv.musix=TRACE -python.binary=/home/zoli/musix/python/venv/bin/python -python.query.py=/home/zoli/musix/python/query.py +python.binary=/home/zoli/musix/musix/python/venv/bin/python +python.query.py=/home/zoli/musix/musix/python/query.py spring.main.allow-circular-references=true \ No newline at end of file diff --git a/musix/src/test/java/zv/musix/RedisBaseTest.java b/musix/src/test/java/zv/musix/RedisBaseTest.java new file mode 100644 index 0000000..edea958 --- /dev/null +++ b/musix/src/test/java/zv/musix/RedisBaseTest.java @@ -0,0 +1,44 @@ +package zv.musix; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static com.github.karsaig.approvalcrest.jupiter.MatcherAssert.assertThat; +import static com.github.karsaig.approvalcrest.jupiter.matcher.Matchers.sameJsonAsApproved; + +@SpringBootTest +@Testcontainers +public class RedisBaseTest { + + private TestInfo testInfo; + + @Container + static GenericContainer redis = new GenericContainer<>(DockerImageName.parse("redis:7.0-alpine")) + .withExposedPorts(6379); + + // Dynamically update Spring Boot's Redis connection properties + @DynamicPropertySource + static void redisProperties(DynamicPropertyRegistry registry) { + registry.add("spring.data.redis.host", redis::getHost); + registry.add("spring.data.redis.port", () -> redis.getMappedPort(6379)); + } + + + + @BeforeEach + void setUp(TestInfo testInfo) { // 2. JUnit 5 injects it here before every test + this.testInfo = testInfo; + } + + public void validateJson(Object object, String fileName){ + assertThat(object, sameJsonAsApproved(testInfo).withUniqueId(fileName)); + } + +} diff --git a/musix/src/test/java/zv/musix/service/5621a6/562371-session_queue-approved.json b/musix/src/test/java/zv/musix/service/5621a6/562371-session_queue-approved.json new file mode 100644 index 0000000..d243576 --- /dev/null +++ b/musix/src/test/java/zv/musix/service/5621a6/562371-session_queue-approved.json @@ -0,0 +1,195 @@ +/*zv.musix.service.SessionServiceTest.sessionNextSongTest*/ +[ + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_0_1", + "videoId": "id_0_1", + "views": null, + "year": null + }, + "userName": "usr_1111" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_1_1", + "videoId": "id_1_1", + "views": null, + "year": null + }, + "userName": "usr_2222" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_2_1", + "videoId": "id_2_1", + "views": null, + "year": null + }, + "userName": "usr_3333" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_3_1", + "videoId": "id_3_1", + "views": null, + "year": null + }, + "userName": "usr_4444" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_0_2", + "videoId": "id_0_2", + "views": null, + "year": null + }, + "userName": "usr_1111" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_1_2", + "videoId": "id_1_2", + "views": null, + "year": null + }, + "userName": "usr_2222" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_2_2", + "videoId": "id_2_2", + "views": null, + "year": null + }, + "userName": "usr_3333" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_3_2", + "videoId": "id_3_2", + "views": null, + "year": null + }, + "userName": "usr_4444" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_0_3", + "videoId": "id_0_3", + "views": null, + "year": null + }, + "userName": "usr_1111" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_1_3", + "videoId": "id_1_3", + "views": null, + "year": null + }, + "userName": "usr_2222" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_2_3", + "videoId": "id_2_3", + "views": null, + "year": null + }, + "userName": "usr_3333" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_3_3", + "videoId": "id_3_3", + "views": null, + "year": null + }, + "userName": "usr_4444" + } +] \ No newline at end of file diff --git a/musix/src/test/java/zv/musix/service/5621a6/562371-starting_user_state-approved.json b/musix/src/test/java/zv/musix/service/5621a6/562371-starting_user_state-approved.json new file mode 100644 index 0000000..036bfdf --- /dev/null +++ b/musix/src/test/java/zv/musix/service/5621a6/562371-starting_user_state-approved.json @@ -0,0 +1,187 @@ +/*zv.musix.service.SessionServiceTest.sessionNextSongTest*/ +[ + { + "musicQueue": [ + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_0_1", + "videoId": "id_0_1", + "views": null, + "year": null + }, + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_0_2", + "videoId": "id_0_2", + "views": null, + "year": null + }, + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_0_3", + "videoId": "id_0_3", + "views": null, + "year": null + } + ], + "nickName": null, + "popped": [], + "userId": "usr_1111" + }, + { + "musicQueue": [ + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_1_1", + "videoId": "id_1_1", + "views": null, + "year": null + }, + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_1_2", + "videoId": "id_1_2", + "views": null, + "year": null + }, + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_1_3", + "videoId": "id_1_3", + "views": null, + "year": null + } + ], + "nickName": null, + "popped": [], + "userId": "usr_2222" + }, + { + "musicQueue": [ + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_2_1", + "videoId": "id_2_1", + "views": null, + "year": null + }, + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_2_2", + "videoId": "id_2_2", + "views": null, + "year": null + }, + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_2_3", + "videoId": "id_2_3", + "views": null, + "year": null + } + ], + "nickName": null, + "popped": [], + "userId": "usr_3333" + }, + { + "musicQueue": [ + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_3_1", + "videoId": "id_3_1", + "views": null, + "year": null + }, + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_3_2", + "videoId": "id_3_2", + "views": null, + "year": null + }, + { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_3_3", + "videoId": "id_3_3", + "views": null, + "year": null + } + ], + "nickName": null, + "popped": [], + "userId": "usr_4444" + } +] \ No newline at end of file diff --git a/musix/src/test/java/zv/musix/service/5621a6/8d8de5-session_queue_not_in_order-approved.json b/musix/src/test/java/zv/musix/service/5621a6/8d8de5-session_queue_not_in_order-approved.json new file mode 100644 index 0000000..6dc3b77 --- /dev/null +++ b/musix/src/test/java/zv/musix/service/5621a6/8d8de5-session_queue_not_in_order-approved.json @@ -0,0 +1,35 @@ +/*zv.musix.service.SessionServiceTest.userIsSkippedWhenNoMusicIsInHisQueue*/ +[ + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_0_1", + "videoId": "id_0_1", + "views": null, + "year": null + }, + "userName": "usr_1111" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_2_1", + "videoId": "id_2_1", + "views": null, + "year": null + }, + "userName": "usr_3333" + } +] \ No newline at end of file diff --git a/musix/src/test/java/zv/musix/service/5621a6/ffa174-session_queue-approved.json b/musix/src/test/java/zv/musix/service/5621a6/ffa174-session_queue-approved.json new file mode 100644 index 0000000..a244327 --- /dev/null +++ b/musix/src/test/java/zv/musix/service/5621a6/ffa174-session_queue-approved.json @@ -0,0 +1,2 @@ +/*zv.musix.service.SessionServiceTest.sessionQueueWhenNewSongIsAddedByUsersInOrderTest*/ +[] \ No newline at end of file diff --git a/musix/src/test/java/zv/musix/service/5621a6/ffa174-session_queue_1-approved.json b/musix/src/test/java/zv/musix/service/5621a6/ffa174-session_queue_1-approved.json new file mode 100644 index 0000000..4b03290 --- /dev/null +++ b/musix/src/test/java/zv/musix/service/5621a6/ffa174-session_queue_1-approved.json @@ -0,0 +1,19 @@ +/*zv.musix.service.SessionServiceTest.sessionQueueWhenNewSongIsAddedByUsersInOrderTest*/ +[ + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_0_1", + "videoId": "id_0_1", + "views": null, + "year": null + }, + "userName": "usr_1111" + } +] \ No newline at end of file diff --git a/musix/src/test/java/zv/musix/service/5621a6/ffa174-session_queue_2-approved.json b/musix/src/test/java/zv/musix/service/5621a6/ffa174-session_queue_2-approved.json new file mode 100644 index 0000000..0dcee02 --- /dev/null +++ b/musix/src/test/java/zv/musix/service/5621a6/ffa174-session_queue_2-approved.json @@ -0,0 +1,35 @@ +/*zv.musix.service.SessionServiceTest.sessionQueueWhenNewSongIsAddedByUsersInOrderTest*/ +[ + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_0_1", + "videoId": "id_0_1", + "views": null, + "year": null + }, + "userName": "usr_1111" + }, + { + "musicRecord": { + "album": null, + "artistList": null, + "category": null, + "duration_seconds": null, + "resultType": null, + "thumbnails": null, + "timeoutInSeconds": 600, + "title": "title_1_1", + "videoId": "id_1_1", + "views": null, + "year": null + }, + "userName": "usr_2222" + } +] \ No newline at end of file diff --git a/musix/src/test/java/zv/musix/service/SessionServiceTest.java b/musix/src/test/java/zv/musix/service/SessionServiceTest.java new file mode 100644 index 0000000..79c55c5 --- /dev/null +++ b/musix/src/test/java/zv/musix/service/SessionServiceTest.java @@ -0,0 +1,114 @@ +package zv.musix.service; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import zv.musix.RedisBaseTest; +import zv.musix.dto.user.CreateUserRequest; +import zv.musix.entity.Session; +import zv.musix.python.json.MusicRecord; +import zv.musix.repository.MusicRepository; +import zv.musix.repository.SessionRepository; + +import java.util.Optional; + +import static org.mockito.Mockito.when; + +import static com.github.karsaig.approvalcrest.jupiter.MatcherAssert.assertThat; +import static com.github.karsaig.approvalcrest.jupiter.matcher.Matchers.sameJsonAsApproved; + +public class SessionServiceTest extends RedisBaseTest { + + + + @MockitoBean + private MusicRepository musicRepository; + + @Autowired + SessionService sessionService; + + @Autowired + SessionRepository sessionRepository; + + @AfterEach + public void reset(){ + sessionRepository.deleteAll(); + sessionRepository.findById(SessionService.SESSION_ID).orElseGet(() -> sessionRepository.save(new Session())); + } + + @BeforeEach + public void prepareSongMocks(){ + for(int j = 0; j < 4; j++){ + for(int i = 0; i < 4; i++){ + MusicRecord musicRecord = new MusicRecord(); + musicRecord.setVideoId("id_"+i+"_"+j); + musicRecord.setTitle("title_"+i+"_"+j); + when(musicRepository.findByVideoId("id_"+i+"_"+j)).thenReturn(Optional.of(musicRecord)); + } + } + } + @Test + void sessionNextSongTest() { + Session session = prepareSession(); + + validateJson(sessionService.generateSessionQueue(session),"session_queue"); + + } + + @Test + void sessionQueueWhenNewSongIsAddedByUsersInOrderTest(){ + Session session = sessionService.getSession(); + sessionService.addUser( new CreateUserRequest("usr_1111")); + sessionService.addUser( new CreateUserRequest("usr_2222")); + + validateJson(sessionService.getSession().getSessionQueue(),"session_queue"); + sessionService.addMusicToQueue("usr_1111","id_0_1"); + validateJson(sessionService.getSession().getSessionQueue(),"session_queue_1"); + sessionService.addMusicToQueue("usr_2222","id_1_1"); + validateJson(sessionService.getSession().getSessionQueue(),"session_queue_2"); + } + + @Test + void userIsSkippedWhenNoMusicIsInHisQueue(){ + Session session = sessionService.getSession(); + sessionService.addUser( new CreateUserRequest("usr_1111")); + sessionService.addUser( new CreateUserRequest("usr_2222")); + sessionService.addUser( new CreateUserRequest("usr_3333")); + + sessionService.addMusicToQueue("usr_1111","id_0_1"); + sessionService.addMusicToQueue("usr_3333","id_2_1"); + + validateJson(sessionService.getSession().getSessionQueue(),"session_queue_not_in_order"); + } + + private Session prepareSession() { + Session session = sessionService.getSession(); + sessionService.addUser( new CreateUserRequest("usr_1111")); + sessionService.addUser( new CreateUserRequest("usr_2222")); + sessionService.addUser( new CreateUserRequest("usr_3333")); + sessionService.addUser( new CreateUserRequest("usr_4444")); + + + sessionService.addMusicToQueue("usr_1111","id_0_1"); + sessionService.addMusicToQueue("usr_1111","id_0_2"); + sessionService.addMusicToQueue("usr_1111","id_0_3"); + sessionService.addMusicToQueue("usr_2222","id_1_1"); + sessionService.addMusicToQueue("usr_2222","id_1_2"); + sessionService.addMusicToQueue("usr_2222","id_1_3"); + sessionService.addMusicToQueue("usr_3333","id_2_1"); + sessionService.addMusicToQueue("usr_3333","id_2_2"); + sessionService.addMusicToQueue("usr_3333","id_2_3"); + sessionService.addMusicToQueue("usr_4444","id_3_1"); + sessionService.addMusicToQueue("usr_4444","id_3_2"); + sessionService.addMusicToQueue("usr_4444","id_3_3"); + + validateJson(sessionService.getSession().getUsers(),"starting_user_state"); + + + return sessionService.getSession(); + } + + +} diff --git a/python/query.py b/python/query.py deleted file mode 100755 index 703dab6..0000000 --- a/python/query.py +++ /dev/null @@ -1,22 +0,0 @@ -from ytmusicapi import YTMusic -import json -import argparse - -parser = argparse.ArgumentParser() -parser.add_argument("--query", type=str, required=True) -parser.add_argument("--filter", type=str, required=False) -parser.add_argument("--limit", type=int, required=False) -args = parser.parse_args() - - -limit=20; -filter="songs" - -if(args.limit != None): - limit=args.limit; -if(args.filter != None): - filter=args.filter - -ytmusic = YTMusic() -search_results = ytmusic.search(args.query, filter=filter, limit = limit) -print(search_results)