Compare commits
624 Commits
v1
...
affdadf3aa
| Author | SHA1 | Date | |
|---|---|---|---|
| affdadf3aa | |||
| 8d9a7bb8b3 | |||
| 1f631be1c7 | |||
| 5b4959771c | |||
| b8e1bbb28c | |||
| c8fe11bd2b | |||
| 09b11cc4ad | |||
| 660319afec | |||
| 8181404f0c | |||
| e1207b8d9b | |||
| 97a676ccd7 | |||
| 136620f1e6 | |||
| 9b11360a0e | |||
| deb69e6014 | |||
| 4e96905fb2 | |||
| e2f344ab4e | |||
| 76a8a72e83 | |||
| 3e302198a2 | |||
| 4187f9da4e | |||
| d85b07bde7 | |||
| 3cbdd3b203 | |||
| 56b14f60f1 | |||
| 01c86406dc | |||
| a28b8aca7a | |||
| bee381702b | |||
| 10cd0f0b09 | |||
| fcaa0dcd07 | |||
| ea35ddb0dc | |||
| b4536ca6e9 | |||
| 7f482fd6c3 | |||
| 1d1dfc2c1c | |||
| a197d7d980 | |||
| 06063b94b3 | |||
| 43d43a6d7d | |||
| 1849cda2f5 | |||
| f836657603 | |||
| 7e0b427137 | |||
| b79c8fe476 | |||
| f71bad3da3 | |||
| 170cac2091 | |||
| ba942b2f8f | |||
| 05c05f1b72 | |||
| ec6782160c | |||
| 1581276854 | |||
| e1c0645428 | |||
| be3cdb8235 | |||
| bfc7d9993a | |||
| ca69d410f2 | |||
| a2d4edc0e5 | |||
| 0cc35300f1 | |||
| 2f21806bea | |||
| c5d75a8710 | |||
| f3dd37f649 | |||
| b035dd6b76 | |||
| 34c4bb8cb7 | |||
| de5c9f4ea1 | |||
| 9ab69ffef5 | |||
| d2ef59d171 | |||
| 8e2b333f47 | |||
| 594fb3073f | |||
| 3e87d5c140 | |||
| 86bec7f2ee | |||
| 8e242dc8da | |||
| efca9cf5d8 | |||
| 8573fffaa2 | |||
| 51aeb67ad6 | |||
| 5e371edf2b | |||
| d46ad72cb6 | |||
| a6ae2aacfb | |||
| dc862e4b3c | |||
| e56c5edfb4 | |||
| 70b310adef | |||
| 6748288f3c | |||
| 2c5d45e9c2 | |||
| c02c13bf90 | |||
| 902ca114c1 | |||
| 17cd039c9b | |||
| 26f955fce2 | |||
| 3cc48fb8f7 | |||
| 60178dc54b | |||
| 8a63a3448a | |||
| f5336f63ce | |||
| 97583349df | |||
| 9bec336323 | |||
| 123b64a666 | |||
| 5a215231fa | |||
| 6a29b5386c | |||
| 7cd138b09f | |||
| 504837fe77 | |||
| 7dde1d86f8 | |||
| a480ae9c50 | |||
| 2c9a87bf3e | |||
| 03ea94b858 | |||
| 9e42985ec8 | |||
| dbf17aee3b | |||
| 2b91d9a798 | |||
| 74d18419a6 | |||
| e3f007b568 | |||
| 6690f09a32 | |||
| 7c496b4c7f | |||
| 77abdd807d | |||
| 7599a7d60a | |||
| 14993b9fe7 | |||
| acba3a9f53 | |||
| 2b62fc141d | |||
| 4baa0bbe77 | |||
| d88376f2fb | |||
| eaece9e334 | |||
| 7e158d48c6 | |||
| 4ed153ac5b | |||
| 6b17296139 | |||
| bb5dffe395 | |||
| 0fa844f856 | |||
| 4fea77c837 | |||
| f76c797a7f | |||
| ad81fd9199 | |||
| f732c80230 | |||
| eb97d15f03 | |||
| be590d8e2b | |||
| a41845e45c | |||
| a1cb4f243a | |||
| 110e055e24 | |||
| f3a35bd62a | |||
| 565d9a5a4d | |||
| dbc29046f5 | |||
| 0152502c1b | |||
| 0520e3d7e5 | |||
| dc49e168ab | |||
| da55f2b19e | |||
| 424f2a8779 | |||
| 255bfba9e3 | |||
| 593a79c506 | |||
| d4a7600c14 | |||
|
|
7ead76fbcc | ||
|
|
f87e4a0800 | ||
|
|
eab03d7f5a | ||
| 9dc77e6dde | |||
| 3a804c99ce | |||
|
|
c49b000521 | ||
|
|
ebc1b9441e | ||
| 0e5402c347 | |||
| 6922242b4f | |||
| 73ca5e57e8 | |||
| a579dd4759 | |||
|
|
609a0297d5 | ||
|
|
0743bad93c | ||
|
|
49848db96f | ||
|
|
ba736d2b19 | ||
|
|
753cb04dfe | ||
|
|
bc25210fe4 | ||
|
|
a4e0175173 | ||
|
|
bd8ea67017 | ||
| 8126250d1a | |||
|
|
8ac2d20d8a | ||
| 1b4d32eed3 | |||
| 1a67fcffb4 | |||
| bbc6c0fbd3 | |||
| 6657bae0cd | |||
| 65ea5985d3 | |||
| 7defc9a6a9 | |||
| 504cb8e950 | |||
| f81b84e4b3 | |||
| c12e1e38b8 | |||
| db7ac8d174 | |||
| 0b0c9304b1 | |||
| f56f592c4c | |||
| b0fe27c643 | |||
| 55bc825cb7 | |||
| 7fa46ef245 | |||
| e1c0722fce | |||
| 3f511cefa8 | |||
| f1adba4fa6 | |||
| a9d3a30782 | |||
| 55a8cfad46 | |||
| 420ff46ceb | |||
| 75f037da02 | |||
| 1238095f09 | |||
| 3084bb268b | |||
| f1c0d3b896 | |||
| 340cf738dc | |||
| f23320eb1c | |||
| 9fb1667bf0 | |||
| 1f95577eb7 | |||
| 45ccb6fc4c | |||
| b1092985ff | |||
| 6c5e4c2a1e | |||
| caa8d47af2 | |||
| cdc4744f28 | |||
| f4a0aabb61 | |||
| 64f4a3a58c | |||
| a23c3b0fdd | |||
| 3dd5d2958a | |||
| 69df761bf4 | |||
| 4a571e1944 | |||
| fa86b26f46 | |||
| 480d118014 | |||
| 2e5d0dcd73 | |||
| b6b488edf6 | |||
| bf5a744499 | |||
| 1a4864ba00 | |||
| e299cde6da | |||
| 8372cfad1b | |||
| d477e803ab | |||
| bf89ef16f7 | |||
| 744d977454 | |||
| b95f89687f | |||
| 143ba3c138 | |||
| 094af845a0 | |||
| 5efeb5fba6 | |||
| 42ab052699 | |||
| e6c9feed6b | |||
| e63a3db8b9 | |||
| 75cefea4fa | |||
| 150a18cc0b | |||
| 64b20e26ac | |||
| f08c8f013d | |||
| 721570927f | |||
| 62fe6089f7 | |||
| 0a76e64d2f | |||
| 2e98d166ec | |||
|
|
bc737c830f | ||
|
|
23a74bdfc6 | ||
|
|
f702167d6e | ||
|
|
fe31c01a06 | ||
|
|
2d0a0e53c0 | ||
|
|
072adb5bb1 | ||
|
|
e5555c815b | ||
|
|
a074f0c4f0 | ||
|
|
d92f996169 | ||
|
|
96bb3a5c0f | ||
|
|
18810766ed | ||
|
|
764ebe3586 | ||
|
|
add4c3e99f | ||
|
|
f9c4ec1d31 | ||
|
|
f2d653563c | ||
|
|
288b0c8d97 | ||
|
|
69e5e1c75b | ||
|
|
399fbbaab5 | ||
|
|
06f7731011 | ||
|
|
81d8bc45ee | ||
|
|
17a4e7ec14 | ||
|
|
b73c3ebfb3 | ||
|
|
b0a044db97 | ||
|
|
fce44f49b6 | ||
|
|
6d0c75ceff | ||
|
|
324bf6891a | ||
|
|
e5f29e4725 | ||
|
|
54e0675ba9 | ||
|
|
e02af774a9 | ||
|
|
f6ed12fc7a | ||
|
|
c15f18108d | ||
|
|
7239182e83 | ||
|
|
eba6e00251 | ||
|
|
6f3341e6ad | ||
|
|
910f190c86 | ||
|
|
c4c3d1bd60 | ||
|
|
f166de1a43 | ||
|
|
71dc81c4dc | ||
|
|
8883b521e9 | ||
|
|
6be3b8338d | ||
|
|
9d1351527d | ||
|
|
13daa3cbac | ||
|
|
19afc5d055 | ||
|
|
bb29177e41 | ||
|
|
f48e5d4b19 | ||
|
|
cf25e4e1e6 | ||
|
|
966e67afee | ||
|
|
a0432eec68 | ||
|
|
522d0c1471 | ||
|
|
ca1b7a84c9 | ||
|
|
e32e35d3af | ||
|
|
ef2e6c9a20 | ||
|
|
fa748494f6 | ||
|
|
73bf27d222 | ||
|
|
c05ea18513 | ||
|
|
23dc3495f1 | ||
|
|
bdcacdf6b7 | ||
|
|
c0ff47e478 | ||
|
|
6d0a59f5b9 | ||
|
|
a56adfed8a | ||
|
|
a2a520e9fd | ||
|
|
bd189caf80 | ||
|
|
333346889d | ||
|
|
26ba7e0ef6 | ||
|
|
5e61526218 | ||
|
|
ae2634f57e | ||
|
|
583aeb9225 | ||
|
|
dcef6e4500 | ||
|
|
e53a1bad0e | ||
|
|
3f0ade7fb2 | ||
|
|
e778b3ebae | ||
|
|
f54e5d8716 | ||
|
|
90b8b00ef6 | ||
|
|
9602a201ce | ||
|
|
ba907d5499 | ||
|
|
1eb63d7a8c | ||
|
|
1cca9457ee | ||
|
|
20b67eafe1 | ||
|
|
4e1f1629a1 | ||
|
|
2eac1b3c78 | ||
|
|
a895372e13 | ||
|
|
52b3616de4 | ||
|
|
caa7bb2af6 | ||
|
|
a8bb2aa000 | ||
|
|
c6d88fe7bf | ||
|
|
48e84e815b | ||
|
|
d821ce43b5 | ||
|
|
83c40ec417 | ||
|
|
fcfbf5a01d | ||
|
|
f924bcfcc6 | ||
|
|
9d9717df61 | ||
|
|
1b6bc61263 | ||
|
|
650b993325 | ||
|
|
1525215757 | ||
|
|
9eb9e047e1 | ||
|
|
326bd09662 | ||
|
|
53509ecf63 | ||
|
|
95a5eafec2 | ||
|
|
2838a91e3c | ||
|
|
7fcd674b7f | ||
|
|
d9d17c8342 | ||
|
|
42e9f18fb6 | ||
|
|
7c2db9bb00 | ||
|
|
2060b9140f | ||
|
|
ed33678f50 | ||
|
|
63cf9c758d | ||
|
|
efe37d2dd7 | ||
|
|
a12e2be0e7 | ||
|
|
7870baed08 | ||
|
|
4e6b75d167 | ||
|
|
38b93cd500 | ||
|
|
04422b94d0 | ||
|
|
be3a5fb6c3 | ||
|
|
8b5270d2ed | ||
|
|
173361bc2b | ||
|
|
09d6da4ad3 | ||
|
|
862b43ae6d | ||
|
|
b1854c1a30 | ||
|
|
a0f256946e | ||
|
|
8b489da287 | ||
|
|
87f93b3cab | ||
|
|
0fd60b5ccb | ||
|
|
ebe1c0b2df | ||
|
|
addb71c558 | ||
|
|
b7a21ceb94 | ||
|
|
67b201e7c5 | ||
|
|
1254925c4a | ||
|
|
1cc32fa5c4 | ||
|
|
77d24ed90f | ||
|
|
d2d2463164 | ||
|
|
2bb3b0fd48 | ||
|
|
e43d6bff06 | ||
|
|
4401a91d35 | ||
|
|
fc863cc422 | ||
|
|
dd3724b4de | ||
|
|
dc1b35fbe0 | ||
|
|
d62af84dfd | ||
|
|
b5119b6be1 | ||
|
|
ac5890f115 | ||
|
|
96a906b717 | ||
|
|
02da555028 | ||
|
|
4d4eb6e640 | ||
|
|
0dd24f1e7f | ||
|
|
20d0e1c8eb | ||
|
|
dfb633f6a7 | ||
|
|
0ff0ce1252 | ||
|
|
09dffe020d | ||
|
|
f3a5a8de25 | ||
|
|
d107164d7d | ||
|
|
fd76a085dc | ||
|
|
7a9708137c | ||
|
|
13a49d3306 | ||
|
|
6fa91d8890 | ||
|
|
a580e4335b | ||
|
|
a4080cc1b1 | ||
|
|
4f5a4913d7 | ||
|
|
83f453da3f | ||
|
|
c340e48f02 | ||
|
|
61492bc669 | ||
|
|
6264a15b1d | ||
|
|
257af8106d | ||
|
|
6eedc8aba9 | ||
|
|
cba98bdf6f | ||
|
|
d2da868b71 | ||
|
|
685221b454 | ||
|
|
f12e5f10d5 | ||
|
|
cc7b4d7daa | ||
|
|
769c876dc5 | ||
|
|
ccec79cca7 | ||
|
|
e79f2199c3 | ||
|
|
c53d315bd8 | ||
|
|
0ae9c27d93 | ||
|
|
2950034a30 | ||
|
|
158115bb3b | ||
|
|
56184a8254 | ||
|
|
c27b1689f3 | ||
|
|
3c3dd2af92 | ||
|
|
125260e7ef | ||
|
|
b608a0779c | ||
|
|
7225db0bf1 | ||
|
|
3f0cdff262 | ||
|
|
3270039a6a | ||
|
|
fda972a90e | ||
|
|
45e81c98bf | ||
|
|
d9dd9bbf4d | ||
|
|
160de6443b | ||
|
|
923a3c18b8 | ||
|
|
8dc37525ce | ||
|
|
7bd34842fa | ||
|
|
da8b01bb98 | ||
|
|
c9fe469f5b | ||
|
|
406f7cad65 | ||
|
|
558e237608 | ||
|
|
b4251a0f1f | ||
|
|
0234a8e179 | ||
|
|
efacaa9b86 | ||
|
|
7c40d999ff | ||
|
|
99c14693d5 | ||
|
|
2cf03d4b68 | ||
|
|
52bab229ea | ||
|
|
8b15383b45 | ||
|
|
eaddefdc8e | ||
|
|
95cfa815fb | ||
|
|
870b2516a1 | ||
|
|
ef982a52ed | ||
|
|
6ae2390b46 | ||
|
|
e1fbf601f3 | ||
|
|
2364a53dd1 | ||
|
|
be7cecc721 | ||
|
|
f2aad2ccdf | ||
|
|
8e76a68b62 | ||
|
|
6c722a9ac3 | ||
|
|
52515e7df1 | ||
|
|
722c56271f | ||
|
|
9e85c35f59 | ||
|
|
538a15b609 | ||
|
|
bb0ab9dc67 | ||
|
|
87b9b5e1c2 | ||
|
|
ba0b46db90 | ||
|
|
ddcbf56abe | ||
|
|
c553f6f5da | ||
|
|
5b25774851 | ||
|
|
01729b982d | ||
|
|
fd01787dfb | ||
|
|
90eed5c74c | ||
|
|
dcfb836b39 | ||
|
|
610501ef19 | ||
|
|
48e2e1eb98 | ||
|
|
f3ad71e33f | ||
|
|
eaacdb3446 | ||
|
|
be173e1d48 | ||
|
|
e3f040c978 | ||
|
|
e25b005643 | ||
|
|
4631e66dca | ||
|
|
73b93f8262 | ||
|
|
5a1ecebbe9 | ||
|
|
8dbf458ea3 | ||
|
|
c5fef04d06 | ||
|
|
5fb507febc | ||
|
|
50d6954829 | ||
|
|
e5f4fd9d62 | ||
|
|
7da87fb065 | ||
|
|
b81646c1d4 | ||
|
|
752f84127c | ||
|
|
bf1b9533c8 | ||
|
|
feddea1a69 | ||
|
|
20fcd5015e | ||
|
|
9abf32b288 | ||
|
|
5d9c32a196 | ||
|
|
cdc2954d0b | ||
|
|
b2929fe211 | ||
|
|
3a74f3abcd | ||
|
|
49077e7023 | ||
|
|
44b2d07fdb | ||
|
|
0ee4f3fe04 | ||
|
|
456d87f262 | ||
|
|
e47a4c29f7 | ||
|
|
f172d765e3 | ||
|
|
9bf129f1ad | ||
|
|
1cb603a3f3 | ||
|
|
0a807605ad | ||
|
|
e20415a5bd | ||
|
|
15a3789174 | ||
|
|
823970f617 | ||
|
|
5a025eb75b | ||
|
|
bf6641c151 | ||
|
|
f18877f9b1 | ||
|
|
c3679afed7 | ||
|
|
a0ca9af5c9 | ||
|
|
d1f73e6d78 | ||
|
|
368886c95c | ||
|
|
86f3957028 | ||
|
|
6ec86fdc25 | ||
|
|
068858f3a1 | ||
|
|
8d11d2360e | ||
|
|
970bca7121 | ||
|
|
07c09e48e3 | ||
|
|
b2c816cafb | ||
|
|
960a0bceff | ||
|
|
aaf4c05630 | ||
|
|
fada1a11b0 | ||
|
|
dfa34a6808 | ||
|
|
1b427607d1 | ||
|
|
70445069fd | ||
|
|
423616b9f3 | ||
|
|
26ed50c94b | ||
|
|
caa34dd79d | ||
|
|
0eabe27196 | ||
|
|
dfc2b5af17 | ||
|
|
955946d0a6 | ||
|
|
9e515d9ed7 | ||
|
|
95507fd41f | ||
|
|
8d75b30ae4 | ||
|
|
64bcfe74e7 | ||
|
|
2729564495 | ||
|
|
0ec6949095 | ||
|
|
681403ec6e | ||
|
|
aaee81e9c4 | ||
|
|
4e5124cc1b | ||
|
|
99a7d7bd73 | ||
|
|
6fd1336f1c | ||
|
|
33c1ffa0ba | ||
|
|
19001e5836 | ||
|
|
12bc66a95b | ||
|
|
63b2b40227 | ||
|
|
bb53f1c40a | ||
|
|
46f08059d7 | ||
|
|
a9af1ec15e | ||
|
|
9179d6825c | ||
|
|
4816befa71 | ||
|
|
67c846ddc8 | ||
|
|
79d860351c | ||
|
|
c1216ea708 | ||
|
|
a11569a737 | ||
|
|
cd4feec58f | ||
|
|
518ec7055a | ||
|
|
2552b92e5d | ||
|
|
e9d3ab5307 | ||
|
|
becc67c46b | ||
|
|
c3a0833410 | ||
|
|
da53a0eef7 | ||
|
|
289f8921ff | ||
|
|
e1a0cfeebb | ||
|
|
55a7d71c7f | ||
|
|
8d1cb47a8a | ||
|
|
2510d6748c | ||
|
|
f17ff9246d | ||
|
|
a2a9a6e21d | ||
|
|
0e1ddfbccb | ||
|
|
2edf8b4a9f | ||
|
|
e47cac71fc | ||
|
|
0aa28d9764 | ||
|
|
8b032462c0 | ||
|
|
3ae694a3da | ||
|
|
741d13b18a | ||
|
|
0d8cc5bd5d | ||
|
|
04ef9dc827 | ||
|
|
c11ff632d2 | ||
|
|
c5a3c0550c | ||
|
|
a67829035e | ||
|
|
f57aac4f6c | ||
|
|
3a53fa0a3c | ||
|
|
7145890801 | ||
|
|
6d5a5fd16c | ||
|
|
3092daaad4 | ||
|
|
0f989a38c3 | ||
|
|
e8706cad1c | ||
|
|
d55490dd51 | ||
|
|
a1cd6b5cd9 | ||
|
|
0e43278bdd | ||
|
|
d8bb2b7356 | ||
|
|
f779e5e920 | ||
|
|
c57ad9cce7 | ||
|
|
b492d65efb | ||
|
|
332937f964 | ||
|
|
f5501f77fe | ||
|
|
30bfae96fd | ||
|
|
d95d613a57 | ||
|
|
1c96f9d13c | ||
|
|
9e182768f6 | ||
|
|
11dace2617 | ||
|
|
93cfe7cd1a | ||
|
|
b580781618 | ||
|
|
ee9190447f | ||
|
|
aa501244e1 | ||
|
|
52d39151da | ||
|
|
0241be13cf | ||
|
|
c3acb4898e | ||
|
|
559a00c181 | ||
|
|
122a205f92 | ||
|
|
c4e7e10f5e | ||
|
|
6d83c31f42 | ||
|
|
d98e8ef0f8 | ||
|
|
a29dc8257e | ||
|
|
801c78bb84 | ||
|
|
a671e601c0 | ||
|
|
4de6804f13 | ||
|
|
7a3d61c659 | ||
|
|
b0d9837256 | ||
|
|
f07e3c5a5a | ||
|
|
19b5c7816a | ||
|
|
dae09668b2 | ||
|
|
aa150a7a69 | ||
|
|
d0f03a19a2 | ||
|
|
863a002370 | ||
|
|
ff9bcc6433 | ||
|
|
e062df4eb6 | ||
|
|
2dd1d1f69c | ||
|
|
3d4f22f6f6 | ||
|
|
48c95d4ec6 | ||
|
|
b19208b3b0 | ||
|
|
6410846afc | ||
|
|
f7a16fd287 | ||
|
|
4159170244 | ||
|
|
8c82631569 | ||
|
|
96bd131807 | ||
|
|
0cde0fe302 | ||
|
|
49c893f515 | ||
|
|
3bb4e7daab | ||
|
|
afb3a7f3a3 | ||
|
|
69b50275cd | ||
|
|
76866fe14f | ||
|
|
3308def6c5 | ||
|
|
39b632d483 |
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "C# (.NET)",
|
||||
"image": "mcr.microsoft.com/devcontainers/dotnet:0-6.0",
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {},
|
||||
"extensions": [
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"portsAttributes": {
|
||||
"5118": {
|
||||
"label": "Moonlight",
|
||||
"onAutoForward": "notify"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
@@ -8,6 +7,7 @@
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
@@ -21,5 +21,10 @@
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
**/appsettings.json
|
||||
**/appsettings.Development.json
|
||||
**/appsettings*
|
||||
**/compose.*
|
||||
**/.env.example
|
||||
LICENSE
|
||||
README.md
|
||||
10
.env.example
Normal file
10
.env.example
Normal file
@@ -0,0 +1,10 @@
|
||||
DATABASE_HOST=localhost
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_USERNAME=user
|
||||
DATABASE_PASSWORD=user
|
||||
DATABASE_DATABASE=user
|
||||
OIDC_AUTHORITY=http://localhost:8092
|
||||
OIDC_AUTHORITY=http://localhost:8092
|
||||
OIDC_CLIENT_ID=client_id
|
||||
OIDC_CLIENT_SECRET=client_secret
|
||||
OIDC_REQUIRE_HTTPS_METADATA=false
|
||||
10
.gitattributes
vendored
10
.gitattributes
vendored
@@ -1,10 +0,0 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
Moonlight/wwwroot/** linguist-vendored
|
||||
Moonlight/wwwroot/assets/js/scripts.bundle.js linguist-vendored
|
||||
Moonlight/wwwroot/assets/js/widgets.bundle.js linguist-vendored
|
||||
Moonlight/wwwroot/assets/js/theme.js linguist-vendored
|
||||
Moonlight/wwwroot/assets/css/boxicons.min.css linguist-vendored
|
||||
Moonlight/wwwroot/assets/css/style.bundle.css linguist-vendored
|
||||
Moonlight/wwwroot/assets/plugins/** linguist-vendored
|
||||
Moonlight/wwwroot/assets/fonts/** linguist-vendored
|
||||
66
.gitea/workflows/publish-nuget.yml
Normal file
66
.gitea/workflows/publish-nuget.yml
Normal file
@@ -0,0 +1,66 @@
|
||||
name: "Dev Publish: Nuget"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- v2.1
|
||||
paths:
|
||||
- 'Moonlight.*/*.csproj'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish Dev Packages
|
||||
|
||||
runs-on: linux_amd64
|
||||
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Publish api server
|
||||
- name: Build project
|
||||
run: dotnet build Moonlight.Api --configuration Debug
|
||||
|
||||
- name: Publish NuGet package
|
||||
run: dotnet pack Moonlight.Api --configuration Debug --output ./artifacts
|
||||
|
||||
- name: Push nuget package
|
||||
run: dotnet nuget push ./artifacts/*.nupkg --skip-duplicate --source https://git.battlestati.one/api/packages/Moonlight-Panel/nuget/index.json --api-key ${{ secrets.ACCESS_TOKEN }}
|
||||
|
||||
- name: Remove artifacts
|
||||
run: rm -rf ./artifacts
|
||||
|
||||
# Publish frontend
|
||||
# We need to build it first so the class list files generate
|
||||
- name: Build project
|
||||
run: dotnet build Moonlight.Frontend --configuration Debug
|
||||
|
||||
- name: Build tailwind styles and extract class list
|
||||
working-directory: Hosts/Moonlight.Frontend.Host/Styles
|
||||
run: npm install && npm run build
|
||||
|
||||
- name: Build project
|
||||
run: dotnet build Moonlight.Frontend --configuration Debug
|
||||
|
||||
- name: Publish NuGet package
|
||||
run: dotnet pack Moonlight.Frontend --configuration Debug --output ./artifacts
|
||||
|
||||
- name: Push nuget package
|
||||
run: dotnet nuget push ./artifacts/*.nupkg --skip-duplicate --source https://git.battlestati.one/api/packages/Moonlight-Panel/nuget/index.json --api-key ${{ secrets.ACCESS_TOKEN }}
|
||||
|
||||
- name: Remove artifacts
|
||||
run: rm -rf ./artifacts
|
||||
|
||||
# Publish shared
|
||||
- name: Build project
|
||||
run: dotnet build Moonlight.Shared --configuration Debug
|
||||
|
||||
- name: Publish NuGet package
|
||||
run: dotnet pack Moonlight.Shared --configuration Debug --output ./artifacts
|
||||
|
||||
- name: Push nuget package
|
||||
run: dotnet nuget push ./artifacts/*.nupkg --skip-duplicate --source https://git.battlestati.one/api/packages/Moonlight-Panel/nuget/index.json --api-key ${{ secrets.ACCESS_TOKEN }}
|
||||
|
||||
- name: Remove artifacts
|
||||
run: rm -rf ./artifacts
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,2 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
ko_fi: masuowo
|
||||
96
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
96
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -1,96 +0,0 @@
|
||||
name: Bug Report
|
||||
description: Something isn't working quite right in the software.
|
||||
labels: [not confirmed]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Bug reports should only be used for reporting issues with how the software works. For assistance installing this software, as well as debugging issues with dependencies, please use our [Discord server](https://discord.gg/TJaspT7A8p).
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: Please provide a clear & concise description of the issue.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: Please describe what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Please be as detailed as possible when providing steps to reproduce, failure to provide steps will result in this issue being closed.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: panel-version
|
||||
attributes:
|
||||
label: Panel Version
|
||||
description: Version number of your Panel (latest is not a version)
|
||||
placeholder: 1.4.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: daemon-version
|
||||
attributes:
|
||||
label: Daemon Version
|
||||
description: Version number of your Daemon (latest is not a version)
|
||||
placeholder: 1.4.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: wings-version
|
||||
attributes:
|
||||
label: Wings Version
|
||||
description: Version number of your Wings (latest is not a version)
|
||||
placeholder: 1.4.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: image-details
|
||||
attributes:
|
||||
label: Games and/or Images Affected
|
||||
description: Please include the specific game(s) or Image(s) you are running into this bug with.
|
||||
placeholder: Minecraft (Paper), Minecraft (Forge)
|
||||
|
||||
- type: input
|
||||
id: docker-image
|
||||
attributes:
|
||||
label: Docker Image
|
||||
description: The specific Docker image you are using for the game(s) above.
|
||||
placeholder: ghcr.io/xxx/yolks:java_17
|
||||
|
||||
- type: textarea
|
||||
id: panel-logs
|
||||
attributes:
|
||||
label: Error Logs
|
||||
description: |
|
||||
Run the following command to collect logs on your system.
|
||||
|
||||
Panel: `docker logs moonlight`
|
||||
Wings: `sudo wings diagnostics`
|
||||
placeholder: logs here
|
||||
render: bash
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please [search here](https://github.com/Moonlight-Panel/Moonlight/issues) to see if an issue already exists for your problem.
|
||||
options:
|
||||
- label: I have searched the existing issues before opening this issue.
|
||||
required: true
|
||||
- label: I have provided all relevant details, including the specific game and Docker images I am using if this issue is related to running a server.
|
||||
required: true
|
||||
- label: I have checked in the Discord server and believe this is a bug with the software, and not a configuration issue with my specific system.
|
||||
required: true
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Installation Help
|
||||
url: https://discord.gg/TJaspT7A8p
|
||||
about: Please visit our Discord for help with your installation.
|
||||
- name: General Question
|
||||
url: https://discord.gg/TJaspT7A8p
|
||||
about: Please visit our Discord for general questions about Moonlight Panel.
|
||||
32
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
32
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Feature Request
|
||||
description: Suggest a new feature or improvement for the software.
|
||||
labels: [feature request]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing feature request for this?
|
||||
description: Please [search here](https://github.com/Moonlight-Panel/Moonlight/issues?q=is%3Aissue) to see if someone else has already suggested this.
|
||||
options:
|
||||
- label: I have searched the existing issues before opening this feature request.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the feature you would like to see.
|
||||
description: "A clear & concise description of the feature you'd like to have added, and what issues it would solve."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like.
|
||||
description: "You must explain how you'd like to see this feature implemented. Technical implementation details are not necessary, rather an idea of how you'd like to see this feature used."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context to this request.
|
||||
description: "Add any other context or screenshots about the feature request."
|
||||
validations:
|
||||
required: false
|
||||
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@@ -1,12 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# Maintain Github workflows
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/" # Must be set to / although they're located in .github/workflows
|
||||
schedule:
|
||||
interval: "daily"
|
||||
27
.github/workflows/canary-docker-build.yml
vendored
27
.github/workflows/canary-docker-build.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Canary Docker Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login into docker hub
|
||||
run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PW }}
|
||||
- name: Build and Push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Moonlight/Dockerfile
|
||||
push: true
|
||||
tags: moonlightpanel/moonlight:canary
|
||||
platforms: linux/amd64,linux/arm64
|
||||
25
.github/workflows/release-docker-build.yml
vendored
25
.github/workflows/release-docker-build.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Release Docker Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login into docker hub
|
||||
run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PW }}
|
||||
- name: Build and Push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Moonlight/Dockerfile
|
||||
push: true
|
||||
tags: moonlightpanel/moonlight:beta
|
||||
platforms: linux/amd64,linux/arm64
|
||||
18
.github/workflows/test-docker-build.yml
vendored
18
.github/workflows/test-docker-build.yml
vendored
@@ -1,18 +0,0 @@
|
||||
name: Test Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Build Docker image
|
||||
run: docker build -t moonlightpanel/moonlight:${{ github.sha }} -f Moonlight/Dockerfile .
|
||||
436
.gitignore
vendored
436
.gitignore
vendored
@@ -1,45 +1,407 @@
|
||||
Common IntelliJ Platform excludes
|
||||
|
||||
# User specific
|
||||
**/.idea/**/workspace.xml
|
||||
**/.idea/**/tasks.xml
|
||||
**/.idea/shelf/*
|
||||
**/.idea/dictionaries
|
||||
**/.idea/httpRequests/
|
||||
|
||||
# Sensitive or high-churn files
|
||||
**/.idea/**/dataSources/
|
||||
**/.idea/**/dataSources.ids
|
||||
**/.idea/**/dataSources.xml
|
||||
**/.idea/**/dataSources.local.xml
|
||||
**/.idea/**/sqlDataSources.xml
|
||||
**/.idea/**/dynamic.xml
|
||||
|
||||
# Rider
|
||||
# Rider auto-generates .iml files, and contentModel.xml
|
||||
**/.idea/**/*.iml
|
||||
**/.idea/**/contentModel.xml
|
||||
**/.idea/**/modules.xml
|
||||
|
||||
|
||||
Moonlight/[Bb]in/
|
||||
Moonlight/[Oo]bj/
|
||||
Moonlight/_UpgradeReport_Files/
|
||||
Moonlight/[Pp]ackages/
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
.vs/
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
[Pp]ackages/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
.DS_Store
|
||||
.idea/.idea.Moonlight/.idea/discord.xml
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
storage/
|
||||
Moonlight/publish.ps1
|
||||
Moonlight/version
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
**/.idea/**
|
||||
|
||||
# Style builds
|
||||
**/style.min.css
|
||||
**/package-lock.json
|
||||
|
||||
# Secrets
|
||||
**/.env
|
||||
**/appsettings.json
|
||||
**/appsettings.Development.json
|
||||
13
.idea/.idea.Moonlight/.idea/.gitignore
generated
vendored
13
.idea/.idea.Moonlight/.idea/.gitignore
generated
vendored
@@ -1,13 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/contentModel.xml
|
||||
/projectSettingsUpdater.xml
|
||||
/modules.xml
|
||||
/.idea.Moonlight.iml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
21
.idea/.idea.Moonlight/.idea/efCoreCommonOptions.xml
generated
21
.idea/.idea.Moonlight/.idea/efCoreCommonOptions.xml
generated
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EfCoreCommonOptions">
|
||||
<option name="migrationsToStartupProjects">
|
||||
<map>
|
||||
<entry key="16141d00-a997-4ba6-b0dc-af6f4712613a" value="16141d00-a997-4ba6-b0dc-af6f4712613a" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="solutionLevelOptions">
|
||||
<map>
|
||||
<entry key="migrationsProject" value="16141d00-a997-4ba6-b0dc-af6f4712613a" />
|
||||
<entry key="startupProject" value="16141d00-a997-4ba6-b0dc-af6f4712613a" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="startupToMigrationsProjects">
|
||||
<map>
|
||||
<entry key="16141d00-a997-4ba6-b0dc-af6f4712613a" value="16141d00-a997-4ba6-b0dc-af6f4712613a" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
14
.idea/.idea.Moonlight/.idea/efCoreDialogsState.xml
generated
14
.idea/.idea.Moonlight/.idea/efCoreDialogsState.xml
generated
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EfCoreDialogsState">
|
||||
<option name="keyValueStorage">
|
||||
<map>
|
||||
<entry key="Common:16141d00-a997-4ba6-b0dc-af6f4712613a:dbContext" value="Moonlight.App.Database.DataContext" />
|
||||
<entry key="Common:buildConfiguration" value="Debug" />
|
||||
<entry key="Common:noBuild" value="false" />
|
||||
<entry key="Common:outputFolder" value="App/Database/Migrations" />
|
||||
<entry key="Common:useDefaultConnection" value="true" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
4
.idea/.idea.Moonlight/.idea/encodings.xml
generated
4
.idea/.idea.Moonlight/.idea/encodings.xml
generated
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
||||
8
.idea/.idea.Moonlight/.idea/indexLayout.xml
generated
8
.idea/.idea.Moonlight/.idea/indexLayout.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/.idea.Moonlight/.idea/vcs.xml
generated
6
.idea/.idea.Moonlight/.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,3 +0,0 @@
|
||||
<values>
|
||||
<value name="mac.address" type="string">a6a05ab0b1614c08281b54fc3b3339170b0f57a5e246c20b7393333dfa28f8f1</value>
|
||||
</values>
|
||||
@@ -1,3 +0,0 @@
|
||||
<values>
|
||||
<value name="StillAlive" type="qword">133564417297189136</value>
|
||||
</values>
|
||||
@@ -1,3 +0,0 @@
|
||||
<values>
|
||||
<value name="MachineId" type="string">{A6A05AB0-B161-4C08-281B-54FC3B333917}</value>
|
||||
</values>
|
||||
35
.vscode/launch.json
vendored
35
.vscode/launch.json
vendored
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md.
|
||||
"name": ".NET Core Launch (web)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/Moonlight/bin/Debug/net6.0/Moonlight.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/Moonlight",
|
||||
"stopAtEntry": false,
|
||||
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
|
||||
"serverReadyAction": {
|
||||
"action": "openExternally",
|
||||
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
|
||||
},
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
||||
41
.vscode/tasks.json
vendored
41
.vscode/tasks.json
vendored
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/Moonlight.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/Moonlight.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/Moonlight.sln"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
Hosts/Moonlight.Api.Host/AppStartupLoader.cs
Normal file
10
Hosts/Moonlight.Api.Host/AppStartupLoader.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using MoonCore.PluginFramework;
|
||||
using Moonlight.Api.Startup;
|
||||
|
||||
namespace Moonlight.Api.Host;
|
||||
|
||||
[PluginLoader]
|
||||
public partial class AppStartupLoader : IAppStartup
|
||||
{
|
||||
|
||||
}
|
||||
70
Hosts/Moonlight.Api.Host/Dockerfile
Normal file
70
Hosts/Moonlight.Api.Host/Dockerfile
Normal file
@@ -0,0 +1,70 @@
|
||||
# Base image
|
||||
FROM cgr.dev/chainguard/aspnet-runtime:latest AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
|
||||
# Build dependencies
|
||||
RUN apt-get update; apt-get install nodejs npm -y; apt-get clean
|
||||
|
||||
# Build options
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
|
||||
# Download npm packages
|
||||
WORKDIR /src/Hosts/Moonlight.Frontend.Host/Styles
|
||||
|
||||
COPY ["Hosts/Moonlight.Frontend.Host/Styles/package.json", "package.json"]
|
||||
|
||||
RUN npm install
|
||||
|
||||
# Restore nuget packages
|
||||
WORKDIR /src
|
||||
|
||||
COPY ["Moonlight.Api/Moonlight.Api.csproj", "Moonlight.Api/"]
|
||||
COPY ["Moonlight.Frontend/Moonlight.Frontend.csproj", "Moonlight.Frontend/"]
|
||||
COPY ["Moonlight.Shared/Moonlight.Shared.csproj", "Moonlight.Shared/"]
|
||||
|
||||
COPY ["Hosts/Moonlight.Frontend.Host/Moonlight.Frontend.Host.csproj", "Hosts/Moonlight.Frontend.Host/"]
|
||||
COPY ["Hosts/Moonlight.Api.Host/Moonlight.Api.Host.csproj", "Hosts/Moonlight.Api.Host/"]
|
||||
|
||||
RUN dotnet restore "Hosts/Moonlight.Api.Host/Moonlight.Api.Host.csproj"
|
||||
RUN dotnet restore "Hosts/Moonlight.Frontend.Host/Moonlight.Frontend.Host.csproj"
|
||||
|
||||
# Copy over the whole sources
|
||||
COPY . .
|
||||
|
||||
# Build styles
|
||||
# We need to build it before, so the class lists get generated
|
||||
WORKDIR "/src/Hosts/Moonlight.Frontend.Host"
|
||||
RUN dotnet build "./Moonlight.Frontend.Host.csproj" -c $BUILD_CONFIGURATION -o /app/build-frontend
|
||||
|
||||
WORKDIR "/src/Hosts/Moonlight.Frontend.Host/Styles"
|
||||
RUN npm run build
|
||||
|
||||
# Build projects
|
||||
WORKDIR "/src/Hosts/Moonlight.Api.Host"
|
||||
RUN dotnet build "./Moonlight.Api.Host.csproj" -c $BUILD_CONFIGURATION -o /app/build-api
|
||||
|
||||
WORKDIR "/src/Hosts/Moonlight.Frontend.Host"
|
||||
RUN dotnet build "./Moonlight.Frontend.Host.csproj" -c $BUILD_CONFIGURATION -o /app/build-frontend
|
||||
|
||||
FROM build AS publish
|
||||
|
||||
# Publish options
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
|
||||
# Publish applications
|
||||
WORKDIR "/src/Hosts/Moonlight.Api.Host"
|
||||
RUN dotnet publish "./Moonlight.Api.Host.csproj" -c $BUILD_CONFIGURATION -o /app/publish-api /p:UseAppHost=false
|
||||
|
||||
WORKDIR "/src/Hosts/Moonlight.Frontend.Host"
|
||||
RUN dotnet publish "./Moonlight.Frontend.Host.csproj" -c $BUILD_CONFIGURATION -o /app/publish-frontend /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=publish /app/publish-api .
|
||||
COPY --from=publish /app/publish-frontend/wwwroot ./wwwroot
|
||||
|
||||
ENTRYPOINT ["dotnet", "Moonlight.Api.Host.dll"]
|
||||
32
Hosts/Moonlight.Api.Host/Moonlight.Api.Host.csproj
Normal file
32
Hosts/Moonlight.Api.Host/Moonlight.Api.Host.csproj
Normal file
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.9"/>
|
||||
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.3"/>
|
||||
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1"/>
|
||||
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Moonlight.Api\Moonlight.Api.csproj"/>
|
||||
<ProjectReference Include="..\Moonlight.Frontend.Host\Moonlight.Frontend.Host.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\.dockerignore">
|
||||
<Link>.dockerignore</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
22
Hosts/Moonlight.Api.Host/Program.cs
Normal file
22
Hosts/Moonlight.Api.Host/Program.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Moonlight.Api.Host;
|
||||
|
||||
var appLoader = new AppStartupLoader();
|
||||
appLoader.Initialize();
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
appLoader.PreBuild(builder);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
appLoader.PostBuild(app);
|
||||
|
||||
appLoader.PostMiddleware(app);
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
app.UseWebAssemblyDebugging();
|
||||
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
await app.RunAsync();
|
||||
14
Hosts/Moonlight.Api.Host/Properties/launchSettings.json
Normal file
14
Hosts/Moonlight.Api.Host/Properties/launchSettings.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5240",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Hosts/Moonlight.Frontend.Host/AppStartupLoader.cs
Normal file
10
Hosts/Moonlight.Frontend.Host/AppStartupLoader.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using MoonCore.PluginFramework;
|
||||
using Moonlight.Frontend.Startup;
|
||||
|
||||
namespace Moonlight.Frontend.Host;
|
||||
|
||||
[PluginLoader]
|
||||
public partial class AppStartupLoader : IAppStartup
|
||||
{
|
||||
|
||||
}
|
||||
24
Hosts/Moonlight.Frontend.Host/Moonlight.Frontend.Host.csproj
Normal file
24
Hosts/Moonlight.Frontend.Host/Moonlight.Frontend.Host.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<StaticWebAssetsEnabled>True</StaticWebAssetsEnabled>
|
||||
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||
<WasmEnableSIMD>true</WasmEnableSIMD>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.1" PrivateAssets="all"/>
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.9"/>
|
||||
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.3"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Moonlight.Frontend\Moonlight.Frontend.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
15
Hosts/Moonlight.Frontend.Host/Program.cs
Normal file
15
Hosts/Moonlight.Frontend.Host/Program.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Moonlight.Frontend.Host;
|
||||
|
||||
var appLoader = new AppStartupLoader();
|
||||
appLoader.Initialize();
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
|
||||
appLoader.PreBuild(builder);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
appLoader.PostBuild(app);
|
||||
|
||||
await app.RunAsync();
|
||||
15
Hosts/Moonlight.Frontend.Host/Properties/launchSettings.json
Normal file
15
Hosts/Moonlight.Frontend.Host/Properties/launchSettings.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "http://localhost:5250",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Hosts/Moonlight.Frontend.Host/Styles/extract-classes.mjs
Normal file
25
Hosts/Moonlight.Frontend.Host/Styles/extract-classes.mjs
Normal file
@@ -0,0 +1,25 @@
|
||||
import fs from 'fs';
|
||||
import selectorParser from 'postcss-selector-parser';
|
||||
|
||||
export default function extractTailwindClasses(opts = {}) {
|
||||
const classSet = new Set();
|
||||
|
||||
return {
|
||||
postcssPlugin: 'extract-tailwind-classes',
|
||||
Rule(rule) {
|
||||
selectorParser(selectors => {
|
||||
selectors.walkClasses(node => {
|
||||
classSet.add(node.value);
|
||||
});
|
||||
}).processSync(rule.selector);
|
||||
},
|
||||
OnceExit() {
|
||||
const classArray = Array.from(classSet).sort();
|
||||
fs.mkdirSync('../../../Moonlight.Frontend/Styles', { recursive: true });
|
||||
fs.writeFileSync('../../../Moonlight.Frontend/Styles/Moonlight.Frontend.map', classArray.join('\n'));
|
||||
console.log(`Extracted classes ${classArray.length}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
extractTailwindClasses.postcss = true;
|
||||
19
Hosts/Moonlight.Frontend.Host/Styles/package.json
Normal file
19
Hosts/Moonlight.Frontend.Host/Styles/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "npx postcss styles.css -o ../wwwroot/style.min.css --watch",
|
||||
"dev-build": "npx postcss styles.css -o ../wwwroot/style.min.css",
|
||||
"build": "cross-env EXTRACT_CLASSES=true npx postcss styles.css -o ../wwwroot/style.min.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"cssnano": "^7.1.2",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-cli": "^11.0.1",
|
||||
"postcss-selector-parser": "^7.1.1",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^10.1.0"
|
||||
}
|
||||
}
|
||||
18
Hosts/Moonlight.Frontend.Host/Styles/postcss.config.mjs
Normal file
18
Hosts/Moonlight.Frontend.Host/Styles/postcss.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import tailwindcss from '@tailwindcss/postcss';
|
||||
import cssnano from 'cssnano';
|
||||
import extractTailwindClasses from './extract-classes.mjs';
|
||||
|
||||
const plugins = [
|
||||
tailwindcss,
|
||||
cssnano({
|
||||
preset: 'default'
|
||||
})
|
||||
];
|
||||
|
||||
if (process.env.EXTRACT_CLASSES === "true") {
|
||||
plugins.push(extractTailwindClasses());
|
||||
}
|
||||
|
||||
export default {
|
||||
plugins
|
||||
};
|
||||
31
Hosts/Moonlight.Frontend.Host/Styles/styles.css
Normal file
31
Hosts/Moonlight.Frontend.Host/Styles/styles.css
Normal file
@@ -0,0 +1,31 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@import "../../../Moonlight.Frontend/bin/ShadcnBlazor/scrollbar.css";
|
||||
@import "../../../Moonlight.Frontend/bin/ShadcnBlazor/default-theme.css";
|
||||
@import "./theme.css";
|
||||
|
||||
@source "../../../Moonlight.Frontend/bin/ShadcnBlazor/ShadcnBlazor.map";
|
||||
|
||||
@source "../../../Moonlight.Api/**/*.razor";
|
||||
@source "../../../Moonlight.Api/**/*.cs";
|
||||
@source "../../../Moonlight.Api/**/*.html";
|
||||
|
||||
@source "../../../Moonlight.Frontend/**/*.razor";
|
||||
@source "../../../Moonlight.Frontend/**/*.cs";
|
||||
@source "../../../Moonlight.Frontend/**/*.html";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
#blazor-error-ui {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
132
Hosts/Moonlight.Frontend.Host/Styles/theme.css
Normal file
132
Hosts/Moonlight.Frontend.Host/Styles/theme.css
Normal file
@@ -0,0 +1,132 @@
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.129 0.042 264.695);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.129 0.042 264.695);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.129 0.042 264.695);
|
||||
--primary: oklch(0.208 0.042 265.755);
|
||||
--primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--secondary: oklch(0.968 0.007 247.896);
|
||||
--secondary-foreground: oklch(0.208 0.042 265.755);
|
||||
--muted: oklch(0.968 0.007 247.896);
|
||||
--muted-foreground: oklch(0.554 0.046 257.417);
|
||||
--accent: oklch(0.968 0.007 247.896);
|
||||
--accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.929 0.013 255.508);
|
||||
--input: oklch(0.929 0.013 255.508);
|
||||
--ring: oklch(0.704 0.04 256.788);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
|
||||
--sidebar: var(--background);
|
||||
--sidebar-foreground: var(--foreground);
|
||||
--sidebar-primary: var(--primary);
|
||||
--sidebar-primary-foreground: var(--primary-foreground);
|
||||
--sidebar-accent: var(--accent);
|
||||
--sidebar-accent-foreground: var(--accent-foreground);
|
||||
--sidebar-border: var(--border);
|
||||
--sidebar-ring: var(--ring);
|
||||
|
||||
--font-sans: Inter, sans-serif;
|
||||
--font-serif: Georgia, serif;
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Deep blue-slate background with purple undertones */
|
||||
--background: oklch(0.16 0.028 260);
|
||||
--foreground: oklch(0.98 0.008 260);
|
||||
|
||||
/* Cards with slightly lighter blue-slate */
|
||||
--card: oklch(0.21 0.032 260);
|
||||
--card-foreground: oklch(0.98 0.008 260);
|
||||
|
||||
/* Popovers with medium depth */
|
||||
--popover: oklch(0.24 0.035 260);
|
||||
--popover-foreground: oklch(0.98 0.008 260);
|
||||
|
||||
/* Vibrant blue-purple primary */
|
||||
--primary: oklch(0.62 0.18 270);
|
||||
--primary-foreground: oklch(0.99 0.005 260);
|
||||
|
||||
/* Secondary with blue-slate tone */
|
||||
--secondary: oklch(0.27 0.038 260);
|
||||
--secondary-foreground: oklch(0.98 0.008 260);
|
||||
|
||||
/* Muted elements */
|
||||
--muted: oklch(0.25 0.035 260);
|
||||
--muted-foreground: oklch(0.66 0.025 260);
|
||||
|
||||
/* Accent with purple-blue blend */
|
||||
--accent: oklch(0.36 0.065 268);
|
||||
--accent-foreground: oklch(0.98 0.008 260);
|
||||
|
||||
/* Destructive red with good contrast */
|
||||
--destructive: oklch(0.62 0.22 25);
|
||||
--destructive-foreground: oklch(0.99 0.005 260);
|
||||
|
||||
/* Subtle borders and inputs */
|
||||
--border: oklch(0.32 0.025 260);
|
||||
--input: oklch(0.30 0.030 260);
|
||||
--ring: oklch(0.62 0.18 270);
|
||||
|
||||
/* Chart colors with blue-purple harmony */
|
||||
--chart-1: oklch(0.58 0.18 270);
|
||||
--chart-2: oklch(0.62 0.16 245);
|
||||
--chart-3: oklch(0.68 0.15 290);
|
||||
--chart-4: oklch(0.60 0.20 260);
|
||||
--chart-5: oklch(0.65 0.14 280);
|
||||
|
||||
/* Sidebar with slightly different depth */
|
||||
--sidebar: oklch(0.18 0.030 260);
|
||||
--sidebar-foreground: oklch(0.97 0.008 260);
|
||||
--sidebar-primary: oklch(0.60 0.17 270);
|
||||
--sidebar-primary-foreground: oklch(0.99 0.005 260);
|
||||
--sidebar-accent: oklch(0.26 0.038 260);
|
||||
--sidebar-accent-foreground: oklch(0.98 0.008 260);
|
||||
--sidebar-border: oklch(0.30 0.025 260);
|
||||
--sidebar-ring: oklch(0.58 0.17 270);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
108
Hosts/Moonlight.Frontend.Host/wwwroot/index.html
Normal file
108
Hosts/Moonlight.Frontend.Host/wwwroot/index.html
Normal file
@@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Moonlight</title>
|
||||
<base href="/" />
|
||||
<link rel="preload" id="webassembly" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=fallback" />
|
||||
<link rel="stylesheet" href="style.min.css" />
|
||||
<script type="importmap"></script>
|
||||
|
||||
<script>
|
||||
window.frontendConfig = {
|
||||
STYLE_TAG_ID: 'theme-variables',
|
||||
configuration: {},
|
||||
|
||||
applyTheme: function(cssContent) {
|
||||
// Find or create the style tag
|
||||
let styleTag = document.getElementById(this.STYLE_TAG_ID);
|
||||
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style');
|
||||
styleTag.id = this.STYLE_TAG_ID;
|
||||
document.head.appendChild(styleTag);
|
||||
}
|
||||
|
||||
// Update the style tag content
|
||||
styleTag.textContent = cssContent;
|
||||
},
|
||||
|
||||
reloadConfiguration: function (){
|
||||
try {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/api/frontend/config', false);
|
||||
xhr.send(null);
|
||||
|
||||
if (xhr.status === 200) {
|
||||
this.configuration = JSON.parse(xhr.responseText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load initial theme:', error);
|
||||
}
|
||||
},
|
||||
|
||||
getConfiguration: function (){
|
||||
return this.configuration;
|
||||
},
|
||||
|
||||
reload: function () {
|
||||
this.reloadConfiguration();
|
||||
|
||||
document.title = this.configuration.name;
|
||||
this.applyTheme(this.configuration.themeCss);
|
||||
}
|
||||
};
|
||||
|
||||
window.frontendConfig.reload();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="bg-background text-foreground">
|
||||
<div id="app">
|
||||
<div class="h-screen w-full flex items-center justify-center">
|
||||
|
||||
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-3 rounded-lg border-dashed p-6 text-center text-balance md:p-12">
|
||||
<div class="flex max-w-sm flex-col items-center gap-2 text-center">
|
||||
<div class="flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0 bg-muted text-foreground size-10 rounded-lg [&_svg:not([class*='size-'])]:size-6">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-zap-icon lucide-zap size-6">
|
||||
<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-lg font-medium tracking-tight">
|
||||
Loading application
|
||||
</div>
|
||||
<div class="flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance">
|
||||
|
||||
<div class="bg-primary/20 w-full relative h-2 overflow-hidden rounded-full">
|
||||
<div class="bg-primary h-full w-[var(--blazor-load-percentage,0)] flex-1 transition-all">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
|
||||
<script src="/_content/ShadcnBlazor/interop.js" defer></script>
|
||||
<script src="/_content/ShadcnBlazor.Extras/interop.js" defer></script>
|
||||
<script src="/_content/ShadcnBlazor.Extras/codemirror-bundle.js" defer></script>
|
||||
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
121
LICENSE
121
LICENSE
@@ -1,121 +0,0 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
6
Moonlight.Api/Configuration/ApiOptions.cs
Normal file
6
Moonlight.Api/Configuration/ApiOptions.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Moonlight.Api.Configuration;
|
||||
|
||||
public class ApiOptions
|
||||
{
|
||||
public int LookupCacheMinutes { get; set; } = 3;
|
||||
}
|
||||
7
Moonlight.Api/Configuration/ContainerHelperOptions.cs
Normal file
7
Moonlight.Api/Configuration/ContainerHelperOptions.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Moonlight.Api.Configuration;
|
||||
|
||||
public class ContainerHelperOptions
|
||||
{
|
||||
public bool IsEnabled { get; set; }
|
||||
public string Url { get; set; } = "http://helper:8080";
|
||||
}
|
||||
10
Moonlight.Api/Configuration/DatabaseOptions.cs
Normal file
10
Moonlight.Api/Configuration/DatabaseOptions.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Moonlight.Api.Configuration;
|
||||
|
||||
public class DatabaseOptions
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; } = 5432;
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Database { get; set; }
|
||||
}
|
||||
7
Moonlight.Api/Configuration/FrontendOptions.cs
Normal file
7
Moonlight.Api/Configuration/FrontendOptions.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Moonlight.Api.Configuration;
|
||||
|
||||
public class FrontendOptions
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
public int CacheMinutes { get; set; } = 3;
|
||||
}
|
||||
11
Moonlight.Api/Configuration/OidcOptions.cs
Normal file
11
Moonlight.Api/Configuration/OidcOptions.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Moonlight.Api.Configuration;
|
||||
|
||||
public class OidcOptions
|
||||
{
|
||||
public string Authority { get; set; }
|
||||
public bool RequireHttpsMetadata { get; set; } = true;
|
||||
public string ResponseType { get; set; } = "code";
|
||||
public string[]? Scopes { get; set; }
|
||||
public string ClientId { get; set; }
|
||||
public string ClientSecret { get; set; }
|
||||
}
|
||||
6
Moonlight.Api/Configuration/SessionOptions.cs
Normal file
6
Moonlight.Api/Configuration/SessionOptions.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Moonlight.Api.Configuration;
|
||||
|
||||
public class SessionOptions
|
||||
{
|
||||
public int ValidationCacheMinutes { get; set; } = 3;
|
||||
}
|
||||
6
Moonlight.Api/Configuration/SettingsOptions.cs
Normal file
6
Moonlight.Api/Configuration/SettingsOptions.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Moonlight.Api.Configuration;
|
||||
|
||||
public class SettingsOptions
|
||||
{
|
||||
public int CacheMinutes { get; set; } = 3;
|
||||
}
|
||||
7
Moonlight.Api/Configuration/VersionOptions.cs
Normal file
7
Moonlight.Api/Configuration/VersionOptions.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Moonlight.Api.Configuration;
|
||||
|
||||
public class VersionOptions
|
||||
{
|
||||
public bool OfflineMode { get; set; }
|
||||
public string? CurrentOverride { get; set; }
|
||||
}
|
||||
44
Moonlight.Api/Database/DataContext.cs
Normal file
44
Moonlight.Api/Database/DataContext.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moonlight.Api.Configuration;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
|
||||
namespace Moonlight.Api.Database;
|
||||
|
||||
public class DataContext : DbContext
|
||||
{
|
||||
public DbSet<User> Users { get; set; }
|
||||
public DbSet<SettingsOption> SettingsOptions { get; set; }
|
||||
public DbSet<Role> Roles { get; set; }
|
||||
public DbSet<RoleMember> RoleMembers { get; set; }
|
||||
public DbSet<ApiKey> ApiKeys { get; set; }
|
||||
public DbSet<Theme> Themes { get; set; }
|
||||
|
||||
private readonly IOptions<DatabaseOptions> Options;
|
||||
|
||||
public DataContext(IOptions<DatabaseOptions> options)
|
||||
{
|
||||
Options = options;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
if (optionsBuilder.IsConfigured)
|
||||
return;
|
||||
|
||||
optionsBuilder.UseNpgsql(
|
||||
$"Host={Options.Value.Host};" +
|
||||
$"Port={Options.Value.Port};" +
|
||||
$"Username={Options.Value.Username};" +
|
||||
$"Password={Options.Value.Password};" +
|
||||
$"Database={Options.Value.Database}"
|
||||
);
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasDefaultSchema("core");
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
46
Moonlight.Api/Database/DatabaseRepository.cs
Normal file
46
Moonlight.Api/Database/DatabaseRepository.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Api.Database.Interfaces;
|
||||
|
||||
namespace Moonlight.Api.Database;
|
||||
|
||||
public class DatabaseRepository<T> where T : class
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
private readonly DbSet<T> Set;
|
||||
|
||||
public DatabaseRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
Set = DataContext.Set<T>();
|
||||
}
|
||||
|
||||
public IQueryable<T> Query() => Set;
|
||||
|
||||
public async Task<T> AddAsync(T entity)
|
||||
{
|
||||
if (entity is IActionTimestamps actionTimestamps)
|
||||
{
|
||||
actionTimestamps.CreatedAt = DateTimeOffset.UtcNow;
|
||||
actionTimestamps.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
var final = Set.Add(entity);
|
||||
await DataContext.SaveChangesAsync();
|
||||
return final.Entity;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(T entity)
|
||||
{
|
||||
if (entity is IActionTimestamps actionTimestamps)
|
||||
actionTimestamps.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
|
||||
Set.Update(entity);
|
||||
await DataContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(T entity)
|
||||
{
|
||||
Set.Remove(entity);
|
||||
await DataContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
24
Moonlight.Api/Database/Entities/ApiKey.cs
Normal file
24
Moonlight.Api/Database/Entities/ApiKey.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Moonlight.Api.Database.Interfaces;
|
||||
|
||||
namespace Moonlight.Api.Database.Entities;
|
||||
|
||||
public class ApiKey : IActionTimestamps
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public required string Name { get; set; }
|
||||
|
||||
[MaxLength(300)]
|
||||
public required string Description { get; set; }
|
||||
|
||||
public string[] Permissions { get; set; } = [];
|
||||
|
||||
[MaxLength(32)]
|
||||
public string Key { get; set; }
|
||||
|
||||
// Action timestamps
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
}
|
||||
24
Moonlight.Api/Database/Entities/Role.cs
Normal file
24
Moonlight.Api/Database/Entities/Role.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Moonlight.Api.Database.Interfaces;
|
||||
|
||||
namespace Moonlight.Api.Database.Entities;
|
||||
|
||||
public class Role : IActionTimestamps
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public required string Name { get; set; }
|
||||
|
||||
[MaxLength(300)]
|
||||
public required string Description { get; set; }
|
||||
|
||||
public string[] Permissions { get; set; } = [];
|
||||
|
||||
// Relations
|
||||
public List<RoleMember> Members { get; set; } = [];
|
||||
|
||||
// Action timestamps
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
}
|
||||
15
Moonlight.Api/Database/Entities/RoleMember.cs
Normal file
15
Moonlight.Api/Database/Entities/RoleMember.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Moonlight.Api.Database.Interfaces;
|
||||
|
||||
namespace Moonlight.Api.Database.Entities;
|
||||
|
||||
public class RoleMember : IActionTimestamps
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public Role Role { get; set; }
|
||||
public User User { get; set; }
|
||||
|
||||
// Action timestamps
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
}
|
||||
16
Moonlight.Api/Database/Entities/SettingsOption.cs
Normal file
16
Moonlight.Api/Database/Entities/SettingsOption.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Moonlight.Api.Database.Entities;
|
||||
|
||||
public class SettingsOption
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(256)]
|
||||
public required string Key { get; set; }
|
||||
|
||||
[MaxLength(4096)]
|
||||
[Column(TypeName = "jsonb")]
|
||||
public required string ValueJson { get; set; }
|
||||
}
|
||||
21
Moonlight.Api/Database/Entities/Theme.cs
Normal file
21
Moonlight.Api/Database/Entities/Theme.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.Api.Database.Entities;
|
||||
|
||||
public class Theme
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public required string Name { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public required string Version { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public required string Author { get; set; }
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
[MaxLength(20_000)]
|
||||
public required string CssContent { get; set; }
|
||||
}
|
||||
26
Moonlight.Api/Database/Entities/User.cs
Normal file
26
Moonlight.Api/Database/Entities/User.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Moonlight.Api.Database.Interfaces;
|
||||
|
||||
namespace Moonlight.Api.Database.Entities;
|
||||
|
||||
public class User : IActionTimestamps
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
// Base information
|
||||
[MaxLength(50)]
|
||||
public required string Username { get; set; }
|
||||
|
||||
[MaxLength(254)]
|
||||
public required string Email { get; set; }
|
||||
|
||||
// Authentication
|
||||
public DateTimeOffset InvalidateTimestamp { get; set; }
|
||||
|
||||
// Relations
|
||||
public List<RoleMember> RoleMemberships { get; set; } = [];
|
||||
|
||||
// Action timestamps
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
}
|
||||
7
Moonlight.Api/Database/Interfaces/IActionTimestamps.cs
Normal file
7
Moonlight.Api/Database/Interfaces/IActionTimestamps.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Moonlight.Api.Database.Interfaces;
|
||||
|
||||
internal interface IActionTimestamps
|
||||
{
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
}
|
||||
80
Moonlight.Api/Database/Migrations/20251225202335_AddedUsersAndSettings.Designer.cs
generated
Normal file
80
Moonlight.Api/Database/Migrations/20251225202335_AddedUsersAndSettings.Designer.cs
generated
Normal file
@@ -0,0 +1,80 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Moonlight.Api.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20251225202335_AddedUsersAndSettings")]
|
||||
partial class AddedUsersAndSettings
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("core")
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.SettingsOption", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SettingsOptions", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(254)
|
||||
.HasColumnType("character varying(254)");
|
||||
|
||||
b.Property<DateTimeOffset>("InvalidateTimestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users", "core");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedUsersAndSettings : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "core");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SettingsOptions",
|
||||
schema: "core",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Key = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
Value = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SettingsOptions", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
schema: "core",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Username = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
Email = table.Column<string>(type: "character varying(254)", maxLength: 254, nullable: false),
|
||||
InvalidateTimestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "SettingsOptions",
|
||||
schema: "core");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users",
|
||||
schema: "core");
|
||||
}
|
||||
}
|
||||
}
|
||||
177
Moonlight.Api/Database/Migrations/20251230200748_AddedRolesAndActionTimestamps.Designer.cs
generated
Normal file
177
Moonlight.Api/Database/Migrations/20251230200748_AddedRolesAndActionTimestamps.Designer.cs
generated
Normal file
@@ -0,0 +1,177 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Moonlight.Api.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20251230200748_AddedRolesAndActionTimestamps")]
|
||||
partial class AddedRolesAndActionTimestamps
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("core")
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(15)
|
||||
.HasColumnType("character varying(15)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Roles", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("RoleMembers", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.SettingsOption", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SettingsOptions", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(254)
|
||||
.HasColumnType("character varying(254)");
|
||||
|
||||
b.Property<DateTimeOffset>("InvalidateTimestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.Api.Database.Entities.Role", "Role")
|
||||
.WithMany("Members")
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.Api.Database.Entities.User", "User")
|
||||
.WithMany("RoleMemberships")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Role");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Navigation("Members");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Navigation("RoleMemberships");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedRolesAndActionTimestamps : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "CreatedAt",
|
||||
schema: "core",
|
||||
table: "Users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
|
||||
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "UpdatedAt",
|
||||
schema: "core",
|
||||
table: "Users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Roles",
|
||||
schema: "core",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(type: "character varying(15)", maxLength: 15, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
Permissions = table.Column<string[]>(type: "text[]", nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Roles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RoleMembers",
|
||||
schema: "core",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
RoleId = table.Column<int>(type: "integer", nullable: false),
|
||||
UserId = table.Column<int>(type: "integer", nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RoleMembers", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RoleMembers_Roles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalSchema: "core",
|
||||
principalTable: "Roles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RoleMembers_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalSchema: "core",
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RoleMembers_RoleId",
|
||||
schema: "core",
|
||||
table: "RoleMembers",
|
||||
column: "RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RoleMembers_UserId",
|
||||
schema: "core",
|
||||
table: "RoleMembers",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "RoleMembers",
|
||||
schema: "core");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Roles",
|
||||
schema: "core");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedAt",
|
||||
schema: "core",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UpdatedAt",
|
||||
schema: "core",
|
||||
table: "Users");
|
||||
}
|
||||
}
|
||||
}
|
||||
215
Moonlight.Api/Database/Migrations/20260116133404_AddedApiKeys.Designer.cs
generated
Normal file
215
Moonlight.Api/Database/Migrations/20260116133404_AddedApiKeys.Designer.cs
generated
Normal file
@@ -0,0 +1,215 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Moonlight.Api.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20260116133404_AddedApiKeys")]
|
||||
partial class AddedApiKeys
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("core")
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.ApiKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(15)
|
||||
.HasColumnType("character varying(15)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ApiKeys", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(15)
|
||||
.HasColumnType("character varying(15)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Roles", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("RoleMembers", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.SettingsOption", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SettingsOptions", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(254)
|
||||
.HasColumnType("character varying(254)");
|
||||
|
||||
b.Property<DateTimeOffset>("InvalidateTimestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.Api.Database.Entities.Role", "Role")
|
||||
.WithMany("Members")
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.Api.Database.Entities.User", "User")
|
||||
.WithMany("RoleMemberships")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Role");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Navigation("Members");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Navigation("RoleMemberships");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedApiKeys : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApiKeys",
|
||||
schema: "core",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(type: "character varying(15)", maxLength: 15, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
Permissions = table.Column<string[]>(type: "text[]", nullable: false),
|
||||
Key = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ApiKeys", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApiKeys",
|
||||
schema: "core");
|
||||
}
|
||||
}
|
||||
}
|
||||
215
Moonlight.Api/Database/Migrations/20260116134322_AdjustedLenghtsOfRoleAndApiKeyStrings.Designer.cs
generated
Normal file
215
Moonlight.Api/Database/Migrations/20260116134322_AdjustedLenghtsOfRoleAndApiKeyStrings.Designer.cs
generated
Normal file
@@ -0,0 +1,215 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Moonlight.Api.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20260116134322_AdjustedLenghtsOfRoleAndApiKeyStrings")]
|
||||
partial class AdjustedLenghtsOfRoleAndApiKeyStrings
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("core")
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.ApiKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("character varying(300)");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ApiKeys", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("character varying(300)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Roles", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("RoleMembers", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.SettingsOption", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SettingsOptions", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(254)
|
||||
.HasColumnType("character varying(254)");
|
||||
|
||||
b.Property<DateTimeOffset>("InvalidateTimestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.Api.Database.Entities.Role", "Role")
|
||||
.WithMany("Members")
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.Api.Database.Entities.User", "User")
|
||||
.WithMany("RoleMemberships")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Role");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Navigation("Members");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Navigation("RoleMemberships");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AdjustedLenghtsOfRoleAndApiKeyStrings : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
schema: "core",
|
||||
table: "Roles",
|
||||
type: "character varying(30)",
|
||||
maxLength: 30,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(15)",
|
||||
oldMaxLength: 15);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
schema: "core",
|
||||
table: "Roles",
|
||||
type: "character varying(300)",
|
||||
maxLength: 300,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(100)",
|
||||
oldMaxLength: 100);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
schema: "core",
|
||||
table: "ApiKeys",
|
||||
type: "character varying(30)",
|
||||
maxLength: 30,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(15)",
|
||||
oldMaxLength: 15);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
schema: "core",
|
||||
table: "ApiKeys",
|
||||
type: "character varying(300)",
|
||||
maxLength: 300,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(100)",
|
||||
oldMaxLength: 100);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
schema: "core",
|
||||
table: "Roles",
|
||||
type: "character varying(15)",
|
||||
maxLength: 15,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(30)",
|
||||
oldMaxLength: 30);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
schema: "core",
|
||||
table: "Roles",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(300)",
|
||||
oldMaxLength: 300);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
schema: "core",
|
||||
table: "ApiKeys",
|
||||
type: "character varying(15)",
|
||||
maxLength: 15,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(30)",
|
||||
oldMaxLength: 30);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
schema: "core",
|
||||
table: "ApiKeys",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(300)",
|
||||
oldMaxLength: 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
251
Moonlight.Api/Database/Migrations/20260118005634_AddedThemes.Designer.cs
generated
Normal file
251
Moonlight.Api/Database/Migrations/20260118005634_AddedThemes.Designer.cs
generated
Normal file
@@ -0,0 +1,251 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Moonlight.Api.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20260118005634_AddedThemes")]
|
||||
partial class AddedThemes
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("core")
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.ApiKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("character varying(300)");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ApiKeys", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("character varying(300)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Roles", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("RoleMembers", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.SettingsOption", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SettingsOptions", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Theme", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("CssContent")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20000)
|
||||
.HasColumnType("character varying(20000)");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Themes", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(254)
|
||||
.HasColumnType("character varying(254)");
|
||||
|
||||
b.Property<DateTimeOffset>("InvalidateTimestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.Api.Database.Entities.Role", "Role")
|
||||
.WithMany("Members")
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.Api.Database.Entities.User", "User")
|
||||
.WithMany("RoleMemberships")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Role");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Navigation("Members");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Navigation("RoleMemberships");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedThemes : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Themes",
|
||||
schema: "core",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
Version = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
Author = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
IsEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
CssContent = table.Column<string>(type: "character varying(20000)", maxLength: 20000, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Themes", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Themes",
|
||||
schema: "core");
|
||||
}
|
||||
}
|
||||
}
|
||||
251
Moonlight.Api/Database/Migrations/20260129134620_SwitchedToJsonForSettingsOption.Designer.cs
generated
Normal file
251
Moonlight.Api/Database/Migrations/20260129134620_SwitchedToJsonForSettingsOption.Designer.cs
generated
Normal file
@@ -0,0 +1,251 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Moonlight.Api.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20260129134620_SwitchedToJsonForSettingsOption")]
|
||||
partial class SwitchedToJsonForSettingsOption
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("core")
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.ApiKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("character varying(300)");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ApiKeys", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("character varying(300)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Roles", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("RoleMembers", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.SettingsOption", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ValueJson")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SettingsOptions", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Theme", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("CssContent")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20000)
|
||||
.HasColumnType("character varying(20000)");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Themes", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(254)
|
||||
.HasColumnType("character varying(254)");
|
||||
|
||||
b.Property<DateTimeOffset>("InvalidateTimestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.Api.Database.Entities.Role", "Role")
|
||||
.WithMany("Members")
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.Api.Database.Entities.User", "User")
|
||||
.WithMany("RoleMemberships")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Role");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Navigation("Members");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Navigation("RoleMemberships");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SwitchedToJsonForSettingsOption : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Value",
|
||||
schema: "core",
|
||||
table: "SettingsOptions");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ValueJson",
|
||||
schema: "core",
|
||||
table: "SettingsOptions",
|
||||
type: "jsonb",
|
||||
maxLength: 4096,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ValueJson",
|
||||
schema: "core",
|
||||
table: "SettingsOptions");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Value",
|
||||
schema: "core",
|
||||
table: "SettingsOptions",
|
||||
type: "character varying(4096)",
|
||||
maxLength: 4096,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
}
|
||||
}
|
||||
248
Moonlight.Api/Database/Migrations/DataContextModelSnapshot.cs
Normal file
248
Moonlight.Api/Database/Migrations/DataContextModelSnapshot.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Moonlight.Api.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Api.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
partial class DataContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("core")
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.ApiKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("character varying(300)");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ApiKeys", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("character varying(300)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Roles", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("RoleMembers", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.SettingsOption", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ValueJson")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SettingsOptions", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Theme", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("CssContent")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20000)
|
||||
.HasColumnType("character varying(20000)");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Themes", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(254)
|
||||
.HasColumnType("character varying(254)");
|
||||
|
||||
b.Property<DateTimeOffset>("InvalidateTimestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users", "core");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.Api.Database.Entities.Role", "Role")
|
||||
.WithMany("Members")
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.Api.Database.Entities.User", "User")
|
||||
.WithMany("RoleMemberships")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Role");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
||||
{
|
||||
b.Navigation("Members");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
||||
{
|
||||
b.Navigation("RoleMemberships");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
77
Moonlight.Api/Helpers/AppConsoleFormatter.cs
Normal file
77
Moonlight.Api/Helpers/AppConsoleFormatter.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
|
||||
namespace Moonlight.Api.Helpers;
|
||||
|
||||
public class AppConsoleFormatter : ConsoleFormatter
|
||||
{
|
||||
private const string TimestampColor = "\e[38;2;148;148;148m";
|
||||
private const string CategoryColor = "\e[38;2;198;198;198m";
|
||||
private const string MessageColor = "\e[38;2;255;255;255m";
|
||||
private const string Bold = "\e[1m";
|
||||
|
||||
// Pre-computed ANSI color codes for each log level
|
||||
private const string CriticalColor = "\e[38;2;255;0;0m";
|
||||
private const string ErrorColor = "\e[38;2;255;0;0m";
|
||||
private const string WarningColor = "\e[38;2;215;215;0m";
|
||||
private const string InfoColor = "\e[38;2;135;215;255m";
|
||||
private const string DebugColor = "\e[38;2;198;198;198m";
|
||||
private const string TraceColor = "\e[38;2;68;68;68m";
|
||||
|
||||
public AppConsoleFormatter() : base(nameof(AppConsoleFormatter))
|
||||
{
|
||||
}
|
||||
|
||||
public override void Write<TState>(
|
||||
in LogEntry<TState> logEntry,
|
||||
IExternalScopeProvider? scopeProvider,
|
||||
TextWriter textWriter)
|
||||
{
|
||||
var message = logEntry.Formatter(logEntry.State, logEntry.Exception);
|
||||
|
||||
// Timestamp
|
||||
textWriter.Write(TimestampColor);
|
||||
textWriter.Write(DateTime.Now.ToString("dd.MM.yy HH:mm:ss"));
|
||||
textWriter.Write(' ');
|
||||
|
||||
// Log level with color and bold
|
||||
var (levelText, levelColor) = GetLevelInfo(logEntry.LogLevel);
|
||||
textWriter.Write(levelColor);
|
||||
textWriter.Write(Bold);
|
||||
textWriter.Write(levelText);
|
||||
textWriter.Write(' ');
|
||||
|
||||
// Category
|
||||
textWriter.Write(CategoryColor);
|
||||
textWriter.Write(logEntry.Category);
|
||||
|
||||
// Message
|
||||
textWriter.Write(MessageColor);
|
||||
textWriter.Write(": ");
|
||||
textWriter.Write(message);
|
||||
|
||||
// Exception
|
||||
if (logEntry.Exception != null)
|
||||
{
|
||||
textWriter.Write(MessageColor);
|
||||
textWriter.WriteLine(logEntry.Exception.ToString());
|
||||
}
|
||||
else
|
||||
textWriter.WriteLine();
|
||||
}
|
||||
|
||||
private static (string text, string color) GetLevelInfo(LogLevel logLevel)
|
||||
{
|
||||
return logLevel switch
|
||||
{
|
||||
LogLevel.Critical => ("CRIT", CriticalColor),
|
||||
LogLevel.Error => ("ERRO", ErrorColor),
|
||||
LogLevel.Warning => ("WARN", WarningColor),
|
||||
LogLevel.Information => ("INFO", InfoColor),
|
||||
LogLevel.Debug => ("DEBG", DebugColor),
|
||||
LogLevel.Trace => ("TRCE", TraceColor),
|
||||
_ => ("NONE", "")
|
||||
};
|
||||
}
|
||||
}
|
||||
98
Moonlight.Api/Helpers/OsHelper.cs
Normal file
98
Moonlight.Api/Helpers/OsHelper.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Moonlight.Api.Helpers;
|
||||
|
||||
public class OsHelper
|
||||
{
|
||||
public static string GetName()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return GetWindowsVersion();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return GetLinuxVersion();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
return "Goofy OS";
|
||||
|
||||
return "Unknown OS";
|
||||
}
|
||||
|
||||
private static string GetWindowsVersion()
|
||||
{
|
||||
var version = Environment.OSVersion.Version;
|
||||
|
||||
// Windows 11 is version 10.0 build 22000+
|
||||
if (version.Major == 10 && version.Build >= 22000)
|
||||
return $"Windows 11 ({version.Build})";
|
||||
|
||||
if (version.Major == 10)
|
||||
return $"Windows 10 ({version.Build})";
|
||||
|
||||
if (version.Major == 6 && version.Minor == 3)
|
||||
return "Windows 8.1";
|
||||
|
||||
if (version.Major == 6 && version.Minor == 2)
|
||||
return "Windows 8";
|
||||
|
||||
if (version.Major == 6 && version.Minor == 1)
|
||||
return "Windows 7";
|
||||
|
||||
return $"Windows {version.Major}.{version.Minor}";
|
||||
}
|
||||
|
||||
private static string GetLinuxVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Read /etc/os-release, should work everywhere
|
||||
if (File.Exists("/etc/os-release"))
|
||||
{
|
||||
var lines = File.ReadAllLines("/etc/os-release");
|
||||
string? name = null;
|
||||
string? version = null;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.StartsWith("NAME="))
|
||||
name = line.Substring(5).Trim('"');
|
||||
else if (line.StartsWith("VERSION_ID="))
|
||||
version = line.Substring(11).Trim('"');
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
return string.IsNullOrEmpty(version) ? name : $"{name} {version}";
|
||||
}
|
||||
}
|
||||
|
||||
//If for some weird reason it still uses lsb release
|
||||
if (File.Exists("/etc/lsb-release"))
|
||||
{
|
||||
var lines = File.ReadAllLines("/etc/lsb-release");
|
||||
string? name = null;
|
||||
string? version = null;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.StartsWith("DISTRIB_ID="))
|
||||
name = line.Substring(11);
|
||||
else if (line.StartsWith("DISTRIB_RELEASE="))
|
||||
version = line.Substring(16);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
return string.IsNullOrEmpty(version) ? name : $"{name} {version}";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return $"Linux {Environment.OSVersion.Version}";
|
||||
}
|
||||
}
|
||||
134
Moonlight.Api/Http/Controllers/Admin/ApiKeyController.cs
Normal file
134
Moonlight.Api/Http/Controllers/Admin/ApiKeyController.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Api.Mappers;
|
||||
using Moonlight.Shared;
|
||||
using Moonlight.Shared.Http.Requests;
|
||||
using Moonlight.Shared.Http.Requests.Admin.ApiKeys;
|
||||
using Moonlight.Shared.Http.Responses;
|
||||
using Moonlight.Shared.Http.Responses.Admin.ApiKeys;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/admin/apiKeys")]
|
||||
public class ApiKeyController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<ApiKey> KeyRepository;
|
||||
|
||||
public ApiKeyController(DatabaseRepository<ApiKey> keyRepository)
|
||||
{
|
||||
KeyRepository = keyRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Permissions.ApiKeys.View)]
|
||||
public async Task<ActionResult<PagedData<ApiKeyDto>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int length,
|
||||
[FromQuery] FilterOptions? filterOptions
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
// Query building
|
||||
var query = KeyRepository.Query();
|
||||
|
||||
// Filters
|
||||
if (filterOptions != null)
|
||||
{
|
||||
foreach (var filterOption in filterOptions.Filters)
|
||||
{
|
||||
query = filterOption.Key switch
|
||||
{
|
||||
nameof(ApiKey.Name) =>
|
||||
query.Where(k => EF.Functions.ILike(k.Name, $"%{filterOption.Value}%")),
|
||||
|
||||
nameof(ApiKey.Description) =>
|
||||
query.Where(k => EF.Functions.ILike(k.Description, $"%{filterOption.Value}%")),
|
||||
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination
|
||||
var data = await query
|
||||
.OrderBy(k => k.Id)
|
||||
.ProjectToDto()
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ToArrayAsync();
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
return new PagedData<ApiKeyDto>(data, total);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = Permissions.ApiKeys.View)]
|
||||
public async Task<ActionResult<ApiKeyDto>> GetAsync([FromRoute] int id)
|
||||
{
|
||||
var key = await KeyRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(k => k.Id == id);
|
||||
|
||||
if (key == null)
|
||||
return Problem("No API key with this id found", statusCode: 404);
|
||||
|
||||
return ApiKeyMapper.ToDto(key);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Permissions.ApiKeys.Create)]
|
||||
public async Task<ActionResult<ApiKeyDto>> CreateAsync([FromBody] CreateApiKeyDto request)
|
||||
{
|
||||
var apiKey = ApiKeyMapper.ToEntity(request);
|
||||
|
||||
apiKey.Key = Guid.NewGuid().ToString("N").Substring(0, 32);
|
||||
|
||||
var finalKey = await KeyRepository.AddAsync(apiKey);
|
||||
|
||||
return ApiKeyMapper.ToDto(finalKey);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = Permissions.ApiKeys.Edit)]
|
||||
public async Task<ActionResult<ApiKeyDto>> UpdateAsync([FromRoute] int id, [FromBody] UpdateApiKeyDto request)
|
||||
{
|
||||
var apiKey = await KeyRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(k => k.Id == id);
|
||||
|
||||
if (apiKey == null)
|
||||
return Problem("No API key with this id found", statusCode: 404);
|
||||
|
||||
ApiKeyMapper.Merge(apiKey, request);
|
||||
await KeyRepository.UpdateAsync(apiKey);
|
||||
|
||||
return ApiKeyMapper.ToDto(apiKey);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = Permissions.ApiKeys.Delete)]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||
{
|
||||
var apiKey = await KeyRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(k => k.Id == id);
|
||||
|
||||
if (apiKey == null)
|
||||
return Problem("No API key with this id found", statusCode: 404);
|
||||
|
||||
await KeyRepository.RemoveAsync(apiKey);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moonlight.Api.Configuration;
|
||||
using Moonlight.Api.Mappers;
|
||||
using Moonlight.Api.Services;
|
||||
using Moonlight.Shared.Http.Events;
|
||||
using Moonlight.Shared.Http.Requests.Admin.ContainerHelper;
|
||||
using Moonlight.Shared.Http.Responses.Admin;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/ch")]
|
||||
public class ContainerHelperController : Controller
|
||||
{
|
||||
private readonly ContainerHelperService ContainerHelperService;
|
||||
private readonly IOptions<ContainerHelperOptions> Options;
|
||||
|
||||
public ContainerHelperController(ContainerHelperService containerHelperService, IOptions<ContainerHelperOptions> options)
|
||||
{
|
||||
ContainerHelperService = containerHelperService;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
[HttpGet("status")]
|
||||
public async Task<ActionResult<ContainerHelperStatusDto>> GetStatusAsync()
|
||||
{
|
||||
if (!Options.Value.IsEnabled)
|
||||
return new ContainerHelperStatusDto(false, false);
|
||||
|
||||
var status = await ContainerHelperService.CheckConnectionAsync();
|
||||
|
||||
return new ContainerHelperStatusDto(true, status);
|
||||
}
|
||||
|
||||
[HttpPost("rebuild")]
|
||||
public Task<IResult> RebuildAsync([FromBody] RequestRebuildDto request)
|
||||
{
|
||||
var result = ContainerHelperService.RebuildAsync(request.NoBuildCache);
|
||||
var mappedResult = result.Select(ContainerHelperMapper.ToDto);
|
||||
|
||||
return Task.FromResult<IResult>(
|
||||
TypedResults.ServerSentEvents(mappedResult)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpPost("version")]
|
||||
public async Task<ActionResult> SetVersionAsync([FromBody] SetVersionDto request)
|
||||
{
|
||||
await ContainerHelperService.SetVersionAsync(request.Version);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
32
Moonlight.Api/Http/Controllers/Admin/DiagnoseController.cs
Normal file
32
Moonlight.Api/Http/Controllers/Admin/DiagnoseController.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.Api.Mappers;
|
||||
using Moonlight.Api.Services;
|
||||
using Moonlight.Shared;
|
||||
using Moonlight.Shared.Http.Responses.Admin;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||
|
||||
[ApiController]
|
||||
[Authorize(Policy = Permissions.System.Diagnose)]
|
||||
[Route("api/admin/system/diagnose")]
|
||||
public class DiagnoseController : Controller
|
||||
{
|
||||
private readonly DiagnoseService DiagnoseService;
|
||||
|
||||
public DiagnoseController(DiagnoseService diagnoseService)
|
||||
{
|
||||
DiagnoseService = diagnoseService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<DiagnoseResultDto[]>> GetAsync()
|
||||
{
|
||||
var results = await DiagnoseService.DiagnoseAsync();
|
||||
|
||||
return results
|
||||
.OrderBy(x => x.Level)
|
||||
.ToDto()
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
171
Moonlight.Api/Http/Controllers/Admin/RoleMembersController.cs
Normal file
171
Moonlight.Api/Http/Controllers/Admin/RoleMembersController.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Api.Mappers;
|
||||
using Moonlight.Shared;
|
||||
using Moonlight.Shared.Http.Responses;
|
||||
using Moonlight.Shared.Http.Responses.Admin.Users;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||
|
||||
[ApiController]
|
||||
[Authorize(Policy = Permissions.Roles.Members)]
|
||||
[Route("api/admin/roles/{roleId:int}/members")]
|
||||
public class RoleMembersController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<User> UsersRepository;
|
||||
private readonly DatabaseRepository<Role> RolesRepository;
|
||||
private readonly DatabaseRepository<RoleMember> RoleMembersRepository;
|
||||
|
||||
public RoleMembersController(
|
||||
DatabaseRepository<User> usersRepository,
|
||||
DatabaseRepository<Role> rolesRepository,
|
||||
DatabaseRepository<RoleMember> roleMembersRepository
|
||||
)
|
||||
{
|
||||
UsersRepository = usersRepository;
|
||||
RolesRepository = rolesRepository;
|
||||
RoleMembersRepository = roleMembersRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<PagedData<UserDto>>> GetAsync(
|
||||
[FromRoute] int roleId,
|
||||
[FromQuery] int startIndex, [FromQuery] int length,
|
||||
[FromQuery] string? searchTerm
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
// Query building
|
||||
|
||||
var query = RoleMembersRepository
|
||||
.Query()
|
||||
.Where(x => x.Role.Id == roleId)
|
||||
.Select(x => x.User);
|
||||
|
||||
// Filtering
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
query = query.Where(x =>
|
||||
EF.Functions.ILike(x.Username, $"%{searchTerm}%") ||
|
||||
EF.Functions.ILike(x.Email, $"%{searchTerm}%")
|
||||
);
|
||||
}
|
||||
|
||||
// Pagination
|
||||
var items = query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ProjectToDto()
|
||||
.ToArray();
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
return new PagedData<UserDto>(items, totalCount);
|
||||
}
|
||||
|
||||
[HttpGet("available")]
|
||||
public async Task<ActionResult<PagedData<UserDto>>> GetAvailableAsync(
|
||||
[FromRoute] int roleId,
|
||||
[FromQuery] int startIndex, [FromQuery] int length,
|
||||
[FromQuery] string? searchTerm
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
// Query building
|
||||
|
||||
var query = UsersRepository
|
||||
.Query()
|
||||
.Where(x => x.RoleMemberships.All(y => y.Role.Id != roleId));
|
||||
|
||||
// Filtering
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
query = query.Where(x =>
|
||||
EF.Functions.ILike(x.Username, $"%{searchTerm}%") ||
|
||||
EF.Functions.ILike(x.Email, $"%{searchTerm}%")
|
||||
);
|
||||
}
|
||||
|
||||
// Pagination
|
||||
var items = query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ProjectToDto()
|
||||
.ToArray();
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
return new PagedData<UserDto>(items, totalCount);
|
||||
}
|
||||
|
||||
[HttpPut("{userId:int}")]
|
||||
public async Task<ActionResult> AddMemberAsync([FromRoute] int roleId, [FromRoute] int userId)
|
||||
{
|
||||
// Check and load role
|
||||
var role = await RolesRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == roleId);
|
||||
|
||||
if (role == null)
|
||||
return Problem("Role not found", statusCode: 404);
|
||||
|
||||
// Check and load user
|
||||
var user = await UsersRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
return Problem("User not found", statusCode: 404);
|
||||
|
||||
// Check if a role member already exists with these details
|
||||
var isUserInRole = await RoleMembersRepository
|
||||
.Query()
|
||||
.AnyAsync(x => x.Role.Id == roleId && x.User.Id == userId);
|
||||
|
||||
if (isUserInRole)
|
||||
return Problem("User is already a member of this role", statusCode: 400);
|
||||
|
||||
var roleMember = new RoleMember
|
||||
{
|
||||
Role = role,
|
||||
User = user
|
||||
};
|
||||
|
||||
await RoleMembersRepository.AddAsync(roleMember);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("{userId:int}")]
|
||||
public async Task<ActionResult> RemoveMemberAsync([FromRoute] int roleId, [FromRoute] int userId)
|
||||
{
|
||||
var roleMember = await RoleMembersRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.User.Id == userId && x.Role.Id == roleId);
|
||||
|
||||
if (roleMember == null)
|
||||
return Problem("User is not a member of this role, the role does not exist or the user does not exist",
|
||||
statusCode: 404);
|
||||
|
||||
await RoleMembersRepository.RemoveAsync(roleMember);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
131
Moonlight.Api/Http/Controllers/Admin/RolesController.cs
Normal file
131
Moonlight.Api/Http/Controllers/Admin/RolesController.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Api.Mappers;
|
||||
using Moonlight.Shared;
|
||||
using Moonlight.Shared.Http.Requests;
|
||||
using Moonlight.Shared.Http.Requests.Admin.Roles;
|
||||
using Moonlight.Shared.Http.Responses;
|
||||
using Moonlight.Shared.Http.Responses.Admin;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/roles")]
|
||||
public class RolesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Role> RoleRepository;
|
||||
|
||||
public RolesController(DatabaseRepository<Role> roleRepository)
|
||||
{
|
||||
RoleRepository = roleRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Permissions.Roles.View)]
|
||||
public async Task<ActionResult<PagedData<RoleDto>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int length,
|
||||
[FromQuery] FilterOptions? filterOptions
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
// Query building
|
||||
|
||||
var query = RoleRepository
|
||||
.Query();
|
||||
|
||||
// Filters
|
||||
if (filterOptions != null)
|
||||
{
|
||||
foreach (var filterOption in filterOptions.Filters)
|
||||
{
|
||||
query = filterOption.Key switch
|
||||
{
|
||||
nameof(Role.Name) =>
|
||||
query.Where(role => EF.Functions.ILike(role.Name, $"%{filterOption.Value}%")),
|
||||
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination
|
||||
var data = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.ProjectToDto()
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ToArrayAsync();
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
return new PagedData<RoleDto>(data, total);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Roles.View)]
|
||||
public async Task<ActionResult<RoleDto>> GetAsync([FromRoute] int id)
|
||||
{
|
||||
var role = await RoleRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (role == null)
|
||||
return Problem("No role with this id found", statusCode: 404);
|
||||
|
||||
return RoleMapper.ToDto(role);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Permissions.Roles.Create)]
|
||||
public async Task<ActionResult<RoleDto>> CreateAsync([FromBody] CreateRoleDto request)
|
||||
{
|
||||
var role = RoleMapper.ToEntity(request);
|
||||
|
||||
var finalRole = await RoleRepository.AddAsync(role);
|
||||
|
||||
return RoleMapper.ToDto(finalRole);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Roles.Edit)]
|
||||
public async Task<ActionResult<RoleDto>> UpdateAsync([FromRoute] int id, [FromBody] UpdateRoleDto request)
|
||||
{
|
||||
var role = await RoleRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (role == null)
|
||||
return Problem("No role with this id found", statusCode: 404);
|
||||
|
||||
RoleMapper.Merge(role, request);
|
||||
|
||||
await RoleRepository.UpdateAsync(role);
|
||||
|
||||
return RoleMapper.ToDto(role);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Roles.Delete)]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||
{
|
||||
var role = await RoleRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (role == null)
|
||||
return Problem("No role with this id found", statusCode: 404);
|
||||
|
||||
await RoleRepository.RemoveAsync(role);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
127
Moonlight.Api/Http/Controllers/Admin/SetupController.cs
Normal file
127
Moonlight.Api/Http/Controllers/Admin/SetupController.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Api.Services;
|
||||
using Moonlight.Shared;
|
||||
using Moonlight.Shared.Http.Requests.Seup;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/setup")]
|
||||
public class SetupController : Controller
|
||||
{
|
||||
private readonly SettingsService SettingsService;
|
||||
private readonly DatabaseRepository<User> UsersRepository;
|
||||
private readonly DatabaseRepository<Role> RolesRepository;
|
||||
|
||||
private const string StateSettingsKey = "Moonlight.Api.Setup.State";
|
||||
|
||||
public SetupController(
|
||||
SettingsService settingsService,
|
||||
DatabaseRepository<User> usersRepository,
|
||||
DatabaseRepository<Role> rolesRepository
|
||||
)
|
||||
{
|
||||
SettingsService = settingsService;
|
||||
UsersRepository = usersRepository;
|
||||
RolesRepository = rolesRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult> GetSetupAsync()
|
||||
{
|
||||
var hasBeenSetup = await SettingsService.GetValueAsync<bool>(StateSettingsKey);
|
||||
|
||||
if (hasBeenSetup)
|
||||
return Problem("This instance is already configured", statusCode: 405);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult> ApplySetupAsync([FromBody] ApplySetupDto dto)
|
||||
{
|
||||
var adminRole = await RolesRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Name == "Administrators");
|
||||
|
||||
if (adminRole == null)
|
||||
{
|
||||
adminRole = await RolesRepository.AddAsync(new Role()
|
||||
{
|
||||
Name = "Administrators",
|
||||
Description = "Automatically generated group for full administrator permissions",
|
||||
Permissions = [
|
||||
Permissions.ApiKeys.View,
|
||||
Permissions.ApiKeys.Create,
|
||||
Permissions.ApiKeys.Edit,
|
||||
Permissions.ApiKeys.Delete,
|
||||
|
||||
Permissions.Roles.View,
|
||||
Permissions.Roles.Create,
|
||||
Permissions.Roles.Edit,
|
||||
Permissions.Roles.Delete,
|
||||
Permissions.Roles.Members,
|
||||
|
||||
Permissions.Users.View,
|
||||
Permissions.Users.Create,
|
||||
Permissions.Users.Edit,
|
||||
Permissions.Users.Delete,
|
||||
Permissions.Users.Logout,
|
||||
|
||||
Permissions.Themes.View,
|
||||
Permissions.Themes.Create,
|
||||
Permissions.Themes.Edit,
|
||||
Permissions.Themes.Delete,
|
||||
|
||||
Permissions.System.Info,
|
||||
Permissions.System.Diagnose,
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var user = await UsersRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(u => u.Email == dto.AdminEmail);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
await UsersRepository.AddAsync(new User()
|
||||
{
|
||||
Email = dto.AdminEmail,
|
||||
Username = dto.AdminUsername,
|
||||
RoleMemberships = [
|
||||
new RoleMember()
|
||||
{
|
||||
Role = adminRole,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow
|
||||
}
|
||||
],
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
user.RoleMemberships.Add(new RoleMember()
|
||||
{
|
||||
Role = adminRole,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
await UsersRepository.UpdateAsync(user);
|
||||
}
|
||||
|
||||
await SettingsService.SetValueAsync(StateSettingsKey, true);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
36
Moonlight.Api/Http/Controllers/Admin/SystemController.cs
Normal file
36
Moonlight.Api/Http/Controllers/Admin/SystemController.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.Api.Services;
|
||||
using Moonlight.Shared;
|
||||
using Moonlight.Shared.Http.Responses.Admin;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/system")]
|
||||
public class SystemController : Controller
|
||||
{
|
||||
private readonly ApplicationService ApplicationService;
|
||||
|
||||
public SystemController(ApplicationService applicationService)
|
||||
{
|
||||
ApplicationService = applicationService;
|
||||
}
|
||||
|
||||
[HttpGet("info")]
|
||||
[Authorize(Policy = Permissions.System.Info)]
|
||||
public async Task<ActionResult<SystemInfoDto>> GetInfoAsync()
|
||||
{
|
||||
var cpuUsage = await ApplicationService.GetCpuUsageAsync();
|
||||
var memoryUsage = await ApplicationService.GetMemoryUsageAsync();
|
||||
|
||||
return new SystemInfoDto(
|
||||
cpuUsage,
|
||||
memoryUsage,
|
||||
ApplicationService.OperatingSystem,
|
||||
DateTimeOffset.UtcNow - ApplicationService.StartedAt,
|
||||
ApplicationService.VersionName,
|
||||
ApplicationService.IsUpToDate
|
||||
);
|
||||
}
|
||||
}
|
||||
145
Moonlight.Api/Http/Controllers/Admin/ThemesController.cs
Normal file
145
Moonlight.Api/Http/Controllers/Admin/ThemesController.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Api.Mappers;
|
||||
using Moonlight.Api.Services;
|
||||
using Moonlight.Shared;
|
||||
using Moonlight.Shared.Http.Requests;
|
||||
using Moonlight.Shared.Http.Requests.Admin.Themes;
|
||||
using Moonlight.Shared.Http.Responses;
|
||||
using Moonlight.Shared.Http.Responses.Admin.Themes;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/themes")]
|
||||
public class ThemesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Theme> ThemeRepository;
|
||||
private readonly FrontendService FrontendService;
|
||||
|
||||
public ThemesController(DatabaseRepository<Theme> themeRepository, FrontendService frontendService)
|
||||
{
|
||||
ThemeRepository = themeRepository;
|
||||
FrontendService = frontendService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Permissions.Themes.View)]
|
||||
public async Task<ActionResult<PagedData<ThemeDto>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int length,
|
||||
[FromQuery] FilterOptions? filterOptions
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
// Query building
|
||||
|
||||
var query = ThemeRepository
|
||||
.Query();
|
||||
|
||||
// Filters
|
||||
if (filterOptions != null)
|
||||
{
|
||||
foreach (var filterOption in filterOptions.Filters)
|
||||
{
|
||||
query = filterOption.Key switch
|
||||
{
|
||||
nameof(Theme.Name) =>
|
||||
query.Where(user => EF.Functions.ILike(user.Name, $"%{filterOption.Value}%")),
|
||||
|
||||
nameof(Theme.Version) =>
|
||||
query.Where(user => EF.Functions.ILike(user.Version, $"%{filterOption.Value}%")),
|
||||
|
||||
nameof(Theme.Author) =>
|
||||
query.Where(user => EF.Functions.ILike(user.Author, $"%{filterOption.Value}%")),
|
||||
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination
|
||||
var data = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.ProjectToDto()
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ToArrayAsync();
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
return new PagedData<ThemeDto>(data, total);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Themes.View)]
|
||||
public async Task<ActionResult<ThemeDto>> GetAsync([FromRoute] int id)
|
||||
{
|
||||
var item = await ThemeRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (item == null)
|
||||
return Problem("No theme with this id", statusCode: 404);
|
||||
|
||||
return ThemeMapper.ToDto(item);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Permissions.Themes.Create)]
|
||||
public async Task<ActionResult<ThemeDto>> CreateAsync([FromBody] CreateThemeDto request)
|
||||
{
|
||||
var theme = ThemeMapper.ToEntity(request);
|
||||
|
||||
var finalTheme = await ThemeRepository.AddAsync(theme);
|
||||
await FrontendService.ResetCacheAsync();
|
||||
|
||||
return ThemeMapper.ToDto(finalTheme);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Themes.Edit)]
|
||||
public async Task<ActionResult<ThemeDto>> UpdateAsync([FromRoute] int id, [FromBody] UpdateThemeDto request)
|
||||
{
|
||||
var theme = await ThemeRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (theme == null)
|
||||
return Problem("No theme with this id found", statusCode: 404);
|
||||
|
||||
ThemeMapper.Merge(theme, request);
|
||||
await ThemeRepository.UpdateAsync(theme);
|
||||
|
||||
await FrontendService.ResetCacheAsync();
|
||||
|
||||
return ThemeMapper.ToDto(theme);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Themes.Delete)]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||
{
|
||||
var theme = await ThemeRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (theme == null)
|
||||
return Problem("No theme with this id found", statusCode: 404);
|
||||
|
||||
await ThemeRepository.RemoveAsync(theme);
|
||||
|
||||
await FrontendService.ResetCacheAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Api.Services;
|
||||
using Moonlight.Shared;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/users/{id:int}")]
|
||||
public class UserActionsController : Controller
|
||||
{
|
||||
// Consider building a service for deletion and logout or actions in general
|
||||
|
||||
private readonly DatabaseRepository<User> UsersRepository;
|
||||
private readonly IMemoryCache Cache;
|
||||
|
||||
public UserActionsController(DatabaseRepository<User> usersRepository, IMemoryCache cache)
|
||||
{
|
||||
UsersRepository = usersRepository;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
[HttpPost("logout")]
|
||||
[Authorize(Policy = Permissions.Users.Logout)]
|
||||
public async Task<ActionResult> LogoutAsync([FromRoute] int id)
|
||||
{
|
||||
var user = await UsersRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(u => u.Id == id);
|
||||
|
||||
if(user == null)
|
||||
return Problem("User not found", statusCode: 404);
|
||||
|
||||
user.InvalidateTimestamp = DateTimeOffset.UtcNow;
|
||||
await UsersRepository.UpdateAsync(user);
|
||||
|
||||
Cache.Remove(string.Format(UserAuthService.CacheKeyPattern, id));
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
135
Moonlight.Api/Http/Controllers/Admin/UsersController.cs
Normal file
135
Moonlight.Api/Http/Controllers/Admin/UsersController.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Api.Mappers;
|
||||
using Moonlight.Shared;
|
||||
using Moonlight.Shared.Http.Requests;
|
||||
using Moonlight.Shared.Http.Requests.Admin.Users;
|
||||
using Moonlight.Shared.Http.Responses;
|
||||
using Moonlight.Shared.Http.Responses.Admin.Users;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/admin/users")]
|
||||
public class UsersController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<User> UserRepository;
|
||||
|
||||
public UsersController(DatabaseRepository<User> userRepository)
|
||||
{
|
||||
UserRepository = userRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Permissions.Users.View)]
|
||||
public async Task<ActionResult<PagedData<UserDto>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int length,
|
||||
[FromQuery] FilterOptions? filterOptions
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
// Query building
|
||||
|
||||
var query = UserRepository
|
||||
.Query();
|
||||
|
||||
// Filters
|
||||
if (filterOptions != null)
|
||||
{
|
||||
foreach (var filterOption in filterOptions.Filters)
|
||||
{
|
||||
query = filterOption.Key switch
|
||||
{
|
||||
nameof(Database.Entities.User.Email) =>
|
||||
query.Where(user => EF.Functions.ILike(user.Email, $"%{filterOption.Value}%")),
|
||||
|
||||
nameof(Database.Entities.User.Username) =>
|
||||
query.Where(user => EF.Functions.ILike(user.Username, $"%{filterOption.Value}%")),
|
||||
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination
|
||||
var data = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.ProjectToDto()
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ToArrayAsync();
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
return new PagedData<UserDto>(data, total);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Users.View)]
|
||||
public async Task<ActionResult<UserDto>> GetAsync([FromRoute] int id)
|
||||
{
|
||||
var user = await UserRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (user == null)
|
||||
return Problem("No user with this id found", statusCode: 404);
|
||||
|
||||
return UserMapper.ToDto(user);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Permissions.Users.Create)]
|
||||
public async Task<ActionResult<UserDto>> CreateAsync([FromBody] CreateUserDto request)
|
||||
{
|
||||
var user = UserMapper.ToEntity(request);
|
||||
user.InvalidateTimestamp = DateTimeOffset.UtcNow.AddMinutes(-1);
|
||||
|
||||
var finalUser = await UserRepository.AddAsync(user);
|
||||
|
||||
return UserMapper.ToDto(finalUser);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Users.Edit)]
|
||||
public async Task<ActionResult<UserDto>> UpdateAsync([FromRoute] int id, [FromBody] UpdateUserDto request)
|
||||
{
|
||||
var user = await UserRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (user == null)
|
||||
return Problem("No user with this id found", statusCode: 404);
|
||||
|
||||
UserMapper.Merge(user, request);
|
||||
await UserRepository.UpdateAsync(user);
|
||||
|
||||
return UserMapper.ToDto(user);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Users.Delete)]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||
{
|
||||
var user = await UserRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(user => user.Id == id);
|
||||
|
||||
if (user == null)
|
||||
return Problem("No user with this id found", statusCode: 404);
|
||||
|
||||
await UserRepository.RemoveAsync(user);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
46
Moonlight.Api/Http/Controllers/Admin/VersionsController.cs
Normal file
46
Moonlight.Api/Http/Controllers/Admin/VersionsController.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.Api.Mappers;
|
||||
using Moonlight.Api.Services;
|
||||
using Moonlight.Shared;
|
||||
using Moonlight.Shared.Http.Responses.Admin;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/versions")]
|
||||
[Authorize(Policy = Permissions.System.Versions)]
|
||||
public class VersionsController : Controller
|
||||
{
|
||||
private readonly VersionService VersionService;
|
||||
|
||||
public VersionsController(VersionService versionService)
|
||||
{
|
||||
VersionService = versionService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<VersionDto[]>> GetAsync()
|
||||
{
|
||||
var versions = await VersionService.GetVersionsAsync();
|
||||
return VersionMapper.ToDtos(versions).ToArray();
|
||||
}
|
||||
|
||||
[HttpGet("instance")]
|
||||
public async Task<ActionResult<VersionDto>> GetInstanceAsync()
|
||||
{
|
||||
var version = await VersionService.GetInstanceVersionAsync();
|
||||
return VersionMapper.ToDto(version);
|
||||
}
|
||||
|
||||
[HttpGet("latest")]
|
||||
public async Task<ActionResult<VersionDto>> GetLatestAsync()
|
||||
{
|
||||
var version = await VersionService.GetLatestVersionAsync();
|
||||
|
||||
if(version == null)
|
||||
return Problem("Unable to retrieve latest version", statusCode: 404);
|
||||
|
||||
return VersionMapper.ToDto(version);
|
||||
}
|
||||
}
|
||||
65
Moonlight.Api/Http/Controllers/AuthController.cs
Normal file
65
Moonlight.Api/Http/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.Shared.Http.Responses.Admin.Auth;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/auth")]
|
||||
public class AuthController : Controller
|
||||
{
|
||||
private readonly IAuthenticationSchemeProvider SchemeProvider;
|
||||
|
||||
public AuthController(IAuthenticationSchemeProvider schemeProvider)
|
||||
{
|
||||
SchemeProvider = schemeProvider;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<SchemeDto[]>> GetSchemesAsync()
|
||||
{
|
||||
var schemes = await SchemeProvider.GetAllSchemesAsync();
|
||||
|
||||
return schemes
|
||||
.Where(scheme => !string.IsNullOrWhiteSpace(scheme.DisplayName))
|
||||
.Select(scheme => new SchemeDto(scheme.Name, scheme.DisplayName!))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
[HttpGet("{schemeName:alpha}")]
|
||||
public async Task<ActionResult> StartAsync([FromRoute] string schemeName)
|
||||
{
|
||||
var scheme = await SchemeProvider.GetSchemeAsync(schemeName);
|
||||
|
||||
if (scheme == null || string.IsNullOrWhiteSpace(scheme.DisplayName))
|
||||
return Problem("Invalid authentication scheme name", statusCode: 400);
|
||||
|
||||
return Challenge(new AuthenticationProperties()
|
||||
{
|
||||
RedirectUri = "/"
|
||||
}, scheme.Name);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("claims")]
|
||||
public Task<ActionResult<ClaimDto[]>> GetClaimsAsync()
|
||||
{
|
||||
var result = User.Claims
|
||||
.Select(claim => new ClaimDto(claim.Type, claim.Value))
|
||||
.ToArray();
|
||||
|
||||
return Task.FromResult<ActionResult<ClaimDto[]>>(result);
|
||||
}
|
||||
|
||||
[HttpGet("logout")]
|
||||
public Task<ActionResult> LogoutAsync()
|
||||
{
|
||||
return Task.FromResult<ActionResult>(
|
||||
SignOut(new AuthenticationProperties()
|
||||
{
|
||||
RedirectUri = "/"
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
25
Moonlight.Api/Http/Controllers/FrontendController.cs
Normal file
25
Moonlight.Api/Http/Controllers/FrontendController.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.Api.Mappers;
|
||||
using Moonlight.Api.Services;
|
||||
using Moonlight.Shared.Http.Responses.Admin.Frontend;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/frontend")]
|
||||
public class FrontendController : Controller
|
||||
{
|
||||
private readonly FrontendService FrontendService;
|
||||
|
||||
public FrontendController(FrontendService frontendService)
|
||||
{
|
||||
FrontendService = frontendService;
|
||||
}
|
||||
|
||||
[HttpGet("config")]
|
||||
public async Task<ActionResult<FrontendConfigDto>> GetConfigAsync()
|
||||
{
|
||||
var configuration = await FrontendService.GetConfigurationAsync();
|
||||
return FrontendConfigMapper.ToDto(configuration);
|
||||
}
|
||||
}
|
||||
13
Moonlight.Api/Http/Controllers/PingController.cs
Normal file
13
Moonlight.Api/Http/Controllers/PingController.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Moonlight.Api.Http.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/ping")]
|
||||
public class PingController : Controller
|
||||
{
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Get() => Ok("Pong");
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Moonlight.Api.Http.Services.ContainerHelper.Events;
|
||||
|
||||
public struct RebuildEventDto
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public RebuildEventType Type { get; set; }
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public string Data { get; set; }
|
||||
}
|
||||
|
||||
public enum RebuildEventType
|
||||
{
|
||||
Log = 0,
|
||||
Failed = 1,
|
||||
Succeeded = 2,
|
||||
Step = 3
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Moonlight.Api.Http.Services.ContainerHelper;
|
||||
|
||||
public class ProblemDetails
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public string Title { get; set; }
|
||||
public int Status { get; set; }
|
||||
public string? Detail { get; set; }
|
||||
public Dictionary<string, string[]>? Errors { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Moonlight.Api.Http.Services.ContainerHelper.Requests;
|
||||
|
||||
public record RequestRebuildDto(bool NoBuildCache);
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Moonlight.Api.Http.Services.ContainerHelper.Requests;
|
||||
|
||||
public record SetVersionDto(string Version);
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Moonlight.Api.Http.Services.ContainerHelper.Events;
|
||||
using Moonlight.Api.Http.Services.ContainerHelper.Requests;
|
||||
|
||||
namespace Moonlight.Api.Http.Services.ContainerHelper;
|
||||
|
||||
[JsonSerializable(typeof(SetVersionDto))]
|
||||
[JsonSerializable(typeof(ProblemDetails))]
|
||||
[JsonSerializable(typeof(RebuildEventDto))]
|
||||
[JsonSerializable(typeof(RequestRebuildDto))]
|
||||
public partial class SerializationContext : JsonSerializerContext
|
||||
{
|
||||
private static JsonSerializerOptions? InternalTunedOptions;
|
||||
|
||||
public static JsonSerializerOptions TunedOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (InternalTunedOptions != null)
|
||||
return InternalTunedOptions;
|
||||
|
||||
InternalTunedOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
||||
InternalTunedOptions.TypeInfoResolverChain.Add(Default);
|
||||
|
||||
return InternalTunedOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Shared;
|
||||
|
||||
namespace Moonlight.Api.Implementations.ApiKeyScheme;
|
||||
|
||||
public class ApiKeySchemeHandler : AuthenticationHandler<ApiKeySchemeOptions>
|
||||
{
|
||||
private readonly DatabaseRepository<ApiKey> ApiKeyRepository;
|
||||
private readonly IMemoryCache MemoryCache;
|
||||
|
||||
public ApiKeySchemeHandler(
|
||||
IOptionsMonitor<ApiKeySchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
DatabaseRepository<ApiKey> apiKeyRepository,
|
||||
IMemoryCache memoryCache
|
||||
) : base(options, logger, encoder)
|
||||
{
|
||||
ApiKeyRepository = apiKeyRepository;
|
||||
MemoryCache = memoryCache;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var authHeaderValue = Request.Headers.Authorization.FirstOrDefault() ?? null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(authHeaderValue))
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
if (authHeaderValue.Length > 32)
|
||||
return AuthenticateResult.Fail("Invalid api key specified");
|
||||
|
||||
if (!MemoryCache.TryGetValue<ApiKeySession>(authHeaderValue, out var apiKey))
|
||||
{
|
||||
apiKey = await ApiKeyRepository
|
||||
.Query()
|
||||
.Where(x => x.Key == authHeaderValue)
|
||||
.Select(x => new ApiKeySession(x.Permissions))
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (apiKey == null)
|
||||
return AuthenticateResult.Fail("Invalid api key specified");
|
||||
|
||||
MemoryCache.Set(authHeaderValue, apiKey, Options.LookupCacheTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (apiKey == null)
|
||||
return AuthenticateResult.Fail("Invalid api key specified");
|
||||
}
|
||||
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||
new ClaimsPrincipal(
|
||||
new ClaimsIdentity(
|
||||
apiKey.Permissions.Select(x => new Claim(Permissions.ClaimType, x)).ToArray()
|
||||
)
|
||||
),
|
||||
Scheme.Name
|
||||
));
|
||||
}
|
||||
|
||||
private record ApiKeySession(string[] Permissions);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace Moonlight.Api.Implementations.ApiKeyScheme;
|
||||
|
||||
public class ApiKeySchemeOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
public TimeSpan LookupCacheTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Moonlight.Shared;
|
||||
|
||||
namespace Moonlight.Api.Implementations;
|
||||
|
||||
public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
|
||||
{
|
||||
protected override Task HandleRequirementAsync(
|
||||
AuthorizationHandlerContext context,
|
||||
PermissionRequirement requirement)
|
||||
{
|
||||
var permissionClaim = context.User.FindFirst(x =>
|
||||
x.Type.Equals(Permissions.ClaimType, StringComparison.OrdinalIgnoreCase) &&
|
||||
x.Value.Equals(requirement.Identifier, StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
|
||||
if (permissionClaim == null)
|
||||
{
|
||||
context.Fail(new AuthorizationFailureReason(
|
||||
this,
|
||||
$"User does not have the requested permission '{requirement.Identifier}'"
|
||||
));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
context.Succeed(requirement);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
42
Moonlight.Api/Implementations/PermissionPolicyProvider.cs
Normal file
42
Moonlight.Api/Implementations/PermissionPolicyProvider.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moonlight.Shared;
|
||||
|
||||
namespace Moonlight.Api.Implementations;
|
||||
|
||||
public class PermissionPolicyProvider : IAuthorizationPolicyProvider
|
||||
{
|
||||
private readonly DefaultAuthorizationPolicyProvider FallbackProvider;
|
||||
|
||||
public PermissionPolicyProvider(IOptions<AuthorizationOptions> options)
|
||||
{
|
||||
FallbackProvider = new DefaultAuthorizationPolicyProvider(options);
|
||||
}
|
||||
|
||||
public async Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
|
||||
{
|
||||
if (!policyName.StartsWith(Permissions.Prefix, StringComparison.OrdinalIgnoreCase))
|
||||
return await FallbackProvider.GetPolicyAsync(policyName);
|
||||
|
||||
var policy = new AuthorizationPolicyBuilder();
|
||||
policy.AddRequirements(new PermissionRequirement(policyName));
|
||||
|
||||
return policy.Build();
|
||||
}
|
||||
|
||||
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
|
||||
=> FallbackProvider.GetDefaultPolicyAsync();
|
||||
|
||||
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
|
||||
=> FallbackProvider.GetFallbackPolicyAsync();
|
||||
}
|
||||
|
||||
public class PermissionRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public string Identifier { get; }
|
||||
|
||||
public PermissionRequirement(string identifier)
|
||||
{
|
||||
Identifier = identifier;
|
||||
}
|
||||
}
|
||||
33
Moonlight.Api/Implementations/UpdateDiagnoseProvider.cs
Normal file
33
Moonlight.Api/Implementations/UpdateDiagnoseProvider.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Moonlight.Api.Interfaces;
|
||||
using Moonlight.Api.Models;
|
||||
using Moonlight.Api.Services;
|
||||
|
||||
namespace Moonlight.Api.Implementations;
|
||||
|
||||
public sealed class UpdateDiagnoseProvider : IDiagnoseProvider
|
||||
{
|
||||
private readonly ApplicationService ApplicationService;
|
||||
|
||||
public UpdateDiagnoseProvider(ApplicationService applicationService)
|
||||
{
|
||||
ApplicationService = applicationService;
|
||||
}
|
||||
|
||||
public Task<DiagnoseResult[]> DiagnoseAsync()
|
||||
{
|
||||
if (ApplicationService.IsUpToDate)
|
||||
return Task.FromResult<DiagnoseResult[]>([]);
|
||||
|
||||
return Task.FromResult<DiagnoseResult[]>([
|
||||
new DiagnoseResult(
|
||||
DiagnoseLevel.Warning,
|
||||
"Instance is not up-to-date",
|
||||
["Moonlight", "Update Check"],
|
||||
"Update your moonlight instance to receive bug fixes, new features and security patches. Update button can be found in the overview",
|
||||
null,
|
||||
"/admin",
|
||||
null
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
8
Moonlight.Api/Interfaces/IDiagnoseProvider.cs
Normal file
8
Moonlight.Api/Interfaces/IDiagnoseProvider.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Moonlight.Api.Models;
|
||||
|
||||
namespace Moonlight.Api.Interfaces;
|
||||
|
||||
public interface IDiagnoseProvider
|
||||
{
|
||||
public Task<DiagnoseResult[]> DiagnoseAsync();
|
||||
}
|
||||
21
Moonlight.Api/Mappers/ApiKeyMapper.cs
Normal file
21
Moonlight.Api/Mappers/ApiKeyMapper.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Shared.Http.Requests.Admin.ApiKeys;
|
||||
using Moonlight.Shared.Http.Responses.Admin.ApiKeys;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace Moonlight.Api.Mappers;
|
||||
|
||||
[Mapper]
|
||||
[SuppressMessage("Mapper", "RMG020:No members are mapped in an object mapping")]
|
||||
[SuppressMessage("Mapper", "RMG012:No members are mapped in an object mapping")]
|
||||
public static partial class ApiKeyMapper
|
||||
{
|
||||
public static partial IQueryable<ApiKeyDto> ProjectToDto(this IQueryable<ApiKey> apiKeys);
|
||||
|
||||
public static partial ApiKeyDto ToDto(ApiKey apiKey);
|
||||
|
||||
public static partial void Merge([MappingTarget] ApiKey apiKey, UpdateApiKeyDto request);
|
||||
|
||||
public static partial ApiKey ToEntity(CreateApiKeyDto request);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user