Compare commits

...

167 Commits

Author SHA1 Message Date
0be1ec655a Update devcontainer.json 2023-10-10 04:03:04 +00:00
1fa276d8c1 Adapt Anngular frontend to karafun terminology 2023-10-03 17:22:39 +00:00
85497a1569 Improve search 2023-10-03 17:21:30 +00:00
ff8c113bfc Evrything is working.
Existing parts of frontend can access backend.
2023-09-30 00:43:19 +00:00
2c549c7c59 Now everything is actually working again. 2023-09-30 00:21:58 +00:00
2aec7a3cff Everything working. 2023-09-29 23:37:34 +00:00
76a17874ef Add frontend launch config 2023-09-29 22:51:38 +00:00
2cf48e5904 Merge branch 'master' of github.com:PhoenixTwoFive/karaoqueue 2023-09-29 22:49:56 +00:00
554ea16b9a Update dependencies 2023-09-29 22:49:49 +00:00
d35b65ac12 Update dependencies 2023-09-29 22:48:56 +00:00
38d201b609 Update 2023-09-29 22:46:55 +00:00
362923c5c2 Update 2023-09-29 22:46:06 +00:00
b9865ca936 Update 2023-09-29 22:44:58 +00:00
2f408ff6ed Add empty test.ts 2023-09-29 22:40:35 +00:00
13ead16ffe Add angular CLI to devcontainer 2023-09-29 22:40:08 +00:00
96511f169e Merge branch 'legacy' 2023-09-29 21:49:47 +00:00
8f926621c1 Merge pull request #62 from PhoenixTwoFive/dependabot/pip/backend/flask-2.3.2
Bump flask from 2.2.3 to 2.3.2 in /backend
2023-09-29 23:33:02 +02:00
a8e1a8f647 Merge pull request #67 from PhoenixTwoFive/dependabot/pip/backend/requests-2.31.0
Bump requests from 2.28.2 to 2.31.0 in /backend
2023-09-29 23:32:42 +02:00
e8e0bca648 Update devcontainer.json 2023-09-22 16:55:05 +00:00
5efa21924b Update dockerfile to adapt to new debian version 2023-06-30 15:59:04 +02:00
4c806c3550 update docker-compose container version 2023-06-30 15:58:34 +02:00
c21e6300e9 Merge pull request #70 from PhoenixTwoFive/64-defaults-for-entry-limitation-too-high
lowered maxqueue to 10 and participant quota to 2
2023-06-30 15:34:56 +02:00
7525708dce lowered maxqueue to 10 and participant quota to 2 2023-06-30 15:32:13 +02:00
37d95f61b2 Merge pull request #69 from PhoenixTwoFive/63-input-sanitization
63 input sanitization
2023-06-30 15:29:46 +02:00
a54953ff0d fix copy functionality
Fixed copy functionality by no longer rendering the data into the
function call as string literal, but instead accessing it from the DOM
in the function call.
2023-06-30 15:28:46 +02:00
349eff9a09 strip whitespace from participant name in enqueue endpoint 2023-06-30 14:50:32 +02:00
b7a79462dc Add pipfile 2023-06-30 00:17:44 +02:00
0a038029f7 Merge pull request #68 from PhoenixTwoFive/61-properly-set-focus-on-opening-enqueue-modal
Set focus on singer name input when modal is shown
2023-06-30 00:16:09 +02:00
93e6606d6d Set focus on singer name input when modal is shown 2023-06-30 00:13:48 +02:00
bd3bec8c4f Bump requests from 2.28.2 to 2.31.0 in /backend
Bumps [requests](https://github.com/psf/requests) from 2.28.2 to 2.31.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.28.2...v2.31.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-23 06:41:06 +00:00
9cb93d2d49 Bump flask from 2.2.3 to 2.3.2 in /backend
Bumps [flask](https://github.com/pallets/flask) from 2.2.3 to 2.3.2.
- [Release notes](https://github.com/pallets/flask/releases)
- [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/flask/compare/2.2.3...2.3.2)

---
updated-dependencies:
- dependency-name: flask
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-02 00:33:58 +00:00
1faa2614fa Merge branch 'legacy' of github.com:PhoenixTwoFive/karaoqueue into legacy 2023-04-28 13:41:51 +02:00
93c8a2cb7b Update Version 2023-04-28 13:41:39 +02:00
ddb1e0d2a0 Merge pull request #58 from PhoenixTwoFive:fix/legacy/fix-no-owned-entry
Fix error on no owned entries
2023-04-28 13:40:28 +02:00
e66e4a6c19 Fix error on no owned entries 2023-04-28 13:39:20 +02:00
08d0e5557b Update docker-compose to new version 2023-04-28 13:13:47 +02:00
a987dfc9d2 Merge branch 'legacy' of github.com:PhoenixTwoFive/karaoqueue into legacy 2023-04-28 13:03:57 +02:00
04511a91a1 Freeze versions to avoid Flask 2.3 deprecation problems 2023-04-28 13:03:53 +02:00
7da0dc19dc Merge pull request #57 from PhoenixTwoFive/feature/legacy/56-buttons-for-copying-to-clipboard-in-entry-list
Feature/legacy/56 buttons for copying to clipboard in entry list
2023-04-28 12:56:36 +02:00
d0d8e41b48 Indicate copyability of table text
Indicate copyability on click of table contents by highlighting
hovered element
2023-04-28 12:53:03 +02:00
f2b4611ea6 Remove problematic tooltip and debug log 2023-04-28 12:52:03 +02:00
b76fcfd8e4 Add Copy functionality and corresponding toasts 2023-04-28 12:40:28 +02:00
c9cbd24569 Merge pull request #55 from PhoenixTwoFive/feature/legacy/53-löschung-von-eigenen-einträgen-erlauben
Feature/legacy/53 löschung von eigenen einträgen erlauben
2023-04-27 00:18:37 +02:00
add528fb80 Löschung eigener Enträge implementiert. 2023-04-26 19:20:21 +02:00
865df5d588 Implement EventID to scope ClientIDs and Entry IDs
Implement an EventID saved in settings. Currently this is used to scope
clientIDs and entryIDs to an event. The client checks the event currently going on on
the server, and discards its localstorage (containing the clientID) if
it has changed
2023-04-26 18:08:03 +02:00
adebf35d08 Update lint.yaml
Remove action run on every push
2023-04-25 16:52:36 +02:00
d2caaac4bc Codecheck (#54)
* Add GitHub Action

* Add Linting

* Add .editorconfig
2023-04-25 16:46:43 +02:00
58dd0dd93b Add devcontainer.json setup 2023-04-02 17:21:56 +02:00
10717e753b Update Compose File 2023-03-31 20:58:47 +02:00
1dd79b3be3 Merge pull request #44 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/loader-utils-2.0.4
Bump loader-utils from 2.0.2 to 2.0.4 in /frontend/ng-karaoqueue
2023-03-31 11:47:18 +02:00
e4dec0393c Merge pull request #43 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/http-cache-semantics-4.1.1
Bump http-cache-semantics from 4.1.0 to 4.1.1 in /frontend/ng-karaoqueue
2023-03-31 11:47:02 +02:00
a0eacf5d13 Merge pull request #42 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/ua-parser-js-0.7.33
Bump ua-parser-js from 0.7.31 to 0.7.33 in /frontend/ng-karaoqueue
2023-03-31 11:46:48 +02:00
d7c8d9cfd9 Merge pull request #41 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/json5-2.2.3
Bump json5 from 2.2.1 to 2.2.3 in /frontend/ng-karaoqueue
2023-03-31 11:46:34 +02:00
08314e1fb1 Merge pull request #40 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/engine.io-6.2.1
Bump engine.io from 6.2.0 to 6.2.1 in /frontend/ng-karaoqueue
2023-03-31 11:46:08 +02:00
e6a3cd43b4 Bump loader-utils from 2.0.2 to 2.0.4 in /frontend/ng-karaoqueue
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.2 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.2...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-30 19:21:22 +00:00
0ff01af411 Bump http-cache-semantics from 4.1.0 to 4.1.1 in /frontend/ng-karaoqueue
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-04 17:11:27 +00:00
b1964ffb55 Bump ua-parser-js from 0.7.31 to 0.7.33 in /frontend/ng-karaoqueue
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.31 to 0.7.33.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.31...0.7.33)

---
updated-dependencies:
- dependency-name: ua-parser-js
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-27 11:01:38 +00:00
4e863ecd34 Bump json5 from 2.2.1 to 2.2.3 in /frontend/ng-karaoqueue
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-09 08:02:28 +00:00
a63aad78b0 Bump engine.io from 6.2.0 to 6.2.1 in /frontend/ng-karaoqueue
Bumps [engine.io](https://github.com/socketio/engine.io) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/socketio/engine.io/releases)
- [Changelog](https://github.com/socketio/engine.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/engine.io/compare/6.2.0...6.2.1)

---
updated-dependencies:
- dependency-name: engine.io
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-22 06:38:44 +00:00
580ede3bb0 Update 2022-09-24 03:48:53 +02:00
1a63232401 Update Everything 2022-09-24 03:07:39 +02:00
8183dcfcf4 Angular CLI update for packages - @angular/material@14 2022-09-24 03:05:13 +02:00
81b36e7f1a Update 2022-09-24 03:04:03 +02:00
3c3795e25a @angular/cli migration - update-angular-packages-version-prefix
Update Angular packages 'dependencies' and 'devDependencies' version prefix to '^' instead of '~'.
2022-09-24 02:56:05 +02:00
7a7a647be3 @angular/cli migration - remove-default-project-option
Remove 'defaultProject' option from workspace configuration. The project to use will be determined from the current working directory.
2022-09-24 02:56:01 +02:00
484b7cdfcd Angular CLI update for packages - @angular/core@14, @angular/cli@14 2022-09-24 02:56:01 +02:00
c8a7979132 @angular/cdk migration - migration-v13
Updates the Angular CDK to v13
2022-09-24 02:55:01 +02:00
45e5fd853e Angular CLI update for packages - @angular/material@13 2022-09-24 02:54:57 +02:00
5c66451524 Update 2022-09-24 02:54:17 +02:00
abc06d3181 @angular/cli migration - update-angular-config-v13
Remove deprecated options from 'angular.json' that are no longer present in v13.
2022-09-24 02:51:08 +02:00
2b4cabcb98 Angular CLI update for packages - @angular/core@13, @angular/cli@13 2022-09-24 02:51:07 +02:00
b3670aa0e0 Update @angular/material and @angular/cdk to 12.x 2022-09-24 02:47:31 +02:00
ba982001ba Update @angular/material and @angular/cdk to 11.x 2022-09-24 02:45:04 +02:00
d1ec708780 Update @angular/material and @angular/cdk to 10.x 2022-09-24 02:43:28 +02:00
f901708896 Remove runtime-config-loader 2022-09-24 02:26:37 +02:00
f0a7ee2d2b @angular/cli migration - update-zonejs
Update 'zone.js' to version 0.11.x. Read more about this here: https://github.com/angular/angular/blob/master/packages/zone.js/CHANGELOG.md#breaking-changes-since-zonejs-v0111
2022-09-24 02:24:19 +02:00
bc5818f50d @angular/cli migration - update-angular-config-v12
Remove deprecated options from 'angular.json' that are no longer present in v12.
2022-09-24 02:24:15 +02:00
a1264cb15b Angular CLI update for packages - @angular/core@12, @angular/cli@12 2022-09-24 02:24:15 +02:00
2e1eab3e2a Update 2022-09-24 02:22:34 +02:00
b69e771b75 Update 2022-09-24 02:22:25 +02:00
7d8ae1167e Update package-lock.json 2022-09-24 02:18:40 +02:00
b9e0302443 Delete package-lock.json 2022-09-24 02:14:48 +02:00
bb25b2801b Remove unused package 2022-09-23 23:20:09 +02:00
4d75c125ba Upgrade Angular to 10.x 2022-09-23 23:13:57 +02:00
5050e0cf60 Merge branch 'legacy' into new_frontend 2022-09-23 22:38:30 +02:00
84badb0e13 Retrieve old back end 2022-03-15 19:45:49 +01:00
10613d5c67 update package-lock.json 2022-03-07 18:33:56 +01:00
5bcf73a871 Merge pull request #32 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/jszip-3.7.1
Bump jszip from 3.2.2 to 3.7.1 in /frontend/ng-karaoqueue
2022-03-07 18:15:32 +01:00
645052027c Merge pull request #31 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/color-string-1.9.0
Bump color-string from 1.5.3 to 1.9.0 in /frontend/ng-karaoqueue
2022-03-07 18:15:20 +01:00
bf96424853 Merge pull request #30 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/karma-6.3.16
Bump karma from 4.4.1 to 6.3.16 in /frontend/ng-karaoqueue
2022-03-07 18:15:10 +01:00
d0e9e31a8c Merge pull request #29 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/url-parse-1.5.10
Bump url-parse from 1.4.7 to 1.5.10 in /frontend/ng-karaoqueue
2022-03-07 18:15:00 +01:00
415758b94d Merge pull request #27 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/follow-redirects-1.14.8
Bump follow-redirects from 1.9.0 to 1.14.8 in /frontend/ng-karaoqueue
2022-03-07 18:14:50 +01:00
89fca235ed Merge pull request #23 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/tar-4.4.19
Bump tar from 4.4.13 to 4.4.19 in /frontend/ng-karaoqueue
2022-03-07 18:14:28 +01:00
020a06d381 Merge pull request #20 from PhoenixTwoFive/dependabot/npm_and_yarn/backend/karaoqueue-backend/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7 in /backend/karaoqueue-backend
2022-03-07 18:14:06 +01:00
e09a9db75f Merge pull request #19 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7 in /frontend/ng-karaoqueue
2022-03-07 18:13:50 +01:00
654b8743d1 Merge pull request #17 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/dns-packet-1.3.4
Bump dns-packet from 1.3.1 to 1.3.4 in /frontend/ng-karaoqueue
2022-03-07 18:13:37 +01:00
3d8dfc9fe2 Bump jszip from 3.2.2 to 3.7.1 in /frontend/ng-karaoqueue
Bumps [jszip](https://github.com/Stuk/jszip) from 3.2.2 to 3.7.1.
- [Release notes](https://github.com/Stuk/jszip/releases)
- [Changelog](https://github.com/Stuk/jszip/blob/master/CHANGES.md)
- [Commits](https://github.com/Stuk/jszip/compare/v3.2.2...v3.7.1)

---
updated-dependencies:
- dependency-name: jszip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 17:13:18 +00:00
a642beee31 Merge pull request #16 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/hosted-git-info-2.8.9
Bump hosted-git-info from 2.8.8 to 2.8.9 in /frontend/ng-karaoqueue
2022-03-07 18:13:13 +01:00
8d42db22b4 Bump color-string from 1.5.3 to 1.9.0 in /frontend/ng-karaoqueue
Bumps [color-string](https://github.com/Qix-/color-string) from 1.5.3 to 1.9.0.
- [Release notes](https://github.com/Qix-/color-string/releases)
- [Changelog](https://github.com/Qix-/color-string/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Qix-/color-string/commits/1.9.0)

---
updated-dependencies:
- dependency-name: color-string
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 17:13:10 +00:00
d7de33063e Merge pull request #15 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21 in /frontend/ng-karaoqueue
2022-03-07 18:13:02 +01:00
607abc28f4 Merge pull request #13 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/ssri-6.0.2
Bump ssri from 6.0.1 to 6.0.2 in /frontend/ng-karaoqueue
2022-03-07 18:12:50 +01:00
4a1a2df725 Merge pull request #12 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/y18n-4.0.1
Bump y18n from 4.0.0 to 4.0.1 in /frontend/ng-karaoqueue
2022-03-07 18:12:35 +01:00
7cc614b496 Merge pull request #11 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/elliptic-6.5.4
Bump elliptic from 6.5.3 to 6.5.4 in /frontend/ng-karaoqueue
2022-03-07 18:12:14 +01:00
e4de261dfa Bump karma from 4.4.1 to 6.3.16 in /frontend/ng-karaoqueue
Bumps [karma](https://github.com/karma-runner/karma) from 4.4.1 to 6.3.16.
- [Release notes](https://github.com/karma-runner/karma/releases)
- [Changelog](https://github.com/karma-runner/karma/blob/master/CHANGELOG.md)
- [Commits](https://github.com/karma-runner/karma/compare/v4.4.1...v6.3.16)

---
updated-dependencies:
- dependency-name: karma
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-02 06:40:14 +00:00
7e66a42f9a Bump url-parse from 1.4.7 to 1.5.10 in /frontend/ng-karaoqueue
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.10.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.10)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-26 21:21:08 +00:00
f4c9b8d26a Bump follow-redirects from 1.9.0 to 1.14.8 in /frontend/ng-karaoqueue
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.9.0 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.9.0...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-13 02:28:48 +00:00
573b7a69da Bump tar from 4.4.13 to 4.4.19 in /frontend/ng-karaoqueue
Bumps [tar](https://github.com/npm/node-tar) from 4.4.13 to 4.4.19.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v4.4.13...v4.4.19)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-02 03:50:18 +00:00
da505e93a3 Bump path-parse from 1.0.6 to 1.0.7 in /backend/karaoqueue-backend
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-12 12:15:17 +00:00
ea7fba17a2 Bump path-parse from 1.0.6 to 1.0.7 in /frontend/ng-karaoqueue
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-11 19:21:03 +00:00
f3bf925e3d Bump dns-packet from 1.3.1 to 1.3.4 in /frontend/ng-karaoqueue
Bumps [dns-packet](https://github.com/mafintosh/dns-packet) from 1.3.1 to 1.3.4.
- [Release notes](https://github.com/mafintosh/dns-packet/releases)
- [Changelog](https://github.com/mafintosh/dns-packet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mafintosh/dns-packet/compare/v1.3.1...v1.3.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-28 09:07:27 +00:00
d84eea9683 Bump hosted-git-info from 2.8.8 to 2.8.9 in /frontend/ng-karaoqueue
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 09:15:19 +00:00
48b9bc843d Bump lodash from 4.17.19 to 4.17.21 in /frontend/ng-karaoqueue
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-10 21:15:40 +00:00
a9e16742e0 Bump ssri from 6.0.1 to 6.0.2 in /frontend/ng-karaoqueue
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-30 14:24:28 +00:00
02a66ba522 Bump y18n from 4.0.0 to 4.0.1 in /frontend/ng-karaoqueue
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-31 08:59:19 +00:00
f61df3e5ef Bump elliptic from 6.5.3 to 6.5.4 in /frontend/ng-karaoqueue
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-10 15:08:24 +00:00
80682ab883 Auth cleanup 2020-10-03 23:29:03 +02:00
ba02a01838 Update Example dotenv File 2020-10-03 15:43:31 +02:00
7cecf3a7a7 Fix Packages 2020-10-02 17:54:14 +02:00
f2b5dc93e2 Merge branch 'master' of github.com:PhoenixTwoFive/karaoqueue into master 2020-10-02 17:47:21 +02:00
b28dbba746 Add authentication and authorization 2020-10-02 17:47:13 +02:00
3990741131 Merge pull request #9 from PhoenixTwoFive/dependabot/npm_and_yarn/backend/karaoqueue-backend/bl-2.2.1
Bump bl from 2.2.0 to 2.2.1 in /backend/karaoqueue-backend
2020-09-25 17:18:38 +02:00
f552f8fefd Merge pull request #10 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/http-proxy-1.18.1
Bump http-proxy from 1.18.0 to 1.18.1 in /frontend/ng-karaoqueue
2020-09-25 17:17:04 +02:00
5e93f8592c Bump http-proxy from 1.18.0 to 1.18.1 in /frontend/ng-karaoqueue
Bumps [http-proxy](https://github.com/http-party/node-http-proxy) from 1.18.0 to 1.18.1.
- [Release notes](https://github.com/http-party/node-http-proxy/releases)
- [Changelog](https://github.com/http-party/node-http-proxy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/http-party/node-http-proxy/compare/1.18.0...1.18.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-10 23:16:24 +00:00
d0e90a5003 Bump bl from 2.2.0 to 2.2.1 in /backend/karaoqueue-backend
Bumps [bl](https://github.com/rvagg/bl) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/rvagg/bl/releases)
- [Commits](https://github.com/rvagg/bl/compare/v2.2.0...v2.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-03 21:18:01 +00:00
60dfea50c4 Merge pull request #4 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/websocket-extensions-0.1.4
Bump websocket-extensions from 0.1.3 to 0.1.4 in /frontend/ng-karaoqueue
2020-08-14 12:37:22 +02:00
1edc1b67bb Merge pull request #5 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/npm-registry-fetch-4.0.5
Bump npm-registry-fetch from 4.0.3 to 4.0.5 in /frontend/ng-karaoqueue
2020-08-14 12:37:11 +02:00
96f12b65c7 Merge pull request #6 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19 in /frontend/ng-karaoqueue
2020-08-14 12:36:59 +02:00
711745cd46 Merge pull request #7 from PhoenixTwoFive/dependabot/npm_and_yarn/frontend/ng-karaoqueue/elliptic-6.5.3
Bump elliptic from 6.5.2 to 6.5.3 in /frontend/ng-karaoqueue
2020-08-14 12:36:47 +02:00
4a52d5407f Merge pull request #8 from PhoenixTwoFive/dependabot/npm_and_yarn/backend/karaoqueue-backend/class-transformer-0.3.1
Bump class-transformer from 0.2.3 to 0.3.1 in /backend/karaoqueue-backend
2020-08-14 12:36:26 +02:00
a95ee7ccec Bump class-transformer in /backend/karaoqueue-backend
Bumps [class-transformer](https://github.com/typestack/class-transformer) from 0.2.3 to 0.3.1.
- [Release notes](https://github.com/typestack/class-transformer/releases)
- [Changelog](https://github.com/typestack/class-transformer/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/typestack/class-transformer/compare/v0.2.3...v0.3.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-07 16:25:31 +00:00
4a820e96fd Bump elliptic from 6.5.2 to 6.5.3 in /frontend/ng-karaoqueue
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.2...v6.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-01 07:48:58 +00:00
e834a85375 Bump lodash from 4.17.15 to 4.17.19 in /frontend/ng-karaoqueue
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-19 01:43:53 +00:00
0a438d96f6 Bump npm-registry-fetch from 4.0.3 to 4.0.5 in /frontend/ng-karaoqueue
Bumps [npm-registry-fetch](https://github.com/npm/registry-fetch) from 4.0.3 to 4.0.5.
- [Release notes](https://github.com/npm/registry-fetch/releases)
- [Changelog](https://github.com/npm/npm-registry-fetch/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/registry-fetch/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-08 01:41:12 +00:00
e155078502 Move configuration to dotenv 2020-06-21 19:44:16 +02:00
097a5328e3 Bump websocket-extensions from 0.1.3 to 0.1.4 in /frontend/ng-karaoqueue
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-07 15:02:20 +00:00
c6c6b5da00 Add empty Controllers 2020-05-30 00:20:26 +02:00
c5e8ae0649 Minor API change
Put login and logout endpoints in their own node
2020-05-30 00:20:12 +02:00
91d02331f4 Add routing-controllers 2020-05-30 00:19:10 +02:00
7212f46191 Start new Node.js-based backend 2020-05-26 16:07:52 +02:00
27eb10d222 Remove Flask-based backend 2020-05-26 16:07:29 +02:00
a8ded945e2 Add Documentation 2020-05-26 16:05:56 +02:00
9ad74552fe Cosmetic change to buttons 2020-04-15 01:01:18 +02:00
578adaf893 Updates to search page 2020-04-15 00:51:26 +02:00
673fde9ee6 Readability fix
Adapt theme to make texts readable on dark backgrounds
2020-04-15 00:48:45 +02:00
e61c7a40cc VSCode configuration for frontend 2020-04-15 00:47:37 +02:00
013c52be61 VSCode Python configuration 2020-04-15 00:47:07 +02:00
ecd08d20ef Delete old Dockerfile 2020-04-15 00:46:33 +02:00
d3aea64880 Begin migrating backend to MongoDB 2020-04-15 00:45:00 +02:00
8687408d9c Integrate dockerized Mongodb configuration 2020-04-15 00:43:40 +02:00
ff3a1a3523 Add search 2020-03-26 02:45:55 +01:00
085f4dae96 Fix app background 2020-03-26 02:45:26 +01:00
27f15713cf Add mdi icons 2020-03-26 02:45:14 +01:00
c0c6fb8b06 Add constructors and accessors to models 2020-03-26 02:43:51 +01:00
869166f818 Send more data to the frontend 2020-03-26 02:40:44 +01:00
ca3d5f8a77 Add models 2020-03-25 18:06:26 +01:00
e989189055 Add services 2020-03-25 18:06:16 +01:00
281e8b5211 Add AppRouter and Components 2020-03-25 18:03:58 +01:00
e78e9da24c Add VS Code tasks 2020-03-25 18:02:09 +01:00
9317563047 Add npm scripts
Add npm script making ng serve accessible from other hosts.
2020-03-25 18:01:42 +01:00
c7fd25fae5 Split VS Code Workspace 2020-03-25 18:00:02 +01:00
8780e6c595 Style Changes 2020-03-25 17:59:38 +01:00
8652abe8dc Switch to SCSS 2020-03-11 02:31:38 +01:00
83a8c963a3 Update npm packages for frontend 2020-03-11 01:44:52 +01:00
e9f43c0169 Properly pass user input to SQLite and fix searches with umlauts.
Resolves #3.
2020-03-10 20:11:11 +01:00
a8871c1106 Fix song database update
Resolves #1
2019-12-13 15:29:58 +01:00
dc03dab0ce Update vscode launch definitins 2019-12-13 15:27:10 +01:00
160a64fc4a Update frontend dependencies 2019-12-13 14:47:27 +01:00
68 changed files with 28052 additions and 286 deletions

View File

@ -0,0 +1,40 @@
{
"image": "mcr.microsoft.com/devcontainers/universal:2",
"hostRequirements": {
"cpus": 4
},
"waitFor": "onCreateCommand",
"onCreateCommand": "curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash && sudo apt install -y libmariadb3 libmariadb-dev",
"updateContentCommand": "pip install -r backend/requirements.txt",
"portsAttributes": {
"5000": {
"label": "Application",
"onAutoForward": "openPreview"
}
},
"customizations": {
"codespaces": {
"openFiles": [
"templates/index.html"
]
},
"vscode": {
"extensions": [
"ms-python.python",
"batisteo.vscode-django",
"ms-python.flake8",
"ms-python.isort",
"ms-python.vscode-pylance",
"redhat.vscode-yaml",
"ms-azuretools.vscode-docker",
"donjayamanne.python-extension-pack"
]
}
},
"forwardPorts": [
5000
],
"features": {
"ghcr.io/devcontainers-contrib/features/angular-cli:2": {}
}
}

36
.editorconfig Normal file
View File

@ -0,0 +1,36 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false
[*.md]
trim_trailing_whitespace = true
[*.py]
indent_size = 4
[*.js]
indent_size = 2
[*.html]
indent_size = 2
[*.css]
indent_size = 2
[*.scss]
indent_size = 2
[*.yaml]
indent_size = 2
[*.yml]
indent_size = 2

3
.flake8 Normal file
View File

@ -0,0 +1,3 @@
[flake8]
ignore = E501
max-line-length = 120

23
.github/workflows/lint.yaml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Lint
on: [pull_request]
jobs:
flake8_py3:
runs-on: ubuntu-latest
steps:
- name: Setup Python
uses: actions/setup-python@v4.6.0
with:
python-version: '3.10'
architecture: x64
- name: Checkout PyTorch
uses: actions/checkout@master
- name: Install flake8
run: pip install flake8
- name: Run flake8
uses: suo/flake8-github-action@releases/v1
with:
checkName: 'flake8_py3' # NOTE: this needs to be the same as the job name
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

6
.gitignore vendored
View File

@ -87,7 +87,7 @@ ipython_config.py
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
Pipfile.lock
# celery beat schedule file
celerybeat-schedule
@ -139,5 +139,7 @@ node_modules/
# Version identification file
.version
**/.angular/
# Docker secrets
secrets.yml
secrets.yml

7
.vscode/launch.json vendored
View File

@ -4,6 +4,13 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"command": "npm start",
"name": "Run npm start",
"request": "launch",
"type": "node-terminal",
"cwd": "${workspaceFolder}/frontend/ng-karaoqueue"
},
{
"preLaunchTask": "mariadb",
"name": "Python: Flask",

14
.vscode/settings.json vendored
View File

@ -1,3 +1,15 @@
{
"python.pythonPath": "/usr/bin/python"
"python.pythonPath": "/usr/bin/python",
"python.testing.unittestArgs": [
"-v",
"-s",
"./backend/tests",
"-p",
"*test.py"
],
"python.testing.pytestEnabled": false,
"python.testing.unittestEnabled": true,
"emmet.includeLanguages": {
"django-html": "html"
}
}

12
.vscode/tasks.json vendored
View File

@ -1,5 +1,5 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
@ -9,12 +9,20 @@
"command": "echo \"$(git rev-parse --abbrev-ref HEAD)-$(git describe)\"> ${workspaceFolder}/backend/.version",
"problemMatcher": []
},
{
"label": "Start Frontend",
"type": "npm",
"script": "start",
"path": "frontend/ng-karaoqueue/",
"problemMatcher": [
"$tsc"
]
},
{
"label": "mariadb",
"type": "shell",
"command": "docker-compose -f docker-compose.yml up --remove-orphans",
"isBackground": true,
"activeOnStart": false
}
]
}

View File

@ -1,12 +1,16 @@
FROM tiangolo/meinheld-gunicorn-flask:python3.9
RUN apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db
RUN curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash
# Currently unusable, mariadb is not available through installer for debian 12
# RUN apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db
# RUN curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash
RUN apt-get update
RUN apt-get upgrade -y
RUN apt-get dist-upgrade
# In the meantime, acquire the mariadb packages through apt
RUN apt-get install -y libmariadb3 libmariadb-dev
COPY ./backend/requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt

View File

@ -0,0 +1,13 @@
{
"folders": [
{
"path": "./frontend/ng-karaoqueue"
},
{
"path": "backend"
},
{
"path": "docs"
}
]
}

48
backend/Pipfile Normal file
View File

@ -0,0 +1,48 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
autopep8 = "==2.0.2"
beautifulsoup4 = "==4.12.0"
bs4 = "==0.0.1"
certifi = "==2022.12.7"
charset-normalizer = "==3.1.0"
click = "==8.1.3"
flake8 = "==6.0.0"
flask = "==2.2.3"
flask-basicauth = "==0.2.0"
greenlet = "==2.0.2"
gunicorn = "==20.1.0"
idna = "==3.4"
itsdangerous = "==2.1.2"
jinja2 = "==3.1.2"
mariadb = "==1.1.6"
markupsafe = "==2.1.2"
mccabe = "==0.7.0"
mysql = "==0.0.3"
mysqlclient = "==2.1.1"
numpy = "==1.24.2"
packaging = "==23.0"
pandas = "==1.5.3"
pycodestyle = "==2.10.0"
pyflakes = "==3.0.1"
pymysql = "==1.0.3"
python-dateutil = "==2.8.2"
pytz = "==2023.3"
requests = "==2.28.2"
six = "==1.16.0"
soupsieve = "==2.4"
sqlalchemy = "==2.0.7"
toml = "==0.10.2"
tomli = "==2.0.1"
typing-extensions = "==4.5.0"
urllib3 = "==1.26.15"
werkzeug = "==2.2.3"
[dev-packages]
[requires]
python_version = "3.9"
python_full_version = "3.9.17"

View File

@ -1,5 +1,5 @@
from flask import Flask, render_template, abort, request, redirect, send_from_directory, jsonify
from flask.wrappers import Request, Response
from flask.wrappers import Response
import helpers
import database
import data_adapters
@ -7,11 +7,15 @@ import os
import json
from flask_basicauth import BasicAuth
from helpers import nocache
from flask_cors import CORS
app = Flask(__name__, static_url_path='/static')
CORS(app)
basic_auth = BasicAuth(app)
accept_entries = True
@app.route("/")
def home():
if basic_auth.authenticate():
@ -36,11 +40,11 @@ def enqueue():
if not helpers.is_valid_uuid(client_id):
print(request.data)
abort(400)
name = request.json['name']
name = request.json['name'].strip()
song_id = request.json['id']
if request.authorization:
database.add_entry(name, song_id, client_id)
return Response('{"status":"OK"}', mimetype='text/json')
entry_id = database.add_entry(name, song_id, client_id)
return Response(f"""{{"status":"OK", "entry_id":{entry_id}}}""", mimetype='text/json')
else:
if helpers.get_accept_entries(app):
if not request.json:
@ -54,8 +58,8 @@ def enqueue():
song_id = request.json['id']
if database.check_queue_length() < int(app.config['MAX_QUEUE']):
if database.check_entry_quota(client_id) < int(app.config['ENTRY_QUOTA']):
database.add_entry(name, song_id, client_id)
return Response('{"status":"OK"}', mimetype='text/json')
entry_id = database.add_entry(name, song_id, client_id)
return Response(f"""{{"status":"OK", "entry_id":{entry_id}}}""", mimetype='text/json')
else:
return Response('{"status":"Du hast bereits ' + str(database.check_entry_quota(client_id)) + ' Songs eingetragen, dies ist das Maximum an Einträgen die du in der Warteliste haben kannst."}', mimetype='text/json', status=423)
else:
@ -95,7 +99,7 @@ def settings_post():
else:
abort(400)
if theme is not None and theme in helpers.get_themes():
helpers.set_theme(app,theme)
helpers.set_theme(app, theme)
else:
abort(400)
if username != "" and username != app.config['BASIC_AUTH_USERNAME']:
@ -137,29 +141,68 @@ def songs():
@basic_auth.required
def update_songs():
database.delete_all_entries()
helpers.reset_current_event_id(app)
status = database.import_songs(
helpers.get_songs(helpers.get_catalog_url()))
print(status)
return Response('{"status": "%s" }' % status, mimetype='text/json')
@app.route("/api/songs/compl") # type: ignore
@app.route("/api/songs/compl") # type: ignore
@nocache
def get_song_completions(input_string=""):
input_string = request.args.get('search', input_string)
if input_string != "":
print(input_string)
result = [list(x) for x in database.get_song_completions(input_string=input_string)]
return jsonify(result)
else:
return 400
@app.route("/api/songs/search")
@nocache
def query_songs_with_details(input_string=""):
input_string = request.args.get("q", input_string)
if input_string == "":
return Response(status=400)
result = []
for x in database.get_songs_with_details(input_string):
# Turn row into dict. Add field labels.
result.append(dict(zip(['karafun_id', 'title', 'artist', 'year', 'duo', 'explicit', 'styles', 'languages'], x)))
return jsonify(result)
@app.route("/api/songs/details/<song_id>")
def get_song_details(song_id):
result = database.get_song_details(song_id)
if result is None:
abort(404)
else:
return jsonify(dict(zip(['karafun_id', 'title', 'artist', 'year', 'duo', 'explicit', 'styles', 'languages'], result[0])))
@app.route("/api/entries/delete/<entry_id>")
@app.route("/api/entries/delete/<entry_id>", methods=['GET'])
@nocache
@basic_auth.required
def delete_entry(entry_id):
def delete_entry_admin(entry_id):
if database.delete_entry(entry_id):
return Response('{"status": "OK"}', mimetype='text/json')
else:
return Response('{"status": "FAIL"}', mimetype='text/json')
@app.route("/api/entries/delete/<entry_id>", methods=['POST'])
@nocache
def delete_entry_user(entry_id):
if not request.json:
print(request.data)
abort(400)
client_id = request.json['client_id']
if not helpers.is_valid_uuid(client_id):
print(request.data)
abort(400)
if database.get_raw_entry(entry_id)[3] != client_id: # type: ignore
print(request.data)
abort(403)
if database.delete_entry(entry_id):
return Response('{"status": "OK"}', mimetype='text/json')
else:
@ -176,7 +219,7 @@ def delete_entries():
return
updates = database.delete_entries(request.json)
if updates >= 0:
return Response('{"status": "OK", "updates": '+str(updates)+'}', mimetype='text/json')
return Response('{"status": "OK", "updates": ' + str(updates) + '}', mimetype='text/json')
else:
return Response('{"status": "FAIL"}', mimetype='text/json', status=400)
@ -190,6 +233,7 @@ def mark_sung(entry_id):
else:
return Response('{"status": "FAIL"}', mimetype='text/json')
@app.route("/api/entries/mark_transferred/<entry_id>")
@nocache
@basic_auth.required
@ -205,7 +249,7 @@ def mark_transferred(entry_id):
@basic_auth.required
def set_accept_entries(value):
if (value == '0' or value == '1'):
helpers.set_accept_entries(app,bool(int(value)))
helpers.set_accept_entries(app, bool(int(value)))
return Response('{"status": "OK"}', mimetype='text/json')
else:
return Response('{"status": "FAIL"}', mimetype='text/json', status=400)
@ -215,7 +259,7 @@ def set_accept_entries(value):
@nocache
def get_accept_entries():
accept_entries = helpers.get_accept_entries(app)
return Response('{"status": "OK", "value": '+str(int(accept_entries))+'}', mimetype='text/json')
return Response('{"status": "OK", "value": ' + str(int(accept_entries)) + '}', mimetype='text/json')
@app.route("/api/played/clear")
@ -233,6 +277,7 @@ def clear_played_songs():
@basic_auth.required
def delete_all_entries():
if database.delete_all_entries():
helpers.reset_current_event_id(app)
return Response('{"status": "OK"}', mimetype='text/json')
else:
return Response('{"status": "FAIL"}', mimetype='text/json')
@ -244,19 +289,24 @@ def admin():
return redirect("/", code=303)
@app.before_first_request
def activate_job():
helpers.load_dbconfig(app)
helpers.load_version(app)
database.create_entry_table()
database.create_song_table()
database.create_done_song_table()
database.create_list_view()
database.create_done_song_view()
database.create_config_table()
helpers.setup_config(app)
@app.route("/api/events/current")
@nocache
def get_current_event():
return Response('{"status": "OK", "event": "' + helpers.get_current_event_id(app) + '"}', mimetype='text/json')
def activate_job():
with app.app_context():
helpers.load_dbconfig(app)
helpers.load_version(app)
database.create_entry_table()
database.create_song_table()
database.create_done_song_table()
database.create_list_view()
database.create_done_song_view()
database.create_config_table()
helpers.setup_config(app)
@app.after_request
def add_header(response):
@ -264,14 +314,17 @@ def add_header(response):
Add headers to both force latest IE rendering engine or Chrome Frame,
and also to cache the rendered page for 10 minutes.
"""
if not 'Cache-Control' in response.headers:
if 'Cache-Control' not in response.headers:
response.headers['Cache-Control'] = 'private, max-age=600, no-cache, must-revalidate'
return response
@app.context_processor
def inject_version():
return dict(karaoqueue_version=app.config['VERSION'])
# Perform setup here so it will be executed when the module is imported by the WSGI server.
activate_job()
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080, debug=True)

View File

@ -1,5 +1,5 @@
def dict_from_rows(rows):
outlist=[]
outlist = []
for row in rows:
outlist.append(dict(row._mapping))
return outlist
return outlist

View File

@ -1,11 +1,10 @@
# -*- coding: utf_8 -*-
from email.mime import base
from MySQLdb import Connection
from sqlalchemy import create_engine, engine, text
import pandas
from io import StringIO
from flask import current_app
import uuid
song_table = "songs"
entry_table = "entries"
@ -18,7 +17,6 @@ sql_engine = None
def get_db_engine() -> engine.base.Engine:
global sql_engine
if (not sql_engine):
print(current_app.config.get("DBCONNSTRING"))
sql_engine = create_engine(
current_app.config.get("DBCONNSTRING")) # type: ignore
return sql_engine
@ -64,7 +62,9 @@ def create_song_table():
`Explicit` INTEGER,
`Date Added` TIMESTAMP,
`Styles` TEXT,
`Languages` TEXT
`Languages` TEXT,
PRIMARY KEY (`Id`),
FULLTEXT KEY (`Title`,`Artist`)
)""")
conn.execute(stmt)
conn.commit()
@ -125,22 +125,55 @@ def get_song_list():
def get_song_completions(input_string):
with get_db_engine().connect() as conn:
prepared_string = f"%{input_string.upper()}%"
prepared_string = f"{input_string}"
prepared_string_with_wildcard = f"%{input_string}%"
stmt = text(
"SELECT CONCAT(Artist, ' - ', Title) AS Song, Id FROM songs WHERE CONCAT(Artist, ' - ', Title) LIKE :prepared_string LIMIT 20;")
"""
SELECT CONCAT(Artist, ' - ', Title) AS Song, Id FROM songs
WHERE MATCH(Artist, Title)
AGAINST (:prepared_string IN NATURAL LANGUAGE MODE)
LIMIT 20;
""")
cur = conn.execute(
stmt, {"prepared_string": prepared_string}) # type: ignore
stmt, {"prepared_string": prepared_string, "prepared_string_with_wildcard": prepared_string_with_wildcard}) # type: ignore
return cur.fetchall()
def get_songs_with_details(input_string: str):
with get_db_engine().connect() as conn:
prepared_string = f"%{input_string}"
stmt = text(
"""
SELECT Id, Title, Artist, Year, Duo, Explicit, Styles, Languages FROM songs
WHERE MATCH(Artist, Title)
AGAINST (:prepared_string IN NATURAL LANGUAGE MODE)
LIMIT 20;
"""
)
cur = conn.execute(
stmt, {"prepared_string": prepared_string})
return cur.fetchall()
def get_song_details(song_id: int):
with get_db_engine().connect() as conn:
stmt = text(
"""
SELECT Id, Title, Artist, Year, Duo, Explicit, Styles, Languages FROM songs
WHERE Id = :song_id;
"""
)
cur = conn.execute(
stmt, {"song_id": song_id})
return cur.fetchall()
def add_entry(name, song_id, client_id):
with get_db_engine().connect() as conn:
stmt = text(
"INSERT INTO entries (Song_Id,Name,Client_Id) VALUES (:par_song_id,:par_name,:par_client_id);")
conn.execute(stmt, {"par_song_id": song_id, "par_name": name,
"par_client_id": client_id}) # type: ignore
"INSERT INTO entries (Song_Id,Name,Client_Id) VALUES (:par_song_id,:par_name,:par_client_id) RETURNING entries.ID;")
cur = conn.execute(stmt, {"par_song_id": song_id, "par_name": name,
"par_client_id": client_id}) # type: ignore
conn.commit()
return True
return cur.fetchone()[0] # type: ignore
def add_sung_song(entry_id):
@ -191,6 +224,26 @@ def clear_played_songs():
return True
def get_entry(id):
try:
with get_db_engine().connect() as conn:
cur = conn.execute(text("SELECT * FROM Liste WHERE entry_ID = :par_id"),
{"par_id": id}) # type: ignore
return cur.fetchall()[0]
except Exception:
return None
def get_raw_entry(id):
try:
with get_db_engine().connect() as conn:
cur = conn.execute(text("SELECT * FROM entries WHERE ID = :par_id"),
{"par_id": id}) # type: ignore
return cur.fetchall()[0]
except Exception:
return None
def delete_entry(id):
with get_db_engine().connect() as conn:
conn.execute(text("DELETE FROM entries WHERE id= :par_id"), {
@ -209,7 +262,7 @@ def delete_entries(ids):
"par_id": idlist})
conn.commit()
return cur.rowcount
except Exception as error:
except Exception:
return -1
@ -227,7 +280,7 @@ def get_config(key: str) -> str:
text("SELECT `Value` FROM config WHERE `Key`= :par_key"), {"par_key": key}) # type: ignore
conn.commit()
return cur.fetchall()[0][0]
except IndexError as error:
except IndexError:
return ""
@ -256,9 +309,24 @@ def check_config_table() -> bool:
if conn.dialect.has_table(conn, 'config'):
# type: ignore
# type: ignore
if (conn.execute(text("SELECT COUNT(*) FROM config")).fetchone()[0] > 0): # type: ignore
if (conn.execute(text("SELECT COUNT(*) FROM config")).fetchone()[0] > 0): # type: ignore
return True
else:
return False
else:
return False
def init_event_id() -> bool:
if not get_config("EventID"):
set_config("EventID", str(uuid.uuid4()))
return True
def reset_event_id() -> bool:
set_config("EventID", str(uuid.uuid4()))
return True
def get_event_id() -> str:
return get_config("EventID")

View File

@ -1,6 +1,5 @@
import requests
from bs4 import BeautifulSoup
import json
import os
import uuid
from flask import make_response, Flask
@ -8,6 +7,7 @@ from functools import wraps, update_wrapper
from datetime import datetime
import database
def get_catalog_url():
r = requests.get('https://www.karafun.de/karaoke-song-list.html')
soup = BeautifulSoup(r.content, 'html.parser')
@ -69,13 +69,15 @@ def load_dbconfig(app: Flask):
else:
app.config['DBCONNSTRING'] = ""
else:
exit("No database connection string found. Cannot continue. Please set the environment variable DBSTRING or create a file .dbconn in the root directory of the project.")
exit("""No database connection string found. Cannot continue.
Please set the environment variable DBSTRING or
create a file .dbconn in the root directory of the project.""")
# Check if config exists in DB, if not, create it.
def setup_config(app: Flask):
if check_config_exists() == False:
if check_config_exists() is False:
print("No config found, creating new config")
initial_username = os.environ.get("INITIAL_USERNAME")
initial_password = os.environ.get("INITIAL_PASSWORD")
@ -89,13 +91,14 @@ def setup_config(app: Flask):
exit()
default_config = {'username': initial_username,
'password': initial_password,
'entryquota': 3,
'maxqueue': 20,
'entryquota': 2,
'maxqueue': 10,
'entries_allowed': 1,
'theme': 'default.css'}
for key, value in default_config.items():
database.set_config(key, value)
print("Created new config")
database.init_event_id()
config = database.get_config_list()
app.config['BASIC_AUTH_USERNAME'] = config['username']
app.config['BASIC_AUTH_PASSWORD'] = config['password']
@ -103,6 +106,7 @@ def setup_config(app: Flask):
app.config['MAX_QUEUE'] = config['maxqueue']
app.config['ENTRIES_ALLOWED'] = bool(config['entries_allowed'])
app.config['THEME'] = config['theme']
app.config['EVENT_ID'] = database.get_event_id()
# set queue admittance
@ -151,6 +155,15 @@ def set_theme(app: Flask, theme: str):
print("Theme not found, not setting theme.")
def get_current_event_id(app: Flask):
return app.config['EVENT_ID']
def reset_current_event_id(app: Flask):
database.reset_event_id()
app.config['EVENT_ID'] = database.get_event_id()
def nocache(view):
@wraps(view)
def no_cache(*args, **kwargs):

View File

@ -1,4 +1,4 @@
from app import app
if __name__ == "__main__":
app.run()
app.run()

View File

@ -1,30 +1,37 @@
autopep8
beautifulsoup4
bs4
certifi
charset-normalizer
click
Flask
Flask-BasicAuth
greenlet
gunicorn
idna
itsdangerous
Jinja2
mariadb
MarkupSafe
mysql
mysqlclient
numpy
pandas
pycodestyle
PyMySQL
python-dateutil
pytz
requests
six
soupsieve
SQLAlchemy
toml
urllib3
Werkzeug
autopep8==2.0.2
beautifulsoup4==4.12.0
bs4==0.0.1
certifi==2022.12.7
charset-normalizer==3.1.0
click==8.1.3
flake8==6.0.0
Flask==2.3.2
Flask-BasicAuth==0.2.0
greenlet==2.0.2
gunicorn==20.1.0
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
mariadb==1.1.6
MarkupSafe==2.1.2
mccabe==0.7.0
mysql==0.0.3
mysqlclient==2.1.1
numpy==1.24.2
packaging==23.0
pandas==1.5.3
pycodestyle==2.10.0
pyflakes==3.0.1
PyMySQL==1.0.3
python-dateutil==2.8.2
pytz==2023.3
requests==2.31.0
six==1.16.0
soupsieve==2.4
SQLAlchemy==2.0.7
toml==0.10.2
tomli==2.0.1
typing_extensions==4.5.0
urllib3==1.26.15
Werkzeug==2.2.3
flask_cors==4.0.0

View File

@ -1,4 +1,180 @@
:root {
/* Navbar */
--navbar-background-color: #343a40;
--navbar-text-color: rgba(255, 255, 255, .5);
--navbar-text-color-hover: rgba(255, 255, 255, .75);
--navbar-text-color-active: rgba(255, 255, 255, 1);
/* Common */
--background-color: #ffffff;
--background-color-var: #f5f5f5;
--text-color: #212529;
--text-color-var: #343a40;
/* Modals */
--modal-background-color: #ffffff;
--modal-separator-color: #dee2e6;
--modal-close-color: #212529;
/* Tables */
--table-border-color: #dee2e6;
/* Input */
--input-background-color: #ffffff;
/* Misc */
--copy-highlight-color: rgba(251, 255, 0, 0.6);
}
body {
padding-top: 5rem;
background-color: var(--background-color);
}
html,
body {
height: 100%;
}
.site {
height: auto;
min-height: 100%;
}
main {
padding-bottom: 60px;
/* Höhe des Footers */
}
.footer {
margin-top: -60px;
width: 100%;
height: 60px;
/* Set the fixed height of the footer here */
/*line-height: 60px; /* Vertically center the text there */
background-color: var(--background-color-var);
}
.topbutton {
width: 100%;
}
table td {
overflow: hidden;
text-overflow: ellipsis;
}
table.entries tbody tr[data-index="0"] {
background-color: #007bff80;
font-weight: 600;
}
table.entries tbody tr[data-index="1"] {
background-color: #007bff40;
font-weight: 500;
}
table.entries tbody tr[data-index="2"] {
background-color: #007bff20;
font-weight: 400;
}
table.entries tbody tr[data-index="3"] {
background-color: #007bff10;
}
table td:first-child {
max-width: 200px !important;
}
.fa-solid {
vertical-align: auto;
}
@media (min-width: 768px) {
.topbutton {
width: auto;
}
}
@media print {
body {
font-size: 1.3em;
}
.footer {
display: none !important;
}
.admincontrols {
display: none;
}
}
body {
background-color: var(--background-color);
color: var(--text-color);
}
.footer {
background-color: var(--background-color-var);
}
.modal-content {
background-color: var(--background-color);
color: var(--text-color);
}
.modal-header {
background-color: var(--background-color);
color: var(--text-color-var);
border-color: var(var(--modal-separator-color));
}
.modal-footer {
background-color: var(--background-color);
color: var(--text-color-var);
border-color: var(var(--modal-separator-color));
}
.form-control {
background-color: var(--input-background-color);
color: var(--text-color)
}
.form-control:focus {
background-color: var(--input-background-color);
color: var(--text-color)
}
.table td,
.table th {
border-color: var(--table-border-color)
}
.table thead th {
border-color: var(--table-border-color)
}
table td.buttoncell {
text-align: end;
}
.close {
color: var(--text-color)
}
pre {
color: var(--text-color-var)
}
#entrytable td>span:hover {
background-color: var(--copy-highlight-color);
}
@media (prefers-color-scheme: dark) {
:root {
/* Navbar */
--navbar-background-color: #343a40;
--navbar-text-color: rgba(255, 255, 255, .5);
@ -6,189 +182,20 @@
--navbar-text-color-active: rgba(255, 255, 255, 1);
/* Common */
--background-color: #ffffff;
--background-color-var: #f5f5f5;
--text-color: #212529;
--text-color-var: #343a40;
--background-color: #121212;
--background-color-var: #232323;
--text-color: #f5f5f5;
--text-color-var: #a2a2a2;
/* Modals */
--modal-background-color: #ffffff;
--modal-separator-color: #dee2e6;
--modal-close-color: #212529;
--modal-background-color: #121212;
--modal-separator-color: #232323;
--modal-close-color: #f5f5f5;
/* Tables */
--table-border-color: #dee2e6;
--table-border-color: #232323;
/* Input */
--input-background-color: #ffffff;
}
body {
padding-top: 5rem;
background-color: var(--background-color);
}
html,
body {
height: 100%;
}
.site {
height: auto;
min-height: 100%;
}
main {
padding-bottom: 60px;
/* Höhe des Footers */
}
.footer {
margin-top: -60px;
width: 100%;
height: 60px;
/* Set the fixed height of the footer here */
/*line-height: 60px; /* Vertically center the text there */
background-color: var(--background-color-var);
}
.topbutton {
width: 100%;
}
table td {
overflow: hidden;
text-overflow: ellipsis;
}
table.entries tbody tr[data-index="0"] {
background-color: #007bff80;
font-weight: 600;
}
table.entries tbody tr[data-index="1"] {
background-color: #007bff40;
font-weight: 500;
}
table.entries tbody tr[data-index="2"] {
background-color: #007bff20;
font-weight: 400;
}
table.entries tbody tr[data-index="3"] {
background-color: #007bff10;
}
table td:first-child {
max-width: 200px !important;
}
.fa-solid {
vertical-align: auto;
}
@media (min-width: 768px) {
.topbutton {
width: auto;
}
}
@media print {
body {
font-size: 1.3em;
}
.footer {
display: none !important;
}
.admincontrols {
display: none;
}
}
body {
background-color: var(--background-color);
color: var(--text-color);
}
.footer {
background-color: var(--background-color-var);
}
.modal-content {
background-color: var(--background-color);
color: var(--text-color);
}
.modal-header {
background-color: var(--background-color);
color: var(--text-color-var);
border-color: var(var(--modal-separator-color));
}
.modal-footer {
background-color: var(--background-color);
color: var(--text-color-var);
border-color: var(var(--modal-separator-color));
}
.form-control {
background-color: var(--input-background-color);
color: var(--text-color)
}
.form-control:focus {
background-color: var(--input-background-color);
color: var(--text-color)
}
.table td,
.table th {
border-color: var(--table-border-color)
}
.table thead th {
border-color: var(--table-border-color)
}
table td.buttoncell {
text-align: end;
}
.close {
color: var(--text-color)
}
pre {
color: var(--text-color-var)
}
@media (prefers-color-scheme: dark) {
:root {
/* Navbar */
--navbar-background-color: #343a40;
--navbar-text-color: rgba(255, 255, 255, .5);
--navbar-text-color-hover: rgba(255, 255, 255, .75);
--navbar-text-color-active: rgba(255, 255, 255, 1);
/* Common */
--background-color: #121212;
--background-color-var: #232323;
--text-color: #f5f5f5;
--text-color-var: #a2a2a2;
/* Modals */
--modal-background-color: #121212;
--modal-separator-color: #232323;
--modal-close-color: #f5f5f5;
/* Tables */
--table-border-color: #232323;
/* Input */
--input-background-color: #343434;
}
--input-background-color: #343434;
}
}

View File

@ -15,9 +15,12 @@
<!-- Bootstrap-Tables -->
<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.21.2/dist/bootstrap-table.min.css">
<!-- Bootstrap-Toaster-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-toaster/4.1.2/css/bootstrap-toaster.min.css"
integrity="sha512-kYPLvO+Bu+xttOhbQvxs9nx7XSdxrb2JexRxQ3CpJQ7EtmlkBsWyOjlinLgiLWeLxuupFYB4cPqLOo0gnBnzeQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.2.1/dist/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
<!-- Custom styles for this template -->
<link href="static/css/style.css" rel="stylesheet">
@ -88,16 +91,12 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha384-xBuQ/xzmlsLoJpyjoggmTEz8OWUFM0/RC5BsqQBDX2v5cMvDHcMakNTNrHIW2I5f" crossorigin="anonymous">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"
integrity="sha512-pumBsjNRGGqkPzKHndZMaAG+bir374sORyzM3uulLV14lN5LyykqNk8eEeUlUkB3U0M4FApyaHraT65ihJhDpQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous">
</script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.6/dist/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.2.1/dist/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js"
integrity="sha256-4F7e4JsAJyLUdpP7Q8Sah866jCOhv72zU5E8lIRER4w=" crossorigin="anonymous">
</script>
@ -105,10 +104,13 @@
<script
src="https://unpkg.com/bootstrap-table@1.21.2/dist/extensions/auto-refresh/bootstrap-table-auto-refresh.min.js"></script>
<script src="https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-toaster/4.1.2/js/bootstrap-toaster.min.js"
integrity="sha512-Ur6jgeoP3jnn38C7oBzDqMLRb+wxG2PXLKqgx2vgQ1ePFvbJ28f9iQSJplHD0APFHELOeS/df+RPNeENFtLrYw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
{% block extrajs %}{% endblock %}
<script>
$(document).ready(function () {
loadOrGenerateClientId()
checkEventID()
// get current URL path and assign 'active' class
var pathname = window.location.pathname;
$('.navbar-nav > li > a[href="' + pathname + '"]').parent().addClass('active');
@ -130,6 +132,50 @@
localStorage.setItem("clientId", create_UUID())
}
}
function getClientId() {
return localStorage.getItem("clientId")
}
async function checkEventID() {
const localEventID = localStorage.getItem("eventID")
const resp = await fetch("/api/events/current")
const respJson = await resp.json()
const remoteEventID = respJson.event
if (localEventID == null || localEventID != remoteEventID) {
localStorage.clear()
localStorage.setItem("eventID", remoteEventID)
loadOrGenerateClientId()
}
}
function addEntry(entryId) {
entryArray = JSON.parse(localStorage.getItem("ownedEntries"))
if (entryArray == null) {
entryArray = []
}
entryArray.push(entryId)
localStorage.setItem("ownedEntries", JSON.stringify(entryArray))
}
function removeEntry(entryId) {
entryArray = JSON.parse(localStorage.getItem("ownedEntries"))
if (entryArray == null) {
entryArray = []
}
entryArray = entryArray.filter(function(value, index, arr){ return value != entryId;});
localStorage.setItem("ownedEntries", JSON.stringify(entryArray))
}
function getOwnedEntries() {
var entries = JSON.parse(localStorage.getItem("ownedEntries"))
if (entries == null) {
entries = []
}
return entries;
}
</script>
</body>

View File

@ -17,6 +17,7 @@
<th data-field="Name">Name</th>
<th data-field="Title">Song</th>
<th data-field="Artist">Künstler</th>
<th scope="col" data-formatter="TableActionsFormatter"></th>
</tr>
</thead>
</table>
@ -34,5 +35,43 @@ $.getJSON("/api/entries/accept", (data) => {
$('[data-toggle="tooltip"]').tooltip()
}
})
function TableActionsFormatter(value,row,index) {
console.log("Value: " + value + ", Row: " + row + ", Index: " + index)
console.log(row)
if (getOwnedEntries().includes(row.entry_ID)) {
return "<button type='button' class='btn btn-danger' data-toggle='tooltip' data-placement='top' title='Eintrag zurückziehen' onclick=\"event.stopPropagation();$(this).tooltip('dispose');requestDeletionAsUser("+row["entry_ID"]+")\"><i class='fas fa-trash'></i></button>"
}
return ""
}
function requestDeletionAsUser(id) {
bootbox.confirm("Wirklich den Eintrag zurückziehen? Das könnte zu einer langen Wartezeit führen!", function (result) {
if (result) {
payload = {
"client_id": localStorage.getItem("clientId"),
"entry_id": id
}
$.ajax({
url: "/api/entries/delete/"+id,
type: "POST",
data: JSON.stringify(payload),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(result) {
toast = {
title: "Erfolgreich zurückgezogen",
message: "Eintrag wurde gelöscht",
status: TOAST_STATUS.SUCCESS,
timeout: 5000
}
Toast.create(toast);
location.reload()
}
})
}
})
}
</script>
{% endblock %}

View File

@ -26,9 +26,9 @@
<thead>
<tr>
<th data-field="state" data-checkbox="true"></th>
<th scope="col" data-field="Name">Name</th>
<th scope="col" data-field="Title">Song</th>
<th scope="col" data-field="Artist">Künstler</th>
<th scope="col" data-field="Name" data-formatter="CopyFormatter">Name</th>
<th scope="col" data-field="Title"data-formatter="CopyFormatter">Song</th>
<th scope="col" data-field="Artist"data-formatter="CopyFormatter">Künstler</th>
<th scope="col" data-formatter="TableActions">Aktionen</th>
</tr>
</thead>
@ -185,21 +185,36 @@
});
}
function TableActions(value, row, index) {
console.log("Value: " + value + ", Row: " + row + ", Index: " + index)
console.log(row)
let outerHTML = ""
if (row.Transferred == 1) {
outerHTML = "<button type=\"button\" class=\"btn btn-default\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Markierung zurückziehen\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');markEntryAsTransferred(" + row.entry_ID + ")\"><i class=\"fas fa-backward\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als gesungen markieren\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');markEntryAsSung(" + row.entry_ID + ")\"><i class=\"fas fa-check\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-danger\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Eintrag löschen\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');confirmDeleteEntry('" + row.Name + "'," + row.entry_ID + ")\"><i class=\"fas fa-trash\"></i></button>";
outerHTML = "<button type=\"button\" class=\"btn btn-default\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Markierung zurückziehen\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');markEntryAsTransferred(" + row.entry_ID + ")\"><i class=\"fas fa-backward\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als gesungen markieren\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');markEntryAsSung(" + row.entry_ID + ")\"><i class=\"fas fa-check\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-danger\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Eintrag löschen\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');confirmDeleteEntry(this.parentElement.parentElement.children[1].innerText," + row.entry_ID + ")\"><i class=\"fas fa-trash\"></i></button>";
} else {
outerHTML = "<button type=\"button\" class=\"btn btn-info\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als übertragen markieren\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');markEntryAsTransferred(" + row.entry_ID + ")\"><i class=\"fas fa-exchange-alt\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als gesungen markieren\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');markEntryAsSung(" + row.entry_ID + ")\"><i class=\"fas fa-check\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-danger\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Eintrag löschen\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');confirmDeleteEntry('" + row.Name + "'," + row.entry_ID + ")\"><i class=\"fas fa-trash\"></i></button>";
outerHTML = "<button type=\"button\" class=\"btn btn-info\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als übertragen markieren\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');markEntryAsTransferred(" + row.entry_ID + ")\"><i class=\"fas fa-exchange-alt\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als gesungen markieren\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');markEntryAsSung(" + row.entry_ID + ")\"><i class=\"fas fa-check\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-danger\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Eintrag löschen\" onclick=\"event.stopPropagation();$(this).tooltip('dispose');confirmDeleteEntry(this.parentElement.parentElement.children[1].innerText," + row.entry_ID + ")\"><i class=\"fas fa-trash\"></i></button>";
}
return outerHTML;
}
function CopyFormatter(value, row, index) {
let escapedString = value.replace("\"","\\\"").replace("\'", "\\\'")
return "<span onclick='copyAndNotify(this.innerText)'>"+value+"</span>";
}
function getIdSelections() {
return $.map($("#entrytable").bootstrapTable('getSelections'), function (row) {
return row.entry_ID
})
}
function copyAndNotify(text) {
navigator.clipboard.writeText(text).then(function () {
let toast = {
title: "Kopiert",
message: text,
status: 1,
timeout: 5000
};
Toast.create(toast);
})
}
</script>
{% endblock %}

View File

@ -67,6 +67,9 @@
e.preventDefault();
submitModal();
});
$('#enqueueModal').on('shown.bs.modal', function (e) {
$("#singerNameInput").focus();
})
});
@ -79,7 +82,7 @@
$.ajax({
type: 'POST',
url: '/api/enqueue',
data: JSON.stringify(data), // or JSON.stringify ({name: 'jonas'}),
data: JSON.stringify(data),
success: success_callback,
statusCode: {
423: blocked_callback
@ -96,7 +99,18 @@
function submitModal() {
var name = $("#singerNameInput").val();
var id = $("#selectedId").attr("value");
enqueue(localStorage.getItem("clientId"),id, name, function () {
enqueue(localStorage.getItem("clientId"),id, name, function (response) {
console.log(response);
entryID = response["entry_id"];
toast = {
title: "Erfolgreich eingetragen",
message: "Du wurdest erfolgreich eingetragen.",
status: TOAST_STATUS.SUCCESS,
timeout: 5000
}
Toast.create(toast);
console.log("Entry ID: " + entryID);
addEntry(entryID);
$("#enqueueModal").modal('hide');
window.location.href = '/#end';
}, function (response) {

View File

@ -6,7 +6,7 @@ secrets:
services:
karaoqueue:
image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.03.2"
image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.06"
build: .
restart: always
ports:

233
docs/swagger.yaml Normal file
View File

@ -0,0 +1,233 @@
openapi: 3.0.2
info:
title: Karaoqueue API
version: '0.0.1'
servers:
- url: 'http://localhost:3000/api'
description: Local Test sever instance
- url: 'https://karaoke.phillipathome.dynv6.net/api'
description: Production API
paths:
/api/enqueue:
post:
summary: "Enqueue a song"
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
client_id:
type: string
name:
type: string
id:
type: string
responses:
'200':
description: "Song enqueued successfully"
'400':
description: "Bad request, JSON is required"
'423':
description: "Cannot enqueue, conditions not met"
/api/queue:
get:
summary: "Get the queue"
responses:
'200':
description: "Queue retrieved successfully"
content:
application/json:
schema:
type: array
items:
type: object
/api/songs:
get:
summary: "Get the list of songs"
responses:
'200':
description: "List of songs retrieved successfully"
content:
application/json:
schema:
type: array
items:
type: object
/api/songs/update:
get:
summary: "Update the list of songs"
responses:
'200':
description: "Songs updated successfully"
'400':
description: "Bad request"
/api/songs/compl:
get:
summary: "Get song completions"
parameters:
- name: search
in: query
required: false
schema:
type: string
responses:
'200':
description: "Song completions retrieved successfully"
content:
application/json:
schema:
type: array
items:
type: object
'400':
description: "Bad request"
/api/entries/delete/{entry_id}:
get:
summary: "Admin deletes an entry"
parameters:
- name: entry_id
in: path
required: true
schema:
type: string
responses:
'200':
description: "Entry deleted successfully"
'400':
description: "Entry deletion failed"
post:
summary: "User deletes an entry"
parameters:
- name: entry_id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
client_id:
type: string
responses:
'200':
description: "Entry deleted successfully"
'400':
description: "Bad request, JSON is required"
'403':
description: "Forbidden, client_id mismatch"
/api/entries/delete:
post:
summary: "Delete multiple entries"
requestBody:
required: true
content:
application/json:
schema:
type: array
items:
type: string
responses:
'200':
description: "Entries deleted successfully"
'400':
description: "Bad request, JSON is required"
/api/entries/mark_sung/{entry_id}:
get:
summary: "Mark an entry as sung"
parameters:
- name: entry_id
in: path
required: true
schema:
type: string
responses:
'200':
description: "Entry marked as sung successfully"
'400':
description: "Marking entry as sung failed"
/api/entries/mark_transferred/{entry_id}:
get:
summary: "Toggle transferred status of an entry"
parameters:
- name: entry_id
in: path
required: true
schema:
type: string
responses:
'200':
description: "Entry transferred status toggled successfully"
'400':
description: "Toggling transferred status failed"
/api/entries/accept/{value}:
get:
summary: "Set accept entries status"
parameters:
- name: value
in: path
required: true
schema:
type: string
responses:
'200':
description: "Accept entries status set successfully"
'400':
description: "Setting accept entries status failed"
/api/entries/accept:
get:
summary: "Get accept entries status"
responses:
'200':
description: "Accept entries status retrieved successfully"
/api/played/clear:
get:
summary: "Clear played songs"
responses:
'200':
description: "Played songs cleared successfully"
'400':
description: "Clearing played songs failed"
/api/entries/delete_all:
get:
summary: "Delete all entries"
responses:
'200':
description: "All entries deleted successfully"
'400':
description: "Deleting all entries failed"
/api/events/current:
get:
summary: "Get the current event"
responses:
'200':
description: "Current event retrieved successfully"
security:
- BasicAuth: []
components:
securitySchemes:
BasicAuth:
type: http
scheme: basic

View File

@ -0,0 +1,25 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "firefox",
"request": "launch",
"reAttach": true,
"name": "Launch Firefox against localhost",
"url": "http://localhost:4200",
"webRoot": "${workspaceFolder}"
},
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:4200",
"webRoot": "${workspaceFolder}",
"runtimeExecutable": "/usr/bin/chromium"
}
]
}

View File

@ -0,0 +1,26 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"group": "build",
"problemMatcher": []
},
{
"type": "npm",
"script": "build",
"group": "build",
"problemMatcher": []
},
{
"type": "npm",
"script": "serve",
"problemMatcher": [
"$tsc"
]
}
]
}

View File

@ -0,0 +1,139 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"ng-karaoqueue": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"preserveSymlinks": true,
"outputPath": "dist/ng-karaoqueue",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.json",
"aot": false,
"assets": [
"src/favicon.ico",
"src/assets",
{
"glob": "mdi.svg",
"input": "node_modules/@mdi/angular-material/",
"output": "/assets/"
}
],
"styles": [
"src/custom-theme.scss",
"src/styles.scss"
],
"scripts": [],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
},
"defaultConfiguration": ""
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "ng-karaoqueue:build"
},
"configurations": {
"production": {
"browserTarget": "ng-karaoqueue:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "ng-karaoqueue:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets",
{
"glob": "mdi.svg",
"input": "node_modules/@mdi/angular-material/",
"output": "/assets/"
}
],
"styles": [
"./app/custom-theme.scss",
"src/styles.scss"
],
"scripts": []
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "ng-karaoqueue:serve"
},
"configurations": {
"production": {
"devServerTarget": "ng-karaoqueue:serve:production"
}
}
}
}
}
},
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"cli": {
"analytics": false
}
}

25555
frontend/ng-karaoqueue/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
{
"name": "ng-karaoqueue",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"serve": "ng serve --host=0.0.0.0",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "^16.2.7",
"@angular/cdk": "^16.2.6",
"@angular/common": "^16.2.7",
"@angular/compiler": "^16.2.7",
"@angular/core": "^16.2.7",
"@angular/forms": "^16.2.7",
"@angular/material": "^16.2.6",
"@angular/platform-browser": "^16.2.7",
"@angular/platform-browser-dynamic": "^16.2.7",
"@angular/router": "^16.2.7",
"@mdi/angular-material": "^7.2.96",
"hammerjs": "^2.0.8",
"rxjs": "~7.8.1",
"tslib": "^2.6.2",
"zone.js": "~0.14.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^16.2.4",
"@angular/cli": "^16.2.4",
"@angular/compiler-cli": "^16.2.7",
"@angular/language-service": "^16.2.7",
"@types/jasmine": "~4.3.6",
"@types/jasminewd2": "~2.0.11",
"@types/node": "~20.7.2",
"jasmine-core": "~5.1.1",
"jasmine-spec-reporter": "~7.0.0",
"karma": "~6.4.2",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage-istanbul-reporter": "~3.0.3",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "^2.1.0",
"protractor": "~7.0.0",
"ts-node": "~10.9.1",
"tslint": "~6.1.0",
"typescript": "~5.2.2"
}
}

View File

@ -0,0 +1,16 @@
import { EntryListingComponent } from './entry-listing/entry-listing.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SongSearchComponent } from './song-search/song-search.component';
const routes: Routes = [
{path: '', component: EntryListingComponent },
{path: 'songs', component: SongSearchComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -0,0 +1,11 @@
<div>
<mat-toolbar color="primary" class="mat-elevation-z6">
<span>KaraoQueue</span>
<span>&nbsp;</span>
<a mat-button routerLink="/" [routerLinkActive]="['is-active']">Warteschlange</a>
<a mat-button routerLink="/songs" [routerLinkActive]="['is-active']">Songs</a>
<div class="spacer"></div>
<a mat-stroked-button>Login</a>
</mat-toolbar>
</div>
<router-outlet></router-outlet>

View File

@ -0,0 +1,7 @@
.spacer {
flex: 1 1 auto;
}
.is-active {
background-color: rgba(255,255,255,0.1);
}

View File

@ -0,0 +1,16 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit{
constructor() {}
ngOnInit(): void {
}
title = 'KaraoQueue';
}

View File

@ -0,0 +1,106 @@
import { BrowserModule, DomSanitizer } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatLegacyAutocompleteModule as MatAutocompleteModule } from '@angular/material/legacy-autocomplete';
import { MatBadgeModule } from '@angular/material/badge';
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox';
import { MatLegacyChipsModule as MatChipsModule } from '@angular/material/legacy-chips';
import { MatStepperModule } from '@angular/material/stepper';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatIconRegistry, MatIconModule } from '@angular/material/icon';
import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input';
import { MatLegacyListModule as MatListModule } from '@angular/material/legacy-list';
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
import { MatNativeDateModule, MatRippleModule } from '@angular/material/core';
import { MatLegacyPaginatorModule as MatPaginatorModule } from '@angular/material/legacy-paginator';
import { MatLegacyProgressBarModule as MatProgressBarModule } from '@angular/material/legacy-progress-bar';
import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
import { MatLegacyRadioModule as MatRadioModule } from '@angular/material/legacy-radio';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatLegacySliderModule as MatSliderModule } from '@angular/material/legacy-slider';
import { MatLegacySlideToggleModule as MatSlideToggleModule } from '@angular/material/legacy-slide-toggle';
import { MatLegacySnackBarModule as MatSnackBarModule } from '@angular/material/legacy-snack-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatLegacyTableModule as MatTableModule } from '@angular/material/legacy-table';
import { MatLegacyTabsModule as MatTabsModule } from '@angular/material/legacy-tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/legacy-tooltip';
import { MatTreeModule } from '@angular/material/tree';
import { HttpClientModule } from '@angular/common/http';
import { EntryListingComponent } from './entry-listing/entry-listing.component';
import { SongSearchComponent } from './song-search/song-search.component';
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
import { SongChipComponent } from './song-chip/song-chip.component';
@NgModule({
declarations: [
AppComponent,
EntryListingComponent,
SongSearchComponent,
SongChipComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MatAutocompleteModule,
MatBadgeModule,
MatBottomSheetModule,
MatButtonModule,
MatButtonToggleModule,
MatCardModule,
MatCheckboxModule,
MatChipsModule,
MatStepperModule,
MatDatepickerModule,
MatDialogModule,
MatDividerModule,
MatExpansionModule,
MatGridListModule,
MatIconModule,
MatInputModule,
MatListModule,
MatMenuModule,
MatNativeDateModule,
MatPaginatorModule,
MatProgressBarModule,
MatProgressSpinnerModule,
MatRadioModule,
MatRippleModule,
MatSelectModule,
MatSidenavModule,
MatSliderModule,
MatSlideToggleModule,
MatSnackBarModule,
MatSortModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
MatTooltipModule,
MatTreeModule,
HttpClientModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(matIconRegistry: MatIconRegistry, domSanitizer: DomSanitizer) {
matIconRegistry.addSvgIconSet(domSanitizer.bypassSecurityTrustResourceUrl('./assets/mdi.svg'));
}
}

View File

@ -0,0 +1 @@
<p>entry-listing works!</p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EntryListingComponent } from './entry-listing.component';
describe('EntryListingComponent', () => {
let component: EntryListingComponent;
let fixture: ComponentFixture<EntryListingComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EntryListingComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EntryListingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-entry-listing',
templateUrl: './entry-listing.component.html',
styleUrls: ['./entry-listing.component.scss']
})
export class EntryListingComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { EntryServiceService } from './entry-service.service';
describe('EntryServiceService', () => {
let service: EntryServiceService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(EntryServiceService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Entry } from './models/entry.model';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class EntryServiceService {
private api: string;
constructor(
private http: HttpClient,
private configSvc: RuntimeConfigLoaderService
) {
this.api = configSvc.getConfigObjectKey("api");
}
getEntries(): Observable<Array<Entry>> {
return null; // TODO
}
}

View File

@ -0,0 +1,10 @@
export class Artist {
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
id: number;
name: string;
}

View File

@ -0,0 +1,12 @@
export class Entry {
constructor(singer_name: string, song: string, auth_cookie?: string) {
this.singer_name=singer_name;
this.song=song;
this.auth_cookie = auth_cookie;
}
singer_name: string;
song: string; //Actually the ID of the Song
auth_cookie?: string; //The "cookie" to authenticate for changing an entry. Null otherwise
}

View File

@ -0,0 +1,16 @@
export class Genre {
constructor(id: number, name: string) {
this.id = id;
this._name = name;
}
public get name() : string {
return this._name;
}
id: number;
_name: string;
}

View File

@ -0,0 +1,10 @@
export class Language {
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
id: number;
name: string;
}

View File

@ -0,0 +1,26 @@
import { Genre } from './genre.model';
import { Language } from './language.model';
import { Artist } from './artist.model';
export class Song {
constructor(title: string, artist: Artist, karafun_id: number, duo: boolean, explicit: boolean, id: number, styles: Array<String>, languages: Array<String>) {
this.title=title;
this.artist=artist;
this.karafun_id=karafun_id;
this.duo=duo;
this.explicit=explicit;
this.id=id;
this.styles=styles;
this.languages=languages;
}
title: string;
artist: Artist;
karafun_id: number;
duo: boolean;
explicit: boolean;
id: number;
styles: Array<String>;
languages: Array<String>;
}

View File

@ -0,0 +1,36 @@
<mat-card class="result">
<div class="card-left">
<mat-card-title>{{song.artist}} - {{song.title}}</mat-card-title>
<div class="song-info">
<div class="info-icons">
<ng-template [ngIf]="song.duo==1" [ngIfElse]="nonduet">
<mat-icon svgIcon="account-multiple"></mat-icon>
</ng-template>
<ng-template #nonduet>
<mat-icon svgIcon="account"></mat-icon>
</ng-template>
<ng-template [ngIf]="song.explicit==1" [ngIfElse]="nonexplicit">
<mat-icon svgIcon="alpha-e-box"></mat-icon>
</ng-template>
<ng-template #nonexplicit>
<mat-icon svgIcon="alpha-e-box" class="icon-disabled"></mat-icon>
</ng-template>
</div>
<div class="genre-list">
<mat-icon svgIcon="music-circle"></mat-icon>
<mat-chip-list *ngFor="let style of song.styles">
<mat-chip>{{style}}</mat-chip>
</mat-chip-list>
</div>
<div class="language-list">
<mat-icon svgIcon="account-voice"></mat-icon>
<mat-chip-list *ngFor="let language of song.languages">
<mat-chip>{{language}}</mat-chip>
</mat-chip-list>
</div>
</div>
</div>
<button mat-flat-button color="accent" class="add-button">
<mat-icon svgIcon="playlist-plus"></mat-icon>
</button>
</mat-card>

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SongChipComponent } from './song-chip.component';
describe('SongChipComponent', () => {
let component: SongChipComponent;
let fixture: ComponentFixture<SongChipComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SongChipComponent]
});
fixture = TestBed.createComponent(SongChipComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-song-chip',
templateUrl: './song-chip.component.html',
styleUrls: ['./song-chip.component.scss']
})
export class SongChipComponent {
}

View File

@ -0,0 +1,13 @@
<div class="content">
<mat-form-field class="search-input">
<mat-label>Suche...</mat-label>
<mat-icon matSuffix svgIcon="magnify"></mat-icon>
<input type="text" matInput placeholder="Tippe einen Künstler oder Song..."
(keyup)="applyFilter($event.target.value)">
</mat-form-field>
</div>
<div class="resultcontainer">
<mat-list *ngFor="let song of songs">
</mat-list>
</div>

View File

@ -0,0 +1,120 @@
.search-input {
width: 95%;
margin-top: 1rem;
}
.add-button {
height: 3rem;
}
.content {
overflow: hidden;
display: flex;
justify-content: center;
}
.resultcontainer {
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
}
.result {
margin-left: auto;
margin-right: auto;
width: 90%;
margin-top: 0.2rem;
display: flex;
align-items: stretch;
justify-content: space-between;
flex-direction: column;
}
.card-left {
display: flex;
flex-direction: column;
width: 100%;
}
.song-info {
box-sizing: border-box;
display: flex;
flex-direction: column;
width: 100%;
}
.song-info > * {
margin-right: 0.5rem;
}
.icon-disabled {
opacity: 0.1;
}
.genre-list > mat-icon {
padding-right: 0.25rem;
}
.genre-list mat-chip-list {
margin-right: 0.15rem;
}
.genre-list {
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
display: flex;
flex-direction: row;
padding-bottom: 0.25rem;
align-items: center;
-webkit-mask-image: linear-gradient(to right, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 2px,rgba(0,0,0,1) calc(100% - 2px),rgba(0,0,0,0) 100%);
mask-image: linear-gradient(to right, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 2px,rgba(0,0,0,1) calc(100% - 2px),rgba(0,0,0,0) 100%);
-ms-overflow-style: none;
}
.language-list > mat-icon {
padding-right: 0.25rem;
}
.language-list mat-chip-list {
margin-right: 0.15rem;
}
.genre-list::-webkit-scrollbar {
width: 0px;
height: 0px;
display: none;
}
.language-list {
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
display: flex;
flex-direction: row;
padding-bottom: 0.25rem;
align-items: center;
-ms-overflow-style: none;
}
.language-list::-webkit-scrollbar {
display: none;
}
$breakpoint-tablet: 768px;
@media (min-width: $breakpoint-tablet) {
.result {
flex-direction: row;
}
.add-button {
height: 6.5rem;
width: 4rem;
align-self: center;
}
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SongSearchComponent } from './song-search.component';
describe('SongSearchComponent', () => {
let component: SongSearchComponent;
let fixture: ComponentFixture<SongSearchComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SongSearchComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SongSearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,43 @@
import { Component, OnInit } from '@angular/core';
import { SongServiceService } from "../song-service.service";
import { Song } from '../models/song.model';
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
@Component({
selector: 'app-song-search',
templateUrl: './song-search.component.html',
styleUrls: ['./song-search.component.scss']
})
export class SongSearchComponent implements OnInit {
private searchSub$ = new Subject<string>();
constructor(private songServiceService: SongServiceService) { }
songs: Array<Song> = new Array<Song>();
updateSongs(text: string) {
this.songServiceService.searchSongByText(text).subscribe(x => {
console.log(x);
this.songs = x;
});
}
applyFilter(filterValue: string) {
this.searchSub$.next(filterValue)
}
ngOnInit(): void {
this.searchSub$.pipe(
debounceTime(400),
distinctUntilChanged()
).subscribe((filterValue: string) => {
this.updateSongs(filterValue);
});
//Für Testzwecke
this.updateSongs("Test");
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { SongServiceService } from './song-service.service';
describe('SongServiceService', () => {
let service: SongServiceService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SongServiceService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,51 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { Song } from './models/song.model';
import { Artist } from './models/artist.model';
import { Genre } from './models/genre.model';
import { Language } from './models/language.model';
import { ConnectableObservable, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SongServiceService {
private api: string;
constructor(
private http: HttpClient,
) {
// TODO: get api from config
this.api= "http://localhost:5000/api";
}
searchSongByText(text: string): Observable<Array<Song>> {
let out = new Array<Song>();
this.http.get(this.api +"/songs/search?q="+text).subscribe((data: Observable<JSON>) => {
data.forEach(element => {
let styles = new Array<String>();
let languages = new Array<String>();
for (let style of element["styles"].split(",")) {
styles.push(style);
}
for (let language of element["languages"].split(",")) {
languages.push(language);
}
out.push(new Song(element["title"],element["artist"],element["karafun_id"],element["duo"],element["explicit"],element["_id"],styles,languages));
});
console.log(out);
});
const observable = new Observable<Array<Song>>( subscriber => {
subscriber.next(out);
})
return observable;
}
}

View File

@ -0,0 +1,3 @@
{
"api":"http://localhost:5000/api"
}

View File

@ -0,0 +1,280 @@
/**
* Generated theme by Material Theme Generator
* https://materialtheme.arcsine.dev
*/
@import '@angular/material/theming';
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Fonts
@import 'https://fonts.googleapis.com/css?family=Material+Icons';
@import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500');
$fontConfig: (
display-4: mat-typography-level(112px, 112px, 300, 'Roboto', -0.0134em),
display-3: mat-typography-level(56px, 56px, 400, 'Roboto', -0.0089em),
display-2: mat-typography-level(45px, 48px, 400, 'Roboto', 0.0000em),
display-1: mat-typography-level(34px, 40px, 400, 'Roboto', 0.0074em),
headline: mat-typography-level(24px, 32px, 400, 'Roboto', 0.0000em),
title: mat-typography-level(20px, 32px, 500, 'Roboto', 0.0075em),
subheading-2: mat-typography-level(16px, 28px, 400, 'Roboto', 0.0094em),
subheading-1: mat-typography-level(15px, 24px, 500, 'Roboto', 0.0067em),
body-2: mat-typography-level(14px, 24px, 500, 'Roboto', 0.0179em),
body-1: mat-typography-level(14px, 20px, 400, 'Roboto', 0.0179em),
button: mat-typography-level(14px, 14px, 500, 'Roboto', 0.0893em),
caption: mat-typography-level(12px, 20px, 400, 'Roboto', 0.0333em),
input: mat-typography-level(inherit, 1.125, 400, 'Roboto', 1.5px)
);
// Foreground Elements
// Light Theme Text
$dark-text: #000000;
$dark-primary-text: rgba($dark-text, 0.87);
$dark-accent-text: rgba($dark-primary-text, 0.54);
$dark-disabled-text: rgba($dark-primary-text, 0.38);
$dark-dividers: rgba($dark-primary-text, 0.12);
$dark-focused: rgba($dark-primary-text, 0.12);
$mat-light-theme-foreground: (
base: black,
divider: $dark-dividers,
dividers: $dark-dividers,
disabled: $dark-disabled-text,
disabled-button: rgba($dark-text, 0.26),
disabled-text: $dark-disabled-text,
elevation: black,
secondary-text: $dark-accent-text,
hint-text: $dark-disabled-text,
accent-text: $dark-accent-text,
icon: $dark-accent-text,
icons: $dark-accent-text,
text: $dark-primary-text,
slider-min: $dark-primary-text,
slider-off: rgba($dark-text, 0.26),
slider-off-active: $dark-disabled-text,
);
// Dark Theme text
$light-text: #ffffff;
$light-primary-text: $light-text;
$light-accent-text: rgba($light-primary-text, 0.7);
$light-disabled-text: rgba($light-primary-text, 0.5);
$light-dividers: rgba($light-primary-text, 0.12);
$light-focused: rgba($light-primary-text, 0.12);
$mat-dark-theme-foreground: (
base: $light-text,
divider: $light-dividers,
dividers: $light-dividers,
disabled: $light-disabled-text,
disabled-button: rgba($light-text, 0.3),
disabled-text: $light-disabled-text,
elevation: black,
hint-text: $light-disabled-text,
secondary-text: $light-accent-text,
accent-text: $light-accent-text,
icon: $light-text,
icons: $light-text,
text: $light-text,
slider-min: $light-text,
slider-off: rgba($light-text, 0.3),
slider-off-active: rgba($light-text, 0.3),
);
// Background config
// Light bg
$light-background: #fafafa;
$light-bg-darker-5: darken($light-background, 5%);
$light-bg-darker-10: darken($light-background, 10%);
$light-bg-darker-20: darken($light-background, 20%);
$light-bg-darker-30: darken($light-background, 30%);
$light-bg-lighter-5: lighten($light-background, 5%);
$dark-bg-tooltip: lighten(#2c2c2c, 20%);
$dark-bg-alpha-4: rgba(#2c2c2c, 0.04);
$dark-bg-alpha-12: rgba(#2c2c2c, 0.12);
$mat-light-theme-background: (
background: $light-background,
status-bar: $light-bg-darker-20,
app-bar: $light-bg-darker-5,
hover: $dark-bg-alpha-4,
card: $light-bg-lighter-5,
dialog: $light-bg-lighter-5,
tooltip: $dark-bg-tooltip,
disabled-button: $dark-bg-alpha-12,
raised-button: $light-bg-lighter-5,
focused-button: $dark-focused,
selected-button: $light-bg-darker-20,
selected-disabled-button: $light-bg-darker-30,
disabled-button-toggle: $light-bg-darker-10,
unselected-chip: $light-bg-darker-10,
disabled-list-option: $light-bg-darker-10,
);
// Dark bg
$dark-background: #2c2c2c;
$dark-bg-lighter-5: lighten($dark-background, 5%);
$dark-bg-lighter-10: lighten($dark-background, 10%);
$dark-bg-lighter-20: lighten($dark-background, 20%);
$dark-bg-lighter-30: lighten($dark-background, 30%);
$light-bg-alpha-4: rgba(#fafafa, 0.04);
$light-bg-alpha-12: rgba(#fafafa, 0.12);
// Background palette for dark themes.
$mat-dark-theme-background: (
background: $dark-background,
status-bar: $dark-bg-lighter-20,
app-bar: $dark-bg-lighter-5,
hover: $light-bg-alpha-4,
card: $dark-bg-lighter-5,
dialog: $dark-bg-lighter-5,
tooltip: $dark-bg-lighter-20,
disabled-button: $light-bg-alpha-12,
raised-button: $dark-bg-lighter-5,
focused-button: $light-focused,
selected-button: $dark-bg-lighter-20,
selected-disabled-button: $dark-bg-lighter-30,
disabled-button-toggle: $dark-bg-lighter-10,
unselected-chip: $dark-bg-lighter-20,
disabled-list-option: $dark-bg-lighter-10,
);
// Compute font config
@include mat-core();
// Theme Config
body {
--primary-color: #090a28;
--primary-lighter-color: #b5b6bf;
--primary-darker-color: #050518;
--text-primary-color: #{$light-primary-text};
--text-primary-lighter-color: #{$dark-primary-text};
--text-primary-darker-color: #{$light-primary-text};
}
$mat-primary: (
main: #090a28,
lighter: #b5b6bf,
darker: #050518,
200: #090a28, // For slide toggle,
contrast : (
main: $light-primary-text,
lighter: $dark-primary-text,
darker: $light-primary-text,
)
);
$theme-primary: mat-palette($mat-primary, main, lighter, darker);
body {
--accent-color: #b70000;
--accent-lighter-color: #e9b3b3;
--accent-darker-color: #9f0000;
--text-accent-color: #{$light-primary-text};
--text-accent-lighter-color: #{$dark-primary-text};
--text-accent-darker-color: #{$light-primary-text};
}
$mat-accent: (
main: #b70000,
lighter: #e9b3b3,
darker: #9f0000,
200: #b70000, // For slide toggle,
contrast : (
main: $light-primary-text,
lighter: $dark-primary-text,
darker: $light-primary-text,
)
);
$theme-accent: mat-palette($mat-accent, main, lighter, darker);
body {
--warn-color: #ffc600;
--warn-lighter-color: #ffeeb3;
--warn-darker-color: #ffb100;
--text-warn-color: #{$dark-primary-text};
--text-warn-lighter-color: #{$dark-primary-text};
--text-warn-darker-color: #{$dark-primary-text};
}
$mat-warn: (
main: #ffc600,
lighter: #ffeeb3,
darker: #ffb100,
200: #ffc600, // For slide toggle,
contrast : (
main: $dark-primary-text,
lighter: $dark-primary-text,
darker: $dark-primary-text,
)
);
$theme-warn: mat-palette($mat-warn, main, lighter, darker);;
$theme: mat-dark-theme($theme-primary, $theme-accent, $theme-warn);
$altTheme: mat-light-theme($theme-primary, $theme-accent, $theme-warn);
// Theme Init
@include angular-material-theme($theme);
.theme-alternate {
@include angular-material-theme($altTheme);
}
// Specific component overrides, pieces that are not in line with the general theming
// Handle buttons appropriately, with respect to line-height
.mat-raised-button, .mat-stroked-button, .mat-flat-button {
padding: 0 1.15em;
margin: 0 .65em;
min-width: 3em;
line-height: 36.4px
}
.mat-standard-chip {
padding: .5em .85em;
min-height: 2.5em;
}
.material-icons {
font-size: 24px;
font-family: 'Material Icons', 'Material Icons';
.mat-badge-content {
font-family: 'Roboto';
}
}
mat-form-field.mat-form-field {
&.mat-focused {
.mat-form-field-label {
color: $light-text;
}
.mat-form-field-ripple {
background-color: $light-text !important;
}
}
.mat-form-field-label.mat-focused,
.mat-form-field.mat-focused.matform-field-should-float {
color: $light-text;
}
.mat-form-field-underline, .mat-form-field-label {
color: $light-accent-text;
}
.mat-form-field-underline.mat-focused {
background-color: $light-text;
}
}
button.mat-menu-item {
line-height: 24px !important;
}
a.mat-menu-item > mat-icon {
margin-bottom: 14px;
}
.mat-icon svg {
height: 24px;
width: 24px;
}

View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>NgKaraoqueue</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body class="mat-typography mat-app-background">
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,4 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -0,0 +1,61 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes recent versions of Safari, Chrome (including
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@ -0,0 +1,10 @@
/* You can add global styles to this file, and also import other style files */
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}

View File

@ -0,0 +1,34 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
<T>(id: string): T;
keys(): string[];
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().forEach(context);

View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"baseUrl": "",
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"lib": [
"es6",
"dom"
],
"mapRoot": "./",
"module": "ES2022",
"moduleResolution": "node",
"outDir": "../dist/out-tsc",
"sourceMap": true,
"target": "ES2022",
"skipLibCheck": true,
"typeRoots": [
"../node_modules/@types"
],
"useDefineForClassFields": false
},
"include": [
"main.ts",
"polyfills.ts",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,16 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/app/app.module.ts",
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,17 @@
/*
This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScripts language server to improve development experience.
It is not intended to be used to perform a compilation.
To learn more about this file see: https://angular.io/config/solution-tsconfig.
*/
{
"files": [],
"references": [
{
"path": "./src/tsconfig.json"
}
],
"compilerOptions": {
"target": "ES2022",
}
}

View File

@ -0,0 +1,18 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

141
package-lock.json generated Normal file
View File

@ -0,0 +1,141 @@
{
"name": "karaoqueue",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"runtime-config-loader": "^5.0.2"
}
},
"node_modules/@angular/common": {
"version": "16.2.7",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.7.tgz",
"integrity": "sha512-vcKbbtDXNmJ8dj1GF52saJRT5U3P+phnIwnv+hQ2c+VVj/S2alWlBkT12iM+KlvnWdxsa0q4yW0G4WvpPJPaMQ==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
"@angular/core": "16.2.7",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/compiler": {
"version": "16.2.7",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.7.tgz",
"integrity": "sha512-Sp+QjHFYjBMhjag/YbIV5skqr/UrpBjCPo1WFBBhj5DKkvgWC7T00yYJn+aBj0DU5ZuMmO/P8Vb7bRIHIRNL4w==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
"@angular/core": "16.2.7"
},
"peerDependenciesMeta": {
"@angular/core": {
"optional": true
}
}
},
"node_modules/@angular/core": {
"version": "16.2.7",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.7.tgz",
"integrity": "sha512-JQOxo+Ja9ThQjUa4vdOMLZfIK2dhR3cnPbqB1tV2WuTmIv49QASbFHsae8zZsS4Au5/TafBaW3KkK9aRU8G5gg==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
"rxjs": "^6.5.3 || ^7.4.0",
"zone.js": "~0.13.0"
}
},
"node_modules/@angular/platform-browser": {
"version": "16.2.7",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.7.tgz",
"integrity": "sha512-yQ/4FB33Jc1Xs+slWfddZpbKdkCHdhCh39Mfjxa1wTen6YJZKmvjBbMNCkvnvNbLqc2IFWRwTQdG8s0n1jfl3A==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
"@angular/animations": "16.2.7",
"@angular/common": "16.2.7",
"@angular/core": "16.2.7"
},
"peerDependenciesMeta": {
"@angular/animations": {
"optional": true
}
}
},
"node_modules/@angular/platform-browser-dynamic": {
"version": "16.2.7",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.7.tgz",
"integrity": "sha512-raeuYEQfByHByLnA5YRR7fYD/5u6hMjONH77p08IjmtdmLb0XYP18l/C4YqsIOQG6kZLNCVWknEHZu3kuvAwtQ==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
"@angular/common": "16.2.7",
"@angular/compiler": "16.2.7",
"@angular/core": "16.2.7",
"@angular/platform-browser": "16.2.7"
}
},
"node_modules/runtime-config-loader": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/runtime-config-loader/-/runtime-config-loader-5.0.2.tgz",
"integrity": "sha512-6LnDfuV79wPuwykgxK0nDx/b28z6iXsKChlnc5OIr+iYPWNzgIe9MBaH7nNw9ACitWq4T1+Rkc+fvJeWNBBpug==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": ">=13.0.0",
"@angular/core": ">=13.0.0",
"@angular/platform-browser-dynamic": ">=13.0.0",
"rxjs": ">=6.6.0"
}
},
"node_modules/rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/zone.js": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.13.3.tgz",
"integrity": "sha512-MKPbmZie6fASC/ps4dkmIhaT5eonHkEt6eAy80K42tAm0G2W+AahLJjbfi6X9NPdciOE9GRFTTM8u2IiF6O3ww==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
}
}
}
}

5
package.json Normal file
View File

@ -0,0 +1,5 @@
{
"dependencies": {
"runtime-config-loader": "^5.0.2"
}
}