Compare commits

...

278 Commits
v5 ... dev

Author SHA1 Message Date
Pig Fang
52f6fefed0
chore: remove unavailable sponsor info 2026-03-30 21:42:23 +08:00
Steven Qiu
840555df42
make tests happy 2026-03-09 23:13:47 +08:00
Steven Qiu
9aa867e8aa
refactor: do not catch exceptions when cannot read options 2026-03-09 20:21:43 +08:00
Steven Qiu
d70b39f445
feat: add Auto-Submitted header to emails
@see https://datatracker.ietf.org/doc/html/rfc3834
Hopefully this could prevent sender being spammed by auto replies...
2025-10-09 01:54:37 +08:00
SANYE-YA
33055ecbf9
fix avatar (refactor needed) (#666) 2025-08-07 05:08:13 +08:00
Steven Qiu
2e39fbce77
fix: avatar (refactor needed) 2025-07-31 19:59:29 +08:00
Steven Qiu
1b3b020d52
fix: make imagick sanitize result stable 2025-07-27 03:34:35 +08:00
Steven Qiu
33d805ee82
fix: skinlib 2d preview (refactor needed) 2025-07-26 21:38:33 +08:00
Steven Qiu
57c02dd51c
fix: check if imagick installed 2025-07-25 17:43:53 +08:00
Steven Qiu
5b6bb98860
chore: update README 2025-07-25 17:39:02 +08:00
Steven Qiu
c01112a6c1
chore: use imagick for Intervention\Image 2025-07-25 17:13:54 +08:00
Steven Qiu
064b0967fc
chore: complete Facade namespaces in use statements 2025-07-02 19:29:19 +08:00
Steven Qiu
9f4c59abec
chore: no .DS_Store [skip ci] 2025-07-02 19:29:19 +08:00
Steven Qiu
d8547a0a3d
refactor: use Intervention/Image to sanitize textures 2025-07-02 19:12:46 +08:00
Steven Qiu
9c51bd602b chore: remove redundant VSCode launch profile 2025-06-29 16:50:14 +08:00
Steven Qiu
bc3f504ca3 chore: ci 2025-06-29 16:50:14 +08:00
Steven Qiu
761cbb7828
feat: max texture width & texture sanitize (#662)
* feat: sanitize uploaded file when user upload texture

* feat: limit max texture width to avoid png bomb

* style: apply php-cs-fixer fixes

* chore: set default value for max_texture_width option

* Update skinlib.yml

Co-authored-by: Pig Fang <g-plane@hotmail.com>

---------

Co-authored-by: Pig Fang <g-plane@hotmail.com>
2025-06-29 16:09:55 +08:00
Steven Qiu
01fe3eb4cb
chore: set filename for ci snapshot build artifact 2025-06-28 17:48:16 +08:00
Steven Qiu
f03dd8122b
chore: remove redundant command in ci 2025-06-28 17:47:28 +08:00
Steven Qiu
cfda2a6bf8
style: apply php-cs-fixer fixes 2025-06-28 06:17:40 +08:00
Steven Qiu
74ce668221
fix: phpunit test 2025-06-28 06:16:49 +08:00
Steven Qiu
5a18d24464
fix: db exception in tests 2025-06-28 03:46:17 +08:00
Steven Qiu
5125862f80
fix: unexpected db query during composer install 2025-06-27 19:23:20 +08:00
Steven Qiu
fa791857ec
fix: ci snapshot build 2025-06-26 21:47:35 +08:00
Steven Qiu
24ad29ea99
style: apply php-cs-fixer fixes 2025-06-26 21:16:56 +08:00
Steven Qiu
cdfb972bd0 fix: scopes missing after cache clear 2025-06-26 21:15:53 +08:00
Zephyr Lykos
1985ce6ff8
config: switch default registry 2025-06-25 22:57:43 +08:00
Steven Qiu
d84eb65d55
Handle null route when request is handled by middleware 2025-06-22 21:50:57 +08:00
Steven Qiu
16474fb5d0
Remove locale cookie for API requests (#660)
* Do not set locale cookie for API requests

https://t.me/blessing_skin/184899

* Remove redundant code
2025-06-22 17:48:48 +08:00
Jerry
186138b884
Fix Netlify links (#656)
* Update README-zh.md

fix: wrong link

* Fix Netlify link

---------

Co-authored-by: Steven Qiu <tnqzh123@littlesk.in>
2025-06-22 17:40:50 +08:00
Zephyr Lykos
9ca6e37e39
chore: update deps 2025-01-18 16:44:33 +08:00
Steven Qiu
866e182c31
Fix #583 (#584) 2024-01-12 20:44:58 +08:00
Zephyr Lykos
739b9c97d7
tests: fix CheckRoleTest warning 2024-01-12 20:41:37 +08:00
Zephyr Lykos
0a43c5aa67
style: format code 2024-01-12 20:37:11 +08:00
Zephyr Lykos
35bd4524e6
ci: bump snapshot builds to PHP 8.2 2024-01-12 20:35:16 +08:00
Zephyr Lykos
eba91d5f1d
chore: update frontend deps 2024-01-12 20:31:53 +08:00
Zephyr Lykos
d764836244
ci: update deps 2024-01-12 20:31:37 +08:00
Zephyr Lykos
b1b4a71c3e
chore: update deps 2024-01-12 20:24:47 +08:00
Zephyr Lykos
fb74b43d64
upgrade dependencies 2023-08-26 14:29:53 +08:00
Zephyr Lykos
2a24506c15
config: switch default registry to CloudFront 2023-07-22 15:10:29 +08:00
Asnxthaony
f436c056e0
fix: load PluginServiceProvider first 2023-05-31 16:47:56 +08:00
Asnxthaony
77b58de7ef
update to checkout@v3 2023-05-30 17:57:08 +08:00
Asnxthaony
f5a5367e3e
fix ci 2023-05-30 17:42:45 +08:00
Asnxthaony
b811a7ae41
BREAKING: increase PHP version requirement to 8.1.0 2023-05-30 16:43:57 +08:00
Asnxthaony
d96dff1144
revert auto load Console commands 2023-05-30 15:09:39 +08:00
Asnxthaony
169ca11030
BREAKING: get ready for Laravel 10 2023-05-30 14:56:27 +08:00
Asnxthaony
b25b7588a9
use twig 3 & upgrade dependencies 2023-05-30 13:19:17 +08:00
Asnxthaony
46cccdb708
feat: generate source maps for production 2023-05-27 00:39:09 +08:00
Asnxthaony
7d9b34ecc9
fix: allow open CLI from anywhere 2023-05-27 00:37:47 +08:00
Asnxthaony
a13879f2df
fix: use unique routes name 2023-05-27 00:36:01 +08:00
Asnxthaony
1e8c0f85d5
upgrade dependencies 2023-05-26 23:46:10 +08:00
Tianle Xu
2a181c9a77
fix(i18n): typo in en/auth.yml (#527) 2023-05-06 00:36:17 +08:00
Haowei Wen
1698f1162e fix ci 2023-02-10 21:28:31 +00:00
Haowei Wen
cba50ecc59 Make skin viewer resizable 2023-02-10 20:35:30 +00:00
Haowei Wen
908f191ac8 Fix sending request to /textures/undefined 2023-02-10 19:30:04 +00:00
Haowei Wen
5a5dbea05b Upgrade skinview3d 2023-02-10 06:48:55 +08:00
Haowei Wen
950f2e6c7a Update fontawesome to 6.3.0 2023-02-09 20:13:05 +00:00
Haowei Wen
ec7be1bf63 Fix dev container 2023-02-09 19:48:30 +00:00
Pig Fang
5c4afe8f80
update middlewares 2023-01-29 11:59:30 +08:00
Pig Fang
ececb3486b
upgrade PHP dependencies 2023-01-29 11:51:06 +08:00
Pig Fang
bebdea9c20
user menu can be controlled by plugins 2023-01-26 09:37:23 +08:00
Pig Fang
b8188c91be
remove meta.js file 2023-01-25 12:01:29 +08:00
Pig Fang
0b516d02c5
clean up 2023-01-25 11:48:46 +08:00
Pig Fang
82dd87703f
first argument of option() function is required 2023-01-25 11:45:33 +08:00
Pig Fang
63b828d46b
remove Workbox 2023-01-24 18:22:16 +08:00
Pig Fang
7d005f7e4a
update test 2023-01-17 10:09:44 +08:00
Pig Fang
5750c1ae94
add test 2023-01-17 10:08:04 +08:00
Pig Fang
7eff0cb66d
update Composer lock file 2023-01-17 10:05:39 +08:00
Pig Fang
eff859a864
update php-cs-fixer config & apply fixes 2023-01-16 23:15:41 +08:00
Pig Fang
64dea61ec9
refactor 2023-01-16 23:06:31 +08:00
Pig Fang
0b169df679
fix running php-cs-fixer 2023-01-16 23:02:44 +08:00
Pig Fang
e72b68f8d6
disable PHP 8.0 in CI temporarily 2023-01-16 22:58:37 +08:00
Pig Fang
f1414c1dcb
add PHP 8.2 in CI 2023-01-16 22:56:27 +08:00
Pig Fang
7086098536
fix #463 2023-01-16 22:54:49 +08:00
Pig Fang
fb35d58a65
update Composer lock file 2023-01-16 22:53:08 +08:00
薄荷牛奶萌
9120efe1c6
The interface has been modified. (#514)
Co-authored-by: Pig Fang <g-plane@hotmail.com>
Co-authored-by: Zephyr Lykos <git@mochaa.ws>
2023-01-11 12:58:37 +08:00
Asnxthaony
564672b436
fix: allow logout when user are banned 2022-12-29 12:06:58 +08:00
Pig Fang
cc8873ec31
New translations setup.yml (Chinese Simplified) (#510) 2022-12-10 10:08:44 +08:00
Pig Fang
fa90b96682
bundle Font Awesome 2022-10-08 15:58:14 +08:00
Pig Fang
f1c7845b8d
New translations admin.yml (Japanese) (#479) 2022-09-29 22:58:56 +08:00
Pig Fang
be244cde60
New translations user.yml (Chinese Simplified) (#469) 2022-08-21 15:02:10 +08:00
bingling_sama
2dc2409d3f
fix: email verification resend link visibility on dark mode (#461) 2022-08-05 14:26:11 +08:00
Pig Fang
32fa6a8a52
New Crowdin updates (#455)
* New translations auth.yml (Spanish)

* New translations options.yml (Spanish)

* New translations user.yml (Spanish)
2022-08-05 14:24:03 +08:00
mochaaP
ec790b41f1
chore(bootstrap): remove environment check
This is not necessary as we should leave troubleshooting to users themselves. It's not our responsibility to warn about misconfigurations.
2022-07-14 03:31:52 +08:00
Pig Fang
b8337bed19
New translations user.yml (Chinese Traditional) (#439) 2022-07-13 01:14:24 +08:00
mochaaP
73e308f15a fix(admin): chart title color on dark mode 2022-07-11 21:16:47 +00:00
mochaaP
3e50237846 Revert "chore(gitpod): private port by default"
This reverts commit b7c3e06590.
2022-07-11 21:05:33 +00:00
Pig Fang
33b64625d9
New translations auth.yml (Spanish) (#436) 2022-07-12 04:15:52 +08:00
Pig Fang
c2c6ef8e51
New Crowdin updates (#432)
* New translations user.yml (French)

* New translations user.yml (Spanish)

* New translations user.yml (German)

* New translations user.yml (Greek)

* New translations user.yml (Italian)

* New translations user.yml (Japanese)

* New translations user.yml (Korean)

* New translations user.yml (Dutch)

* New translations user.yml (Portuguese)

* New translations user.yml (Russian)

* New translations user.yml (Chinese Simplified)

* New translations user.yml (Chinese Traditional)
2022-07-06 14:00:13 +08:00
Haowei Wen
a153753dc5 fix typo 2022-07-06 13:43:14 +08:00
Asnxthaony
e965a53c18
feat(closet): add sanity check on closet management 2022-07-05 17:09:06 +08:00
Asnxthaony
1a7f76ea69
fix(skinlib): cast allow_downloading_texture to boolean 2022-07-04 19:11:05 +08:00
mochaaP
fe7307d655
chore(docs): add requirement for OpenSSL 1.1.1 [skip ci] 2022-06-28 14:56:24 +08:00
mochaaP
411f85b43b
chore(docs): add requirement for OpenSSL 1.1.1 2022-06-28 14:55:18 +08:00
mochaaP
4303d14e64
chore: remove stale bot 2022-06-27 11:39:46 +08:00
mochaaP
b7c3e06590 chore(gitpod): private port by default 2022-06-23 10:19:33 +00:00
mochaaP
8fd8b806b1 Revert "fix(test): disable concurrency"
This reverts commit b190ffb3d2.
2022-06-23 09:18:57 +00:00
mochaaP
2786626a3f fix: text overflow on home page 2022-06-23 08:39:43 +00:00
mochaaP
8279817d8c chore: switch default marketplace source 2022-06-23 08:19:46 +00:00
mochaaP
b737a65f5f Bump version to 6.0.2 2022-06-23 07:58:55 +00:00
mochaaP
01f3bd4eea Revert "fix: update lockfile"
This reverts commit b35a526d7b.
2022-06-23 07:55:23 +00:00
mochaaP
a8f2f64260 chore(ci): frozen yarn lockfile on test 2022-06-23 07:43:05 +00:00
mochaaP
b190ffb3d2 fix(test): disable concurrency 2022-06-23 07:19:50 +00:00
mochaaP
3686f6a2bd fix(ci): yarn cache 2022-06-23 07:16:26 +00:00
mochaaP
b35a526d7b fix: update lockfile 2022-06-23 07:13:58 +00:00
mochaaP
e7d4eaa69a Revert "chore: bump dependencies"
This reverts commit aa55856d91.
2022-06-23 07:03:53 +00:00
mochaaP
d3599b1a1c fix(test): workaround race condition 2022-06-23 07:00:38 +00:00
mochaaP
aa55856d91 chore: bump dependencies 2022-06-23 06:31:10 +00:00
mochaaP
ac63dc368c chore: fixed width icons 2022-06-23 06:02:11 +00:00
mochaaP
c81fd58ec4 chore: align checkbox with title 2022-06-23 06:01:24 +00:00
mochaaP
f0a692f492 fix: card color on dark mode 2022-06-23 06:00:47 +00:00
mochaaP
68701d1b3c fix: export global with Object.assign 2022-06-21 15:07:47 +00:00
mochaaP
190642224d fix: running on gitpod 2022-06-21 15:07:22 +00:00
mochaaP
6ebe817b47
Bump version to 6.0.1 2022-06-19 16:01:46 +08:00
mochaaP
0f31756643 fix: export globals
This reverts commit 899de768f0.
2022-06-17 07:49:13 +00:00
mochaaP
f90a8c5371 fix: jQuery export 2022-06-17 07:38:37 +00:00
mochaaP
0394597b52 chore: use project typescript in vscode 2022-06-17 07:21:31 +00:00
mochaaP
9384e7dde6 chore: migrate to gp ports wait 2022-06-17 07:21:06 +00:00
mochaaP
eca2dffc31 fix: remove gitpod config from tsconfig 2022-06-17 14:03:20 +08:00
mochaaP
cca94c85ad chore: move in gitpod config 2022-06-17 13:47:29 +08:00
mochaaP
899de768f0 fix: export globals 2022-06-17 13:24:52 +08:00
mochaaP
74db97c644 Revert "expose React globals"
This reverts commit ccbb4f42f0.
2022-06-17 13:20:49 +08:00
Pig Fang
ccbb4f42f0
expose React globals
fix bs-community/blessing-skin-plugins#145
2022-06-15 14:16:37 +08:00
Pig Fang
e57038256c Bump version to 6.0.0 2022-06-13 17:41:08 +08:00
graywolf
663d9120b6
feat: sort closet by desc (#412)
for consistency with skinlib
2022-06-03 20:11:59 +08:00
Pig Fang
205f89bfc5
New translations setup.yml (Japanese) (#391) 2022-04-20 14:06:34 +08:00
mochaaP
7b267497c0
fix(build): composer autoload 2022-04-18 22:21:29 +08:00
mochaaP
c0112c7e25
chore(ci): manual notification push 2022-04-17 05:32:39 +08:00
mochaaP
81b7d0d7b7
feat(ci): snapshot builds 2022-04-17 03:43:18 +08:00
mochaaP
d4ea48c904
chore: make typescript happy (explicit any) 2022-04-17 02:33:36 +08:00
mochaaP
e4d35ad012
feat: re-add docker build support (#395)
also fixed failed ci
2022-04-17 02:23:48 +08:00
mochaaP
2452ed06da
chore: new copyright footer options (#392)
Squashed commit of the following:

commit ea640e77447e5a120f679c0b8f27d048487dd560
Author: Cinnamoroll-Rabbit <101342651+Cinnamoroll-Rabbit@users.noreply.github.com>
Date:   Sat Apr 16 23:16:00 2022 +0800

    chore: new copyright footer options
2022-04-17 02:17:54 +08:00
Pig Fang
b738b388ae
New Crowdin updates (#390) 2022-04-15 11:46:44 +08:00
Pig Fang
157994aaea
New translations options.yml (Russian) (#388) 2022-04-10 09:31:57 +08:00
Asnxthaony
c2a6ba9570
fix webpack config 2022-03-26 02:07:55 +08:00
Pig Fang
5596c636b7
New Crowdin updates (#383) 2022-03-24 08:30:37 +08:00
Pig Fang
5fc5c2c8c2
remove usage of jsDelivr
fix #369
fix #372
fix #375
2022-02-16 17:57:01 +08:00
Pig Fang
424ab20db1
fix possible React Hooks issues 2022-02-13 12:09:08 +08:00
Pig Fang
8b3376bf9b
refactor: use facade instead of service container 2022-02-13 12:05:39 +08:00
Pig Fang
ca42d65d1c reset page when changing skin library search query
fix #362
2022-02-12 18:11:48 +08:00
Asnxthaony
ab58c86695
reset page to 1 on new search 2022-02-12 18:04:49 +08:00
Pig Fang
3ebaf81880
fix loading AdminLTE dependencies 2022-02-12 17:57:47 +08:00
Pig Fang
8ecfc076fd
add pre-commit hook to format code 2022-02-12 17:37:16 +08:00
Pig Fang
fce1e8874f
apply Prettier fixes 2022-02-12 17:35:09 +08:00
Pig Fang
192c968bae
fix tests 2022-02-12 17:31:17 +08:00
Pig Fang
04065aa427
upgrade AdminLTE 2022-02-12 17:26:38 +08:00
Pig Fang
1cfbbd6967
fix WebP compatibility 2022-02-07 09:19:48 +08:00
Pig Fang
e4bd8a7702
Bump version to 6.0.0-rc.2 2022-02-05 22:16:25 +08:00
Pig Fang
b42f87eaa2
check prerelease when releasing 2022-02-05 22:14:08 +08:00
Pig Fang
7de23b6652
fix WebP compatibility 2022-02-05 22:04:17 +08:00
Pig Fang
b8205cd71f
apply Prettier fixes 2022-02-05 16:24:37 +08:00
Pig Fang
e0bbc2f3f5
fix tests 2022-02-05 16:16:41 +08:00
Pig Fang
5579bed8f0
upgrade skinview3d 2022-02-05 15:02:58 +08:00
Pig Fang
1c105629fc
simplify VS Code extensions recommendation [skip ci] 2022-02-04 22:54:21 +08:00
Pig Fang
1b55037db6
add npm script for type checking 2022-02-03 18:00:08 +08:00
Pig Fang
69e54d3198
fix logo in readme [skip ci] 2022-01-05 11:19:45 +08:00
Pig Fang
11a2c602ee
fix rendering content policy 2021-12-14 23:23:13 +08:00
Pig Fang
72be88b494
remove an extension recommendation 2021-12-14 23:12:12 +08:00
Pig Fang
2530f13f60
add PHP 8.1 to CI 2021-12-12 19:41:08 +08:00
Pig Fang
89db8299bb Bump version to 6.0.0-rc.1 2021-12-12 19:36:55 +08:00
Pig Fang
1b00600c3b
BREAKING: increase PHP version requirement to 8.0.2 2021-12-12 19:20:06 +08:00
Pig Fang
b2c5cca240
remove PHP 7.4 from CI 2021-12-12 18:15:21 +08:00
Pig Fang
efa20f4940
fix test 2021-12-12 18:13:39 +08:00
Pig Fang
faea0126ea
apply php-cs-fixer fixes 2021-12-12 17:59:27 +08:00
Pig Fang
88b5da401a
upgrade PHP dependencies 2021-12-12 10:58:08 +08:00
mochaaP
f3d1b196ff
fix: build on node 17 2021-11-12 14:54:22 +08:00
Pig Fang
0276d2a35b
bump version to 6.0.0-beta.4 2021-10-31 15:26:58 +08:00
Pig Fang
e253e7bff5
upgrade dependency (fix #311) 2021-10-31 15:07:47 +08:00
Pig Fang
09b7f949b7
update sponsors and backers 2021-09-04 17:19:49 +08:00
Pig Fang
07c79da198
ignore size limit warning 2021-08-23 15:35:14 +08:00
Pig Fang
e89b65afdf
fix chart data (fix #336) 2021-08-22 19:27:34 +08:00
Pig Fang
6db0a0adeb
change date format of chart in admin panel 2021-08-22 18:14:56 +08:00
Pig Fang
458791df14
New Crowdin updates (#335) 2021-08-22 17:21:29 +08:00
Pig Fang
3f7ba49c2c
fix player name rule 2021-08-22 16:45:26 +08:00
Pig Fang
2c579a4043
fix chart data 2021-08-22 12:47:25 +08:00
Pig Fang
d98beae262
tweak charts of admin panel 2021-08-22 12:04:13 +08:00
Pig Fang
014864e47d
change font of usage in admin panel 2021-08-22 11:06:55 +08:00
Pig Fang
b5eecc837d
New Crowdin updates (#329) 2021-08-22 10:58:46 +08:00
Pig Fang
e0d6403499
apply Prettier fixes 2021-08-22 10:57:45 +08:00
Pig Fang
79ec6e206a
fix duplicated logout modal (fix #332) 2021-08-22 10:50:58 +08:00
Pig Fang
e8ec0c4c0d
remove "url-loader" and "file-loader" 2021-08-22 10:39:41 +08:00
Pig Fang
61f0bf174f
report webpack warnings 2021-08-21 23:06:23 +08:00
Pig Fang
ff017573c3
use lock icon to indicate a private texture 2021-08-19 23:23:02 +08:00
Pig Fang
c12c1fbd01
make whole card of skin library item clickable 2021-08-19 23:15:56 +08:00
Pig Fang
7512eeb465
upgrade blessing/texture-renderer to accept fixes 2021-08-15 15:05:38 +08:00
Pig Fang
2b4364f2f7
New Crowdin updates (#322) 2021-08-11 23:27:02 +08:00
Pig Fang
4044e50aa5
enable Russian translations option 2021-08-08 13:29:56 +08:00
Pig Fang
d8a02a6a46
New Crowdin updates (#319)
Finished Russian.
2021-08-08 13:16:00 +08:00
Asnxthaony
61ce97ad79
use cached scopes 2021-08-07 13:20:47 +08:00
Pig Fang
a79cb1c639
update issue templates config 2021-07-30 14:46:26 +08:00
Pig Fang
e1262b4179
update issue templates 2021-07-30 14:41:27 +08:00
Asnxthaony
20e299e6c3
fix i18n 2021-07-30 06:27:57 +08:00
Asnxthaony
1f24e75f76
added missing service provider 2021-07-27 08:06:13 +08:00
Pig Fang
861412dcfb
update stale label 2021-07-26 09:38:08 +08:00
Pig Fang
38a6e436a9
New Crowdin updates (#303) 2021-07-25 13:26:40 +08:00
Pig Fang
4875564ee4
apply php-cs-fixer fix 2021-07-25 13:26:13 +08:00
Pig Fang
baf4921479
new player name rule: allow UTF-8 2021-07-25 13:14:08 +08:00
Pig Fang
1b0e9ae8d5
add stale bot 2021-07-25 11:43:58 +08:00
dz_paji
b886556137
Update auth.yml (#309) 2021-07-15 20:35:30 +08:00
Pig Fang
12801e5f04 Bump version to 6.0.0-beta.3 2021-07-03 15:42:21 +08:00
Pig Fang
ab8d1a970c
New Crowdin updates (#297) 2021-06-21 09:25:14 +08:00
Pig Fang
3ef1b249f4 update readme 2021-06-07 09:56:36 +08:00
Pig Fang
46c0119c7f fix dependencies 2021-06-07 09:54:29 +08:00
Pig Fang
35963a7e70 Revert "upgrade PHP dependencies"
This reverts commit 7388e46a2d.
2021-06-07 09:49:32 +08:00
Pig Fang
7388e46a2d upgrade PHP dependencies 2021-06-07 09:39:58 +08:00
Pig Fang
af76ac1a6b
fix color in dark mode 2021-06-06 22:41:02 +08:00
Pig Fang
073da66623
support toggling dark mode 2021-06-06 18:07:08 +08:00
Pig Fang
9c54427b08
add IDE helper 2021-06-06 17:32:35 +08:00
Pig Fang
b941210d3c
fix TypeScript errors 2021-06-06 14:33:28 +08:00
Pig Fang
7e04f72292
support dark mode UI 2021-06-06 14:27:21 +08:00
Pig Fang
1ad19878f8
upgrade AdminLTE 2021-06-05 20:05:12 +08:00
Pig Fang
09bba22227
upgrade Prettier 2021-06-05 17:11:42 +08:00
Pig Fang
7381699690
upgrade Jest and TypeScript 2021-06-05 17:10:41 +08:00
Pig Fang
ed82469f88
upgrade webpack-related dependencies 2021-06-05 16:14:39 +08:00
Pig Fang
c9838025f6
New Crowdin updates (#288) 2021-05-22 16:37:39 +08:00
Pig Fang
c4e292c877
fix resolving report with non-existing reporter 2021-05-04 18:32:35 +08:00
Pig Fang
b7af1ebf19
fix: check texture size for capes 2021-05-04 18:20:24 +08:00
Pig Fang
6226784b10
apply php-cs-fixer fixes 2021-05-04 18:17:45 +08:00
Pig Fang
5550b877df
add php-cs-fixer as dev dependency 2021-05-04 18:17:01 +08:00
Pig Fang
bb0a1dbc40
upgrade composer/semver 2021-05-04 18:05:39 +08:00
Pig Fang
8813e58269
re-enable PHP 8.0 on CI 2021-05-02 11:40:05 +08:00
Pig Fang
9cf1269798
upgrade PHP dependencies 2021-05-02 11:35:41 +08:00
Pig Fang
14fd06a11f
fix tests 2021-05-02 11:35:28 +08:00
Pig Fang
332b8cf7ef
remove unnessary "skip-ci" detection 2021-05-02 11:23:09 +08:00
Pig Fang
0a74ddd903
fix linting error 2021-05-02 11:22:11 +08:00
Pig Fang
4ccee9e47e
update sponsors and backers 2021-05-02 11:13:44 +08:00
Asnxthaony
387fe81a60
feat: OAuth scope (#287)
Co-authored-by: Pig Fang <g-plane@hotmail.com>
2021-04-18 15:31:57 +08:00
Pig Fang
5fb4240a47
fix page isn't updated when switching category in closet
fix #284
2021-04-03 10:14:21 +08:00
Pig Fang
6f638dd9fc
fix tests 2021-04-03 09:42:24 +08:00
Pig Fang
2b474437f0
don't switch category when click current closet tab
fix #279
2021-04-03 09:24:01 +08:00
Pig Fang
25a93d79b7
upgrade testing-library 2021-04-03 09:04:19 +08:00
Pig Fang
49df9d92a0
New Crowdin updates (#267) 2021-02-20 10:59:34 +08:00
Pig Fang
97057bc432
fix order of loading front-end l10n file 2021-02-18 09:43:23 +08:00
Pig Fang
fa3a3a16cb
upgrade Jest 2021-02-18 09:39:30 +08:00
Pig Fang
1db946e372
fix panic on empty notification content 2021-02-17 22:40:02 +08:00
Pig Fang
de1f596401
Bump version to 6.0.0-beta.2 2021-02-17 16:04:27 +08:00
Pig Fang
460153104c
upgrade "blessing/texture-renderer" 2021-02-17 16:01:13 +08:00
Pig Fang
28b80278ba
upgrade Laravel 2021-02-17 10:32:32 +08:00
Pig Fang
45a854450e
swap readme default language 2021-02-17 10:25:54 +08:00
Pig Fang
ad6eabff0d
update sponsors 2021-02-15 11:07:12 +08:00
Pig Fang
7be5a4ea62
Bump version to 6.0.0-beta.1 2021-02-15 10:58:56 +08:00
Pig Fang
89bb2b4db9
reject single-layer alex texture 2021-02-13 15:19:12 +08:00
Pig Fang
6f97c1efcc
remove usage of iconv 2021-01-30 17:31:43 +08:00
Pig Fang
eae6ff887c
remove "tymon/jwt-auth" package 2021-01-30 16:43:14 +08:00
Pig Fang
4020571e53
enable "zh-TW" option 2021-01-30 13:00:50 +08:00
Pig Fang
d229cb1190
upgrade webpack-related deps 2021-01-30 12:54:14 +08:00
Pig Fang
9a88570387
New Crowdin updates (#255) 2021-01-30 11:58:55 +08:00
Pig Fang
03076a9405
New Crowdin updates (#244) 2021-01-02 16:48:31 +08:00
Pig Fang
f2ddcb6ce4
use React 17 new JSX runtime 2021-01-02 16:38:18 +08:00
Pig Fang
746832a9f6
upgrade TypeScript 2021-01-02 16:12:59 +08:00
Pig Fang
928d6ffa8c
Revert "add PHP 8.0 tests on CI"
This reverts commit 2b620c3338.
2021-01-02 16:06:02 +08:00
Pig Fang
d5fd4e4c1b
upgrade PHP dependencies 2021-01-02 16:04:38 +08:00
Pig Fang
2b620c3338
add PHP 8.0 tests on CI 2021-01-02 15:55:32 +08:00
Pig Fang
4ae6368931
run php-cs-fixer 2021-01-02 15:51:37 +08:00
Pig Fang
64f2beda02
upgrade PHP dependencies (fix #247) 2021-01-01 19:03:58 +08:00
Steven Qiu
5b7bf8e7aa
Correct a word 2020-12-24 09:34:19 +08:00
Pig Fang
84c544b683
New Crowdin updates (#243) 2020-11-08 16:41:05 +08:00
Pig Fang
34c90b50f2
upgrade to webpack 5
Web CLI update:
* commander -> cac
* enquirer -> prompts
2020-11-06 15:37:45 +08:00
Pig Fang
b07e227e91
upgrade webpack loaders and plugins 2020-11-04 14:31:32 +08:00
Pig Fang
7e8f7a4a68
tweak CSS minification 2020-11-04 11:34:36 +08:00
Pig Fang
d2ffb5b9bf
fix test 2020-10-31 10:54:50 +08:00
Pig Fang
66eb658410
generate asset tags at compile time 2020-10-31 10:43:47 +08:00
Pig Fang
17ad007d40
scripts -> tools 2020-10-30 10:11:50 +08:00
Pig Fang
2163356f7e
remove Composer --no-suggest flag 2020-10-30 10:04:51 +08:00
Pig Fang
4731fc0940
fix prefetch 2020-10-26 16:12:09 +08:00
Pig Fang
3669f18e6f
update sponsors 2020-10-26 11:37:32 +08:00
Pig Fang
cf4ebfeaff
upgrade React to 17 2020-10-26 11:22:44 +08:00
Restent Ou
e9dc9ab32f
fix broken link in readme (#241) 2020-10-25 20:05:56 +08:00
Pig Fang
43d410081e
run fmt 2020-10-25 17:46:40 +08:00
Pig Fang
ddb939e562
upgrade front-end tests related deps 2020-10-25 17:35:27 +08:00
Pig Fang
d3ebd0432e
enable React strict mode 2020-10-25 10:55:58 +08:00
Pig Fang
9bfc0e6076
don't allow to render avatar for non-skin texture 2020-10-18 12:12:28 +08:00
Pig Fang
311b0690fc
upgrade Laravel to 8 2020-10-14 11:56:34 +08:00
Pig Fang
a5921770f0
use PHP 7.4 syntaxes 2020-10-14 09:48:45 +08:00
Pig Fang
0959ec02d1
tweak CI 2020-10-13 11:10:49 +08:00
Pig Fang
2c226fb1fd
upgrade PHPUnit 2020-10-13 11:06:35 +08:00
Pig Fang
986a64fe71
require PHP 7.4 2020-10-13 10:10:03 +08:00
Pig Fang
59f7bc106d
update backers 2020-10-13 09:52:34 +08:00
385 changed files with 24017 additions and 19649 deletions

View File

@ -0,0 +1,45 @@
APP_DEBUG=true
APP_ENV=development
APP_FALLBACK_LOCALE=en
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blessingskin
DB_USERNAME=username
DB_PASSWORD=secret
DB_PREFIX=
# Hash Algorithm for Passwords
#
# Available values:
# - BCRYPT, ARGON2I, PHP_PASSWORD_HASH
# - MD5, SALTED2MD5
# - SHA256, SALTED2SHA256
# - SHA512, SALTED2SHA512
#
# New sites are *highly* recommended to use BCRYPT.
#
PWD_METHOD=BCRYPT
APP_KEY=base64:JaytOHG/JlLgulTVAhiS0tRqnAfCkQydbdP6VRmoAMY=
MAIL_MAILER=smtp
MAIL_HOST=
MAIL_PORT=465
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=
MAIL_FROM_ADDRESS=
MAIL_FROM_NAME=
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_CONNECTION=sync
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PLUGINS_DIR=null
PLUGINS_URL=null

26
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,26 @@
# [Choice] PHP version (use -bullseye variants on local arm64/Apple Silicon): 8, 8.1, 8.0, 7, 7.4, 7.3, 8-bullseye, 8.1-bullseye, 8.0-bullseye, 7-bullseye, 7.4-bullseye, 7.3-bullseye, 8-buster, 8.1-buster, 8.0-buster, 7-buster, 7.4-buster
ARG VARIANT=8-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/php:0-${VARIANT}
# Install MariaDB client
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get install -y mariadb-client zlib1g-dev libpng-dev libzip-dev libwebp-dev \
&& apt-get clean -y && rm -rf /var/lib/apt/lists/*
# Install php-mysql driver
RUN docker-php-ext-install mysqli pdo pdo_mysql gd zip
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# Enable Apache rewrite module
RUN a2enmod rewrite
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

View File

@ -0,0 +1,13 @@
Listen 8080
<Directory /workspace/public/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
<VirtualHost *:8080>
DocumentRoot /workspace/public
ErrorLog /workspace/storage/logs/apache-error.log
CustomLog /workspace/storage/logs/apache-access.log combined
</VirtualHost>

View File

@ -0,0 +1,34 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.231.6/containers/php-mariadb
// Update the VARIANT arg in docker-compose.yml to pick a PHP version
{
"name": "PHP & MariaDB (Community)",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
// Set *default* container specific settings.json values on container create.
"settings": {},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"xdebug.php-debug",
"bmewburn.vscode-intelephense-client",
"mrmlnc.vscode-apache"
],
// For use with PHP or Apache (e.g.php -S localhost:8080 or apache2ctl start)
"forwardPorts": [8080, 3306],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "sudo truncate -s 0 /etc/apache2/ports.conf && sudo rm -f /etc/apache2/sites-enabled/000-default.conf && sudo ln -sf /workspace/.devcontainer/blessing-skin.apache.conf /etc/apache2/sites-enabled/ && ln -sf .devcontainer/.env.devcontainer .env && composer install && yarn install",
// Start apache2 after the container is started
"postStartCommand": "apache2ctl start && echo '\\n👉 \\e[0;32mPlease run '\\'yarn build\\'' to build the frontend. Application is available on port 8080.\\e[0m 👈\\n'",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",
"features": {
"powershell": "latest"
}
}

View File

@ -0,0 +1,47 @@
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
# Update 'VARIANT' to pick a version of PHP version: 8, 8.1, 8.0, 7, 7.4
# Append -bullseye or -buster to pin to an OS version.
# Use -bullseye variants on local arm64/Apple Silicon.
VARIANT: "8-bullseye"
# Optional Node.js version
NODE_VERSION: "lts/*"
volumes:
- ..:/workspace:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db
# Uncomment the next line to use a non-root user for all processes.
# user: vscode
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
db:
image: mariadb:10.4
restart: unless-stopped
volumes:
- mariadb-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: mariadb
MYSQL_DATABASE: blessingskin
MYSQL_USER: username
MYSQL_PASSWORD: secret
# Add "forwardPorts": ["3306"] to **devcontainer.json** to forward MariaDB locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
volumes:
mariadb-data:

View File

@ -1,6 +1,9 @@
.git/
.github/
.vscode/
.idea/
.cache/
.cache-loader/
coverage/
node_modules/
plugins/**
@ -8,7 +11,13 @@ public/app/*
public/lang/*
public/plugins/**
resources/assets/tests/
scripts/
storage/*.db
storage/*.sqlite
storage/insane-profile-cache
storage/oauth-public.key
storage/oauth-private.key
storage/install.lock
storage/options.php
storage/debugbar
storage/framework/cache/**
storage/framework/sessions/**
@ -17,24 +26,34 @@ storage/framework/views/**
storage/logs/**
storage/packages/**
storage/textures/*
storage/update_cache/*
target/
tests/
vendor/
vendor/*
_ide_helper.php
.dockerignore
.editorconfig
.env
.env.testing
.eslintignore
.eslintrc.yml
.gitignore
.php_cs.*
.php-cs-fixer.cache
.php-cs-fixer.dist.php
.phpstorm.meta.php
.phpunit.result.cache
.sass-cache
.uini
azure-pipelines.yml
crowdin.yml
docker-compose.yml
*.Dockerfile
Dockerfile*
index.html
junit.xml
phpunit.xml
README*.md
server.php
tsconfig.dev.json
tsconfig.eslint.json
yarn-error.log

View File

@ -2,9 +2,6 @@ APP_DEBUG=false
APP_ENV=production
APP_FALLBACK_LOCALE=en
ASSET_ENV=production
ASSET_URL=
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306

View File

@ -35,3 +35,71 @@ PLUGINS_URL=
TEXTURES_DIR=
JWT_SECRET=1tdM3gXarxYI4KlAHMBo238iC2tEb4I3EtBlZTQQXvInXIt7V2ix7hJ1KTvxCKZW
PASSPORT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC6q6SCprX3yfOE
DFBnfFk3R+33qvoe26nYQkavKfv7zA9KQxCBNHEsFKOQ6ui+ViebVHAIHBPm2518
REVMLN2JONvXbPETV6gJO/b6FFwo2Aow/GbTnesLhWEAPW11ei0/hBbjWF9hQZ/n
x3YsFk0xtml2iPDijfUohwp50iFyCQylw4S5Sy3vuVdM063dkxvECsU6wmHDev9C
PxFZGl3W2iLSwttYl7xmlwll8xuqxDQUJpJbOxrPeDKdDI1ikarSqA1c8bV1YLT+
CHxB7T5b1EPeaYRmeLl+wyd/ZxeBWWgDLBusi5wPFpSEIVxu1RzTYarOXEpD+XNV
Ohpb7LxpbJx8YRJru1B6CBouVO2pqoCEM21GtG7zSDNtaY+yVcj9Kf2SIbfz0e6a
SLAcyOT0q3aId4+z9q8TAfPVV7y0D7B3PaOz/3pMC+jHDjCRwQkN/DR7ODUKEjCd
UjsveGHtseBa3qBoKWcg39ZvwYeDF0P/cFa/yOqw5JxyWhbjk95YdPaSixdUyyMG
XkFTOrnBREVtBAfdTOG6WTFcIlyJK+ST0cJXVcrjFonbmJCCwTJqxz9t+935CfTt
CzLLPdONU16jZJ7j1cVoWb4L8o0OA+FBSawBxFOKyhFlh3+HKRnSCZSDqaSDsYUf
M3IupiGUcByhpZ9mNqhnLeivIXwapwIDAQABAoICAFu27F+S2DH0A9S7lh+aPV1H
VniKhVR2+aZ6va7fUmJ++n4yoB/TK82MIGcZu5uUyeXr4RVi8jZJbcF565BHNNtw
V7cq2/F0bmeHEkwBh9w7dRpnUIAlhS/GawfKpoaDLksYM4SkzUwECbQ/0GRN2sST
ipKGKtAtDihI3RFIeE1Ge/PPsdy2Ps4bAnUJRdHpLsmtvwSlL5JzUonyYawlI7jl
uRlTSqDnAFZpW+E+xjerKalC4EK5se0AceGuoqKszkCs98/UJCMVDigH9EER9sL4
chYLQtVz+DN7X+MdPDO9wThZygkHGPhi0DpxB7CevYhv4pN8TbLDE3Lq1ruWf2T3
7ts21ymjVIiOayBA9l86P0FSS/lP9KF53LyeNkOJKUy5On6xHoW5IKlrMJdmGwFH
B4yaR7bw5vxErhpMTzcYJVWqjCbo+PBJhdy7x+2XrrBLs9X0hfS/jeeAIuRlzju1
9xe3zO6U41sDjkCkrUavOn57DL6jh9LMgxT8cZkSdrP6rpawEyjPUi5kMbbQkv0j
eWiqz0vozJN5HcVpj36F5kqZyCnIojmeo4FCKdn7n/wvyGYQPSAekVpV80KzoJ2j
GQ440Q7Sgozj/Lw4cCPgG3/MA1Dwu+TUuaddFjBH2oqZ2X0bCqVCEVWhfmaD4z2R
I9C9nLvpxoMtcCHkG1VFAoIBAQDfawcBHMPxZQOy/qTqXZd7YR3bzGgd/SkLEWwg
PtDGKe36tDf3E/RGRij4HGl9v/fA7N/CufW0tJY7Ii/cn7yYdZg/dzaCoYIZbICl
CytWtsM0iH3XPuY3UpgsOwML0xK8hdD1U7qBKk5rnjF9Q9vKrB8ATR9hM0bPaGtr
Bqfx8A+kj6wqRpA8jbN0kZxJVv0/LgZSCrH3qUjHfXoYYtLhMBZd8UydyvyETo/1
Z44WS9oNqX8mBUvHjsuOQx3+eFPOr69QPIo06QMxytSEzMilgz40QUBWF68gKwGi
NYdUfR3IXVTmvJhYH2mQWqMKVk+KJFd2UanjKbBCOKrDSG4TAoIBAQDV5LMp/ztd
YQvMCWJzUrpazGqkoEGli/qxWb/pDpemgQT++lt6PBRQmmLKXZfNp9VJdZdP6+lF
ypGcA8tACY93m7Fk4wtewXG+0oTxmBkWqSiiO3ExoBQTxXLoZ9GwKt/exaM25QJ1
O2livxrYFFJbUe1YRqQENIURYk13RgeIaWS0gd9vp/yp0EhZAvGUPHFjKRsLjw7Y
gZDJ+lXj2pXg9THUzkhVDm+fM6blIsLcvf7qc8yKQI3nwZr00e/ba7xSNF8AhpdP
rxw59vm1RzsngZpZOK0Z1143gFRtGhhVWtvmhCvJo1EOssFu01ixE0bNq5nRIIJo
O/mY7NC9bCOdAoIBAEBfcyYz5pUwGM/DJTtN+i6XfeXt0HYLkn7Y50GnN7pRLHuW
36U2P6Tb5EQQ06hi3nzdA1/0+sG1Yq/pGsdD0zBOea6Xp8IdzQGMTMjBHhyfDkGd
rjyNqAF6r9PWsPsANx7Qo7N8C3nZ+bxyWSoRmkucKlaI4ii8gIOUP5cX1N4V4Dv3
FZEcwcRgw7srlU9gXBmPJk0PPdXxFcI8+if6mW4+z8MDmqLAcN+iT0JTMxJjipFz
K+qFjh8Smr4Dwqmme+dKoYXJ27yBAuWe3nrhElL2LL8bqfDkZBYtrgvRxotmfWVU
1vigkHibnGv2YZHB6qsP649w2jVUtq9t6m3X+bcCggEAEKDqCN7N17GewCsOm1aY
JEz2EXxf/iXGxJjsoYq/4XLwV35RNEyNa8LE4WSrU5KzszVQISd/CCz6av2khIL5
w1u4S9aW4LP7StGFAl9HvApEnXAvmaMPTIYyK70+gQqkQuZsjOz65vBKfiHLTXcu
++h/ojhDsgv/OF3DFf28wi8nZB0gqMaPjwghR8JB07trOUFN1/U0O0K/ZeRvXvp0
YnvNdvTejLZFmUPjuracHZsrwUBla24fWiAkEtprYkya5G0r4ZeVFd3QPPVlbmFu
SOD7heoxEuw6Z+gzKBQ6RhB9PguSd+eZeqINBbeqkoGkJIMtvyNe4AmhmvD2PXO1
xQKCAQAx12vD3q2+DHrpJ4fKGp1RbCxFIOYgXcBJ6zCozVZXpLUa54IBTLyQyDNF
D8+Wq5b1IbCnxBn55tsgq/CSLkVHigVfUDGnAt3lD+ggLdZmu7ayQY0BNe1quF5M
EgkOE0uYtq+2p+u4gxRB+gnQd1YIlPs0U0mrawbzV5ZX2vR5Ry1XpBno75JpnUbN
3D9DuIDVLuqsKJcx6KClWef2PzJB2uN7jDf4UnCtp5QCFB1GNt+5AzCNh9Bozas8
OflASrSn64AeyCZycCplmRY/F4BnH++7YI0Q/mjawByW7qYoHkFzmKuUcgakh200
y/ieeWy8Vunl0e4T4Bz/0zInBifn
-----END PRIVATE KEY-----"
PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuqukgqa198nzhAxQZ3xZ
N0ft96r6Htup2EJGryn7+8wPSkMQgTRxLBSjkOrovlYnm1RwCBwT5tudfERFTCzd
iTjb12zxE1eoCTv2+hRcKNgKMPxm053rC4VhAD1tdXotP4QW41hfYUGf58d2LBZN
MbZpdojw4o31KIcKedIhcgkMpcOEuUst77lXTNOt3ZMbxArFOsJhw3r/Qj8RWRpd
1toi0sLbWJe8ZpcJZfMbqsQ0FCaSWzsaz3gynQyNYpGq0qgNXPG1dWC0/gh8Qe0+
W9RD3mmEZni5fsMnf2cXgVloAywbrIucDxaUhCFcbtUc02GqzlxKQ/lzVToaW+y8
aWycfGESa7tQeggaLlTtqaqAhDNtRrRu80gzbWmPslXI/Sn9kiG389HumkiwHMjk
9Kt2iHePs/avEwHz1Ve8tA+wdz2js/96TAvoxw4wkcEJDfw0ezg1ChIwnVI7L3hh
7bHgWt6gaClnIN/Wb8GHgxdD/3BWv8jqsOSccloW45PeWHT2kosXVMsjBl5BUzq5
wURFbQQH3UzhulkxXCJciSvkk9HCV1XK4xaJ25iQgsEyasc/bfvd+Qn07Qsyyz3T
jVNeo2Se49XFaFm+C/KNDgPhQUmsAcRTisoRZYd/hykZ0gmUg6mkg7GFHzNyLqYh
lHAcoaWfZjaoZy3oryF8GqcCAwEAAQ==
-----END PUBLIC KEY-----"

View File

@ -8,6 +8,7 @@ extends:
- eslint:recommended
- plugin:@typescript-eslint/recommended
- plugin:@typescript-eslint/recommended-requiring-type-checking
- plugin:react-hooks/recommended
rules:
prefer-const: error
'@typescript-eslint/no-unsafe-assignment': off
@ -23,3 +24,4 @@ rules:
- off
- checksVoidReturn: false
'@typescript-eslint/unbound-method': off
'@typescript-eslint/restrict-template-expressions': off

View File

@ -46,7 +46,7 @@ location ~* \w+\.hot-update\.json$ {
}
```
`APP_ENV` 为其它值时,您需要事先执行 `pwsh ./scripts/build.ps1`。此命令将构建并压缩前端资源。通常用于生产环境。
`APP_ENV` 为其它值时,您需要事先执行 `pwsh ./tools/build.ps1`。此命令将构建并压缩前端资源。通常用于生产环境。
> 如果传递 `-Simple` 参数给 `build.ps1` 脚本,则只会运行 webpack 来编译代码,而不会复制首页背景以及生成 commit 信息。

View File

@ -1,36 +0,0 @@
<!-- 在提交一个新 issue 前,请先阅读以下内容: -->
<!-- Before opening a new issue, please make sure to READ the articles below: -->
<!-- * FAQ 常见问题: https://git.io/fjRtn -->
<!-- * 报告问题的正确姿势https://git.io/fjRtc -->
<!-- 把下面模板中的占位文字删除,并按照你的情况认真填写,谢谢 -->
<!-- Please remove the placeholders and fill in the template according to your situation. -->
## The Problem 问题描述
<!-- 如果您要报告的问题是与插件相关的,请在 bs-community/blessing-skin-plugins 提 issue谢谢合作 -->
<!-- If you're going to report a problem related to plugins, please open issue at bs-community/blessing-skin-plugins. -->
## Environment 运行环境
- Blessing Skin 版本 (Version of Blessing Skin):
- PHP 版本 (Version of PHP):
- Apache / Nginx:
- 什么浏览器,出现错误时的地址栏 URL 是什么 (Which browser and URL):
## Error Message 错误信息
<!--
请提供详细信息,如截图。日志内容请不要直接贴出来,请把它放在 pastebin 等网站上。
不提供详细信息的 issue 或不按要求提供日志的将被直接忽略,谢谢合作。
Please provide more information, such as screenshots.
For logs, don't paste it in issue directly. You can paste in on pastebin.
You will be ignored if you don't provide enough information or
your logs messes up the issue.
Thanks for your cooperation.
-->
## Steps to Reproduce 重现步骤
<!-- Tell us how to reproduce this issue. -->
<!-- 详细描述你出错前的操作步骤 -->

View File

@ -0,0 +1,71 @@
name: Bug 报告
description: 发起 bug 报告
body:
- type: markdown
attributes:
value: |
在报告问题之前,请确保您已经 **认真** 阅读:
- [FAQ](https://blessing.netlify.app/en/faq.html)
- [报告问题的正确姿势](https://blessing.netlify.app/report.html)
- type: input
id: bs
attributes:
label: Blessing Skin 版本
validations:
required: true
- type: dropdown
id: php
attributes:
label: PHP 版本
options:
- '7.3'
- '7.4'
- '8.0'
- '8.1'
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: 出现问题时所使用的浏览器
multiple: true
options:
- Firefox
- Chrome
- Safari
- Microsoft Edge
validations:
required: true
- type: dropdown
id: web-server
attributes:
label: 您正在使用的 Web Server
options:
- Nginx
- Apache
- type: checkboxes
id: baota
attributes:
label: 您正在使用宝塔吗?
options:
- label:
- type: textarea
id: what-happened
attributes:
label: 出现了什么问题?
description: 顺便告诉我们,您期望的行为是怎样的?
validations:
required: true
- type: textarea
id: logs
attributes:
label: 错误日志
description: 您可以粘贴 Blessing Skin 的日志或 Web Server 的日志。Blessing Skin 的日志位于 `storage/logs` 目录里。
render: text
- type: textarea
id: reproduction
attributes:
label: 重现步骤
description: 详细描述您出错前的操作步骤
validations:
required: true

64
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: Bug Report
description: File a bug report
body:
- type: markdown
attributes:
value: |
Please filing an issue, please make sure you've read:
- [FAQ](https://blessing.netlify.app/en/faq.html)
- type: input
id: bs
attributes:
label: Which version of Blessing Skin are you using?
validations:
required: true
- type: dropdown
id: php
attributes:
label: Which version of PHP are you using?
options:
- '7.3'
- '7.4'
- '8.0'
- '8.1'
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What browsers are you seeing the problem on?
multiple: true
options:
- Firefox
- Chrome
- Safari
- Microsoft Edge
validations:
required: true
- type: dropdown
id: web-server
attributes:
label: Which web server are you using?
options:
- Nginx
- Apache
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
validations:
required: true
- type: textarea
id: logs
attributes:
label: Error Logs
description: You can paste logs of Blessing Skin or your web server. Logs of Blessing Skin can be found at `storage/logs` directory.
render: text
- type: textarea
id: reproduction
attributes:
label: Steps to Reproduce
description: Tell us how to reproduce this issue.
validations:
required: true

20
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,20 @@
blank_issues_enabled: false
contact_links:
- name: Ask questions about using Blessing Skin
url: https://github.com/bs-community/blessing-skin-server/discussions
about: If you're not going to report a bug, please ask and answer questions there.
- name: Report Issue about Blessing Skin plugins
url: https://github.com/bs-community/blessing-skin-plugins/issues
about: Please ask and answer questions there.
- name: Report Issue about integrating with Flarum
url: https://github.com/bs-community/flarum-oauth-client/issues
about: Please ask and answer questions there.
- name: 询问关于使用 Blessing Skin 的问题
url: https://github.com/bs-community/blessing-skin-server/discussions
about: 如果您并不是要报告 bug请在那里进行讨论。
- name: 报告与 Blessing Skin 插件有关的问题
url: https://github.com/bs-community/blessing-skin-plugins/issues
about: 请在那里报告问题。
- name: 报告与 Flarum 对接有关的问题
url: https://github.com/bs-community/flarum-oauth-client/issues
about: 请在那里报告问题。

View File

@ -18,104 +18,138 @@ jobs:
php-lint:
name: PHP Linting
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'skip ci')"
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
coverage: none
extensions: mbstring, dom, fileinfo, gd
- name: Install dependencies
run: |
composer install --prefer-dist --no-progress --no-suggest
composer global require friendsofphp/php-cs-fixer
- name: Prepare
run: |
cp .env.example .env
php artisan key:generate
mkdir -p resources/views/overrides
- name: Validate Twig templates
run: php artisan twig:lint -v
- name: Check coding style
run: php-cs-fixer fix --dry-run --stop-on-violation --diff-format=udiff
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
coverage: none
extensions: mbstring, dom, fileinfo, gd, imagick
- name: Install dependencies
run: |
composer install --prefer-dist --no-progress
- name: Prepare
run: |
cp .env.example .env
mkdir -p resources/views/overrides
- name: Validate Twig templates
run: php artisan twig:lint -v
- name: Check coding style
run: ./vendor/bin/php-cs-fixer fix --dry-run --stop-on-violation --diff --format=txt
php:
name: PHP ${{ matrix.php }} Tests
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'skip ci')"
strategy:
fail-fast: false
matrix:
php: ['7.2', '7.3', '7.4']
php: ['8.2', '8.3']
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP only
uses: shivammathur/setup-php@v2
if: matrix.php != '7.2'
with:
php-version: ${{ matrix.php }}
coverage: none
extensions: mbstring, dom, fileinfo, sqlite, gd, zip
- name: Setup PHP with Xdebug
uses: shivammathur/setup-php@v2
if: matrix.php == '7.2'
with:
php-version: ${{ matrix.php }}
coverage: xdebug
extensions: mbstring, dom, fileinfo, sqlite, gd, zip
- name: Cache Composer dependencies
uses: actions/cache@v1
with:
path: vendor
key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader
- name: Run tests only
if: matrix.php != '7.2'
run: ./vendor/bin/phpunit
shell: pwsh
- name: Run tests with coverage report
if: matrix.php == '7.2'
run: ./vendor/bin/phpunit --coverage-clover=coverage.xml
shell: pwsh
- name: Upload coverage report
uses: codecov/codecov-action@v1
if: matrix.php == '7.2' && success()
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: github-actions
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP only
uses: shivammathur/setup-php@v2
if: matrix.php != '8.3'
with:
php-version: ${{ matrix.php }}
coverage: none
extensions: mbstring, dom, fileinfo, sqlite, gd, zip, imagick
- name: Setup PHP with Xdebug
uses: shivammathur/setup-php@v2
if: matrix.php == '8.3'
with:
php-version: ${{ matrix.php }}
coverage: xdebug
extensions: mbstring, dom, fileinfo, sqlite, gd, zip, imagick
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run tests only
if: matrix.php != '8.3'
run: ./vendor/bin/phpunit
- name: Run tests with coverage report
if: matrix.php == '8.3'
run: ./vendor/bin/phpunit --coverage-clover=coverage.xml
- name: Upload coverage report
uses: codecov/codecov-action@v1
if: matrix.php == '8.3' && success()
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: github-actions
lint:
name: Frontend Linting
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'skip ci')"
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies
run: yarn
- name: Run checks
run: |
yarn lint
yarn fmt:check
yarn tsc -p . --noEmit
yarn tsc -p ./resources/assets/tests --noEmit
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run checks
run: |
yarn lint
yarn fmt:check
yarn type:check
jest:
name: Frontend Tests
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'skip ci')"
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies
run: yarn
- name: Run tests
run: yarn test --coverage
- name: Upload coverage report
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: github-actions
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: yarn
- name: Run tests
run: yarn test --coverage
- name: Upload coverage report
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: github-actions
build:
name: Snapshot Build
runs-on: ubuntu-latest
steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
coverage: none
extensions: mbstring, dom, fileinfo, sqlite, gd, zip, imagick
- name: Checkout code
uses: actions/checkout@v4
- name: Cache Node dependencies
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-yarn-lock-${{ hashFiles('yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-lock-
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: |
composer install --prefer-dist --no-progress --no-dev
yarn install --frozen-lockfile
- name: Build frontend
run: |
yarn build
cp resources/assets/src/images/bg.webp public/app/
cp resources/assets/src/images/favicon.ico public/app/
- uses: benjlevesque/short-sha@v3.0
id: short-sha
- name: Archive release
run: zip -9 -r blessing-skin-server-${{ steps.short-sha.outputs.sha }}.zip app bootstrap config database plugins public resources/lang resources/views resources/misc/textures routes storage vendor .env.example artisan LICENSE README.md README-zh.md index.html
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
if-no-files-found: error
name: blessing-skin-server-${{ steps.short-sha.outputs.sha }}.zip
path: blessing-skin-server-${{ steps.short-sha.outputs.sha }}.zip

View File

@ -9,31 +9,32 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Build and create archive
run: ./scripts/release.ps1
shell: pwsh
env:
AZURE_TOKEN: ${{ secrets.AZURE_TOKEN }}
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
- name: Get version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- name: Upload release asset
id: upload_release_asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./blessing-skin-server-${{ steps.get_version.outputs.VERSION }}.zip
asset_name: blessing-skin-server-${{ steps.get_version.outputs.VERSION }}.zip
asset_content_type: application/zip
- name: Checkout code
uses: actions/checkout@v4
- name: Build and create archive
run: ./tools/release.ps1
shell: pwsh
env:
AZURE_TOKEN: ${{ secrets.AZURE_TOKEN }}
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
- name: Get version
id: get_version
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
- name: Upload release asset
id: upload_release_asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./blessing-skin-server-${{ steps.get_version.outputs.VERSION }}.zip
asset_name: blessing-skin-server-${{ steps.get_version.outputs.VERSION }}.zip
asset_content_type: application/zip

View File

@ -14,23 +14,24 @@ on:
- 'routes/**'
- '*.lock'
- 'webpack.*'
workflow_dispatch:
jobs:
notification:
name: Send Message
runs-on: ubuntu-latest
steps:
- name: Download bot
run: |
$headers = @{ Authorization = 'Bearer ${{ secrets.GITHUB_TOKEN }}' }
$botRelease = (Invoke-WebRequest -Headers $headers 'https://api.github.com/repos/bs-community/telegram-bot/releases/latest').Content | ConvertFrom-Json
$botBinUrl = ((Invoke-WebRequest -Headers $headers $botRelease.assets_url).Content | ConvertFrom-Json).browser_download_url
bash -c "curl --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -fSL $botBinUrl -o bot"
chmod +x ./bot
shell: pwsh
- name: Run bot
run: ./bot diff
shell: pwsh
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
- name: Download bot
run: |
$headers = @{ Authorization = 'Bearer ${{ secrets.GITHUB_TOKEN }}' }
$botRelease = (Invoke-WebRequest -Headers $headers 'https://api.github.com/repos/bs-community/telegram-bot/releases/latest').Content | ConvertFrom-Json
$botBinUrl = ((Invoke-WebRequest -Headers $headers $botRelease.assets_url).Content | ConvertFrom-Json).browser_download_url
bash -c "curl --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -fSL $botBinUrl -o bot"
chmod +x ./bot
shell: pwsh
- name: Run bot
run: ./bot diff
shell: pwsh
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}

7
.gitignore vendored
View File

@ -1,4 +1,3 @@
.DS_Store
.env
.sass-cache
coverage
@ -24,7 +23,7 @@ storage/oauth-private.key
storage/install.lock
storage/options.php
.phpunit.result.cache
.php_cs.cache
.php-cs-fixer.cache
resources/views/overrides
public/sw.js
public/meta.js
.DS_Store
*/.DS_Store

41
.gitpod.yml Normal file
View File

@ -0,0 +1,41 @@
tasks:
- init: yarn install
command: yarn dev
- init: composer install
command: |
cp .env.example .env
mkdir public/app/
cp resources/assets/src/images/bg.webp resources/assets/src/images/favicon.ico public/app
touch storage/database.db
sed 's/DB_CONNECTION=mysql/DB_CONNECTION=sqlite/' -i .env
sed 's/DB_DATABASE=blessingskin/DB_DATABASE=\/workspace\/blessing-skin-server\/storage\/database\.db/' -i .env
php artisan key:generate
php artisan serve --host=0.0.0.0
- command: gp ports await 8080 && gp preview $(gp url 8000)
github:
prebuilds:
# enable for the master/default branch (defaults to true)
master: true
# enable for all branches in this repo (defaults to false)
branches: false
# enable for pull requests coming from this repo (defaults to true)
pullRequests: true
# add a check to pull requests (defaults to true)
addCheck: true
# add a "Review in Gitpod" button as a comment to pull requests (defaults to false)
addComment: false
vscode:
extensions:
- 'editorconfig.editorconfig'
- 'eamodio.gitlens'
- 'bmewburn.vscode-intelephense-client'
- 'esbenp.prettier-vscode'
- 'jpoissonnier.vscode-styled-components'
- 'mblode.twig-language-2'
- 'felixfbecker.php-debug'
ports:
- port: 8080
visibility: public

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn pretty-quick --staged

23
.php-cs-fixer.dist.php Normal file
View File

@ -0,0 +1,23 @@
<?php
$finder = PhpCsFixer\Finder::create()
->in('app')
->in('database')
->in('routes')
->in('tests');
$config = new PhpCsFixer\Config();
return $config->setRules([
'@Symfony' => true,
'align_multiline_comment' => true,
'array_syntax' => ['syntax' => 'short'],
'increment_style' => ['style' => 'post'],
'list_syntax' => ['syntax' => 'short'],
'yoda_style' => false,
'global_namespace_import' => [
'import_constants' => true,
'import_functions' => true,
'import_classes' => null,
],
])
->setFinder($finder);

View File

@ -1,21 +0,0 @@
<?php
$finder = PhpCsFixer\Finder::create()
->in('app')
->in('database')
->in('routes')
->in('tests')
;
return PhpCsFixer\Config::create()
->setRules([
'@Symfony' => true,
'align_multiline_comment' => true,
'array_syntax' => ['syntax' => 'short'],
'increment_style' => ['style' => 'post'],
'list_syntax' => ['syntax' => 'short'],
'yoda_style' => false,
])
->setFinder($finder)
;

View File

@ -1,12 +1,7 @@
{
"recommendations": [
"editorconfig.editorconfig",
"eamodio.gitlens",
"bmewburn.vscode-intelephense-client",
"esbenp.prettier-vscode",
"jpoissonnier.vscode-styled-components",
"mblode.twig-language-2",
"firefox-devtools.vscode-firefox-debug",
"felixfbecker.php-debug"
"esbenp.prettier-vscode"
]
}

68
.vscode/launch.json vendored
View File

@ -1,38 +1,34 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Jest Tests",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["${file}"],
"internalConsoleOptions": "openOnSessionStart",
"skipFiles": [
"<node_internals>/**"
]
},
{
"type": "php",
"request": "launch",
"name": "Launch with XDebug",
"ignore": [
"**/vendor/**/*.php"
]
},
{
"type": "firefox",
"request": "launch",
"reAttach": true,
"name": "Launch with Firefox Debugger",
"url": "http://localhost/",
"webRoot": "${workspaceFolder}",
"pathMappings": [
{
"url": "webpack:///",
"path": "${workspaceFolder}/"
}
]
}
]
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Jest Tests",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["${file}"],
"internalConsoleOptions": "openOnSessionStart",
"skipFiles": ["<node_internals>/**"]
},
{
"type": "php",
"request": "launch",
"name": "Launch with XDebug",
"ignore": ["**/vendor/**/*.php"]
},
{
"type": "firefox",
"request": "launch",
"reAttach": true,
"name": "Launch with Firefox Debugger",
"url": "http://localhost/",
"webRoot": "${workspaceFolder}",
"pathMappings": [
{
"url": "webpack:///",
"path": "${workspaceFolder}/"
}
]
}
]
}

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

82
Dockerfile Normal file
View File

@ -0,0 +1,82 @@
FROM composer:latest as vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install \
--prefer-dist \
--no-dev \
--no-suggest \
--no-progress \
--no-autoloader \
--no-scripts \
--no-interaction \
--ignore-platform-reqs
FROM node:alpine as frontend
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY postcss.config.js tsconfig.build.json tsconfig.json webpack.config.ts ./
COPY tools/*Plugin.ts ./tools/
COPY resources ./resources
RUN yarn build && \
cp resources/assets/src/images/bg.webp public/app/ && \
cp resources/assets/src/images/favicon.ico public/app/ && \
# Strip unused files
rm -rf *.config.js *.config.ts tsconfig.* \
package.json yarn.lock node_modules/ \
resources/assets/ resources/lang resources/misc resources/misc/backgrounds/ \
tools/
FROM composer:latest as builder
WORKDIR /app
COPY . ./
COPY --from=vendor /app ./
COPY --from=frontend /app/public ./public
COPY --from=frontend /app/resources/views/assets ./resources/views/assets
RUN composer dump-autoload -o --no-dev -n && \
rm -rf *.config.js *.config.ts tsconfig.* \
package.json yarn.lock node_modules/ \
resources/assets/ resources/misc resources/misc/backgrounds/ \
tools/ && \
mv .env.example .env && \
php artisan key:generate && \
mv .env storage/ && \
ln -s storage/.env .env && \
touch storage/database.db && \
mkdir storage/plugins && \
sed 's/PLUGINS_DIR=null/PLUGINS_DIR=\/app\/storage\/plugins/' -i storage/.env && \
sed 's/DB_CONNECTION=mysql/DB_CONNECTION=sqlite/' -i storage/.env && \
sed 's/DB_DATABASE=blessingskin/DB_DATABASE=\/app\/storage\/database\.db/' -i storage/.env
FROM php:8-apache
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions && \
install-php-extensions gd zip
WORKDIR /app
COPY --from=builder /app ./
ENV APACHE_DOCUMENT_ROOT /app/public
RUN chown -R www-data:www-data . && \
sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf && \
sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf && \
a2enmod rewrite headers
EXPOSE 80
VOLUME ["/app/storage"]

92
README-zh.md Normal file
View File

@ -0,0 +1,92 @@
- **简体中文**
- [English](./README.md)
<p align="center"><img src="https://media.githubusercontent.com/media/bs-community/logo/main/logo.png"></p>
<p align="center">
<a href="https://github.com/bs-community/blessing-skin-server/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/bs-community/blessing-skin-server/CI?style=flat-square"></a>
<a href="https://codecov.io/gh/bs-community/blessing-skin-server"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/bs-community/blessing-skin-server?style=flat-square"></a>
<a href="https://github.com/bs-community/blessing-skin-server/releases"><img alt="GitHub release (latest SemVer including pre-releases)" src="https://img.shields.io/github/v/release/bs-community/blessing-skin-server?include_prereleases&style=flat-square"></a>
<a href="https://github.com/bs-community/blessing-skin-server/blob/master/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/bs-community/blessing-skin-server?style=flat-square"></a>
<a href="https://discord.com/invite/QAsyEyt"><img alt="Discord" src="https://discord.com/api/guilds/761226550921658380/widget.png"></a>
</p>
优雅的开源 Minecraft 皮肤站,现在,回应您的等待。
Blessing Skin 是一款能让您上传、管理和分享您的 Minecraft 皮肤和披风的 Web 应用程序。与修改游戏材质包不同的是,所有人都能在游戏中看到各自的皮肤和披风(当然,前提是玩家们要使用同一个皮肤站)。
Blessing Skin 是一个开源的 PHP 项目,这意味着您可以自由地在您的服务器上部署它。
## 特性
- 完整实现了一个皮肤站该有的功能
- 支持单用户多个角色
- 通过皮肤库来分享您的皮肤和披风!
- 易于使用
- 可视化的用户、角色、材质管理页面
- 详细的站点配置页面
- 多处 UI/UX 优化只为更好的用户体验
- 安全
- 支持多种安全密码 Hash 算法
- 注册可要求 Email 验证
- 防止恶意请求的积分系统
- 强大的可扩展性
- 多种多样的插件
- 支持与 Authme/Discuz 等程序的用户数据对接(插件)
- 支持自定义 Yggdrasil API 外置登录系统(插件)
## 环境要求
Blessing Skin 对您的服务器有一定的要求。在大多数情况下,下列所需的 PHP 扩展已经开启。
- 一台支持 URL 重写的主机Nginx 或 Apache
- PHP >= 8.1.0
- 安装并启用如下 PHP 扩展:
- OpenSSL >= 1.1.1 (TLS 1.3)
- PDO
- Mbstring
- Tokenizer
- GD
- XML
- Ctype
- JSON
- fileinfo
- zip
- Imagick
## 快速使用
请参阅 [安装指南](https://blessing.netlify.app/setup.html)。
## 插件系统
Blessing Skin 提供了强大的插件系统,您可以通过添加多种多样的插件来为您的皮肤站添加功能。
## 自行构建
详情可阅读 [这里](https://blessing.netlify.app/build.html)。
> 您可以订阅我们的 Telegram 频道 [Blessing Skin News](https://t.me/blessing_skin_news) 来获取最新开发动态。当有新的 Commit 被推送时,我们的机器人将会在频道内发送一条消息来提示您能否拉取最新代码,以及拉取后应该做什么。
## 国际化i18n
Blessing Skin 可支持多种语言,当前支持英语、简体中文和西班牙语。
如果您愿意将您的翻译贡献出来,欢迎参与 [我们的 Crowdin 项目](https://crowdin.com/project/blessing-skin)。
## 问题报告
请参阅 [报告问题的正确姿势](https://blessing.netlify.app/report.html)。
## 相关链接
- [用户手册](https://blessing.netlify.app/)
- [插件开发文档](https://bs-plugin.netlify.app/)
## 版权
MIT License
Copyright (c) 2016-present The Blessing Skin Team
程序原作者为 [@printempw](https://printempw.github.io/),转载请注明。

173
README.md
View File

@ -1,48 +1,48 @@
- **简体中文**
- [English](./README_EN.md)
- [简体中文](./README-zh.md)
- **English**
<p align="center"><img src="https://img.blessing.studio/images/2017/01/01/bs-logo.png"></p>
<p align="center"><img src="https://media.githubusercontent.com/media/bs-community/logo/main/logo.png"></p>
<p align="center">
<a href="https://github.com/bs-community/blessing-skin-server/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/bs-community/blessing-skin-server/CI?style=flat-square"></a>
<a href="https://github.com/bs-community/blessing-skin-server/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/bs-community/blessing-skin-server/CI.yml?branch=dev&style=flat-square"></a>
<a href="https://codecov.io/gh/bs-community/blessing-skin-server"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/bs-community/blessing-skin-server?style=flat-square"></a>
<a href="https://github.com/bs-community/blessing-skin-server/releases"><img alt="GitHub release (latest SemVer including pre-releases)" src="https://img.shields.io/github/v/release/bs-community/blessing-skin-server?include_prereleases&style=flat-square"></a>
<a href="https://github.com/bs-community/blessing-skin-server/blob/master/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/bs-community/blessing-skin-server?style=flat-square"></a>
<a href="https://discord.com/invite/QAsyEyt"><img alt="Discord" src="https://discord.com/api/guilds/761226550921658380/widget.png"></a>
</p>
优雅的开源 Minecraft 皮肤站,现在,回应您的等待。
Puzzled by losing your custom skins in Minecraft servers runing in offline mode? Now you can easily get them back with the help of Blessing Skin!
Blessing Skin 是一款能让您上传、管理和分享您的 Minecraft 皮肤和披风的 Web 应用程序。与修改游戏材质包不同的是,所有人都能在游戏中看到各自的皮肤和披风(当然,前提是玩家们要使用同一个皮肤站)。
Blessing Skin is a web application where you can upload, manage and share your custom skins & capes! Unlike modifying a resource pack, everyone in the game will see the different skins of each other (of course they should register at the same website too).
Blessing Skin 是一个开源的 PHP 项目,这意味着您可以自由地在您的服务器上部署它。
Blessing Skin is an open-source project written in PHP, which means you can deploy it freely on your own web server!
## 特性
## Features
- 完整实现了一个皮肤站该有的功能
- 支持单用户多个角色
- 通过皮肤库来分享您的皮肤和披风!
- 易于使用
- 可视化的用户、角色、材质管理页面
- 详细的站点配置页面
- 多处 UI/UX 优化只为更好的用户体验
- 安全
- 支持多种安全密码 Hash 算法
- 注册可要求 Email 验证
- 防止恶意请求的积分系统
- 强大的可扩展性
- 多种多样的插件
- 支持与 Authme/Discuz 等程序的用户数据对接(插件)
- 支持自定义 Yggdrasil API 外置登录系统(插件)
- A fully functional skin hosting service
- Multiple player names can be owned by one user on the website
- Share your skins and capes online with skin library!
- Easy-to-use
- Visual page for user/player/texture management
- Detailed option pages
- Many tweaks for a better UI/UX
- Security
- Support many secure password hash algorithms
- Email verification for registration
- Score system for preventing evil requests
- Incredibly extensible
- Plenty of plugins available
- Integration with Authme/Discuz (available as plugin)
- Support custom Yggdrasil API authentication (available as plugin)
## 环境要求
## Requirements
Blessing Skin 对您的服务器有一定的要求。在大多数情况下,下列所需的 PHP 扩展已经开启。
Blessing Skin has only a few system requirements. In most cases, these PHP extensions are already enabled.
- 一台支持 URL 重写的主机Nginx 或 Apache
- PHP >= 7.2.5
- 安装并启用如下 PHP 扩展:
- OpenSSL
- Web server with URL rewriting enabled (Nginx or Apache)
- PHP >= 8.1.0
- PHP Extensions
- OpenSSL >= 1.1.1 (TLS 1.3)
- PDO
- Mbstring
- Tokenizer
@ -52,120 +52,39 @@ Blessing Skin 对您的服务器有一定的要求。在大多数情况下,下
- JSON
- fileinfo
- zip
- Imagick
## 快速使用
## Quick Install
请参阅 [安装指南](https://blessing.netlify.app/setup.html)。
Please read [Installation Guide](https://blessing.netlify.app/en/setup.html).
## 插件系统
## Plugin System
Blessing Skin 提供了强大的插件系统,您可以通过添加多种多样的插件来为您的皮肤站添加功能。
Blessing Skin provides an elegant and powerful plugin system, and you can attach plenty of functions and customization to your site via installing plugins.
## 支持并赞助 Blessing Skin
## Build From Source
如果您觉得这个软件对您很有帮助,欢迎通过赞助来支持开发!
Please refer to [Manual Build](https://blessing.netlify.app/build.html).
目前可在 [爱发电](https://afdian.net/@blessing-skin) 上赞助。
## Internationalization
### Sponsors
Blessing Skin supports multiple languages, while currently supporting English, Simplified Chinese and Spanish.
<table>
<tbody>
<tr>
<td align=center>
<a href="https://afdian.net/@ValiantShishu976400">
<img src="https://pic1.afdiancdn.com/user/178a08963a5e11e9addd52540025c377/avatar/ece9f089aaf2c2f83204a8de11697caf_w350_h350_s16.jpg" width="120" height="120">
<br>
飒爽师叔
</a>
</td>
<td align=center>
<a href="https://afdian.net/@Luohuayu">
<img src="https://pic1.afdiancdn.com/user/66c740fad75011ea9fce52540025c377/avatar/870ee9ea29a1c179c435f1ad64aee79b_w640_h640_s52.jpg" width="120" height="120">
<br>
落花雨
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/default/avatar/avatar-purple.png" width="120" height="120">
<br>
graytoowolf
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/e227ea708ac911eaa6c852540025c377/avatar/a56a7ceafa12e96ba6750e880f04b7e4_w1024_h1024_s883.jpg" width="120" height="120">
<br>
mcha0
</a>
</td>
<td align=center>
<a href="https://afdian.net/@mengluorj">
<img src="https://pic1.afdiancdn.com/user/ffc6500452ed11e9994e52540025c377/avatar/ae9c5ec36b51e8314787cc19acf2d12e_w815_h815_s459.jpg" width="120" height="120">
<br>
MengLuoRJ
</a>
</td>
</tr>
</tbody>
</table>
If you are willing to contribute your translation, welcome to join [our Crowdin project](https://crowdin.com/project/blessing-skin).
### Backers
## Report Bugs
<table>
<tbody>
<tr>
<td align=center>
<a href="https://afdian.net/@mfwg6">
<img src="https://pic1.afdiancdn.com/user/18ad3338e58a11e9b29352540025c377/avatar/eb04b4b54975d0d229e77fbcd4220dc4_w1080_h1920_s541.jpg" width="75" height="75">
<br>
皮皮帕
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/68d07bf851fc11e98e5652540025c377/avatar/48538be153c8eebc3eb5cb6bc085cde9_w574_h574_s173.jpg" width="75" height="75">
<br>
dz_paji
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/b68f3a9aaef511e9826f52540025c377/avatar/03b244e92f9c4198672ce46e3fd7e100_w690_h690_s129.jpeg" width="75" height="75">
<br>
神奇威廉
</a>
</td>
</tr>
</tbody>
</table>
Read [FAQ](https://blessing.netlify.app/faq.html) and double check if your situation doesn't suit any case mentioned there before reporting.
## 自行构建
When reporting a problem, please attach your log file (located at `storage/logs/laravel.log`) and the information of your server where the error occured on. You should also read this [guide](https://blessing.netlify.app/report.html) before reporting a problem.
详情可阅读 [这里](https://blessing.netlify.com/build.html)。
## Related Links
> 您可以订阅我们的 Telegram 频道 [Blessing Skin News](https://t.me/blessing_skin_news) 来获取最新开发动态。当有新的 Commit 被推送时,我们的机器人将会在频道内发送一条消息来提示您能否拉取最新代码,以及拉取后应该做什么。
- [User Manual](https://blessing.netlify.app/en/)
- [Plugins Development Documentation](https://bs-plugin.netlify.app/)
## 国际化i18n
Blessing Skin 可支持多种语言,当前支持英语、简体中文和西班牙语。
如果您愿意将您的翻译贡献出来,欢迎参与 [我们的 Crowdin 项目](https://crowdin.com/project/blessing-skin)。
## 问题报告
请参阅 [报告问题的正确姿势](https://blessing.netlify.com/report.html)。
## 相关链接
- [用户手册](https://blessing.netlify.app/)
- [插件开发文档](https://bs-plugin.netlify.app/)
## 版权
## Copyright & License
MIT License
Copyright (c) 2016-present The Blessing Skin Team
程序原作者为 [@printempw](https://blessing.studio/),转载请注明。

View File

@ -1,169 +0,0 @@
- [简体中文](./README.md)
- **English**
<p align="center"><img src="https://img.blessing.studio/images/2017/01/01/bs-logo.png"></p>
<p align="center">
<a href="https://github.com/bs-community/blessing-skin-server/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/bs-community/blessing-skin-server/CI?style=flat-square"></a>
<a href="https://codecov.io/gh/bs-community/blessing-skin-server"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/bs-community/blessing-skin-server?style=flat-square"></a>
<a href="https://github.com/bs-community/blessing-skin-server/releases"><img alt="GitHub release (latest SemVer including pre-releases)" src="https://img.shields.io/github/v/release/bs-community/blessing-skin-server?include_prereleases&style=flat-square"></a>
<a href="https://github.com/bs-community/blessing-skin-server/blob/master/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/bs-community/blessing-skin-server?style=flat-square"></a>
<a href="https://discord.com/invite/QAsyEyt"><img alt="Discord" src="https://discord.com/api/guilds/761226550921658380/widget.png"></a>
</p>
Puzzled by losing your custom skins in Minecraft servers runing in offline mode? Now you can easily get them back with the help of Blessing Skin!
Blessing Skin is a web application where you can upload, manage and share your custom skins & capes! Unlike modifying a resource pack, everyone in the game will see the different skins of each other (of course they should register at the same website too).
Blessing Skin is an open-source project written in PHP, which means you can deploy it freely on your own web server!
## Features
- A fully functional skin hosting service
- Multiple player names can be owned by one user on the website
- Share your skins and capes online with skin library!
- Easy-to-use
- Visual page for user/player/texture management
- Detailed option pages
- Many tweaks for a better UI/UX
- Security
- Support many secure password hash algorithms
- Email verification for registration
- Score system for preventing evil requests
- Incredibly extensible
- Plenty of plugins available
- Integration with Authme/CrazyLogin/Discuz (available as plugin)
- Support custom Yggdrasil API authentication (available as plugin)
## Requirements
Blessing Skin has only a few system requirements. In most cases, these PHP extensions are already enabled.
- Web server with URL rewriting enabled (Nginx or Apache)
- PHP >= 7.2.5
- PHP Extensions
- OpenSSL
- PDO
- Mbstring
- Tokenizer
- GD
- XML
- Ctype
- JSON
- fileinfo
- zip
## Quick Install
Please read [Installation Guide](https://blessing.netlify.app/en/setup.html).
## Plugin System
Blessing Skin provides an elegant and powerful plugin system, and you can attach plenty of functions and customization to your site via installing plugins.
## Supporting Blessing Skin
Welcome to sponsoring Blessing Skin if this software is useful for you!
Currently you can sponsor us via [爱发电](https://afdian.net/@blessing-skin).
### Sponsors
<table>
<tbody>
<tr>
<td align=center>
<a href="https://afdian.net/@ValiantShishu976400">
<img src="https://pic1.afdiancdn.com/user/178a08963a5e11e9addd52540025c377/avatar/ece9f089aaf2c2f83204a8de11697caf_w350_h350_s16.jpg" width="120" height="120">
<br>
飒爽师叔
</a>
</td>
<td align=center>
<a href="https://afdian.net/@Luohuayu">
<img src="https://pic1.afdiancdn.com/user/66c740fad75011ea9fce52540025c377/avatar/870ee9ea29a1c179c435f1ad64aee79b_w640_h640_s52.jpg" width="120" height="120">
<br>
落花雨
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/default/avatar/avatar-purple.png" width="120" height="120">
<br>
graytoowolf
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/e227ea708ac911eaa6c852540025c377/avatar/a56a7ceafa12e96ba6750e880f04b7e4_w1024_h1024_s883.jpg" width="120" height="120">
<br>
mcha0
</a>
</td>
<td align=center>
<a href="https://afdian.net/@mengluorj">
<img src="https://pic1.afdiancdn.com/user/ffc6500452ed11e9994e52540025c377/avatar/ae9c5ec36b51e8314787cc19acf2d12e_w815_h815_s459.jpg" width="120" height="120">
<br>
MengLuoRJ
</a>
</td>
</tr>
</tbody>
</table>
### Backers
<table>
<tbody>
<tr>
<td align=center>
<a href="https://afdian.net/@mfwg6">
<img src="https://pic1.afdiancdn.com/user/18ad3338e58a11e9b29352540025c377/avatar/eb04b4b54975d0d229e77fbcd4220dc4_w1080_h1920_s541.jpg" width="75" height="75">
<br>
皮皮帕
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/68d07bf851fc11e98e5652540025c377/avatar/48538be153c8eebc3eb5cb6bc085cde9_w574_h574_s173.jpg" width="75" height="75">
<br>
dz_paji
</a>
</td>
<td align=center>
<a href="">
<img src="https://pic1.afdiancdn.com/user/b68f3a9aaef511e9826f52540025c377/avatar/03b244e92f9c4198672ce46e3fd7e100_w690_h690_s129.jpeg" width="75" height="75">
<br>
神奇威廉
</a>
</td>
</tr>
</tbody>
</table>
## Build From Source
Please refer to [Manual Build](https://blessing.netlify.app/build.html).
## Internationalization
Blessing Skin supports multiple languages, while currently supporting English, Simplified Chinese and Spanish.
If you are willing to contribute your translation, welcome to join [our Crowdin project](https://crowdin.com/project/blessing-skin).
## Report Bugs
Read [FAQ](https://blessing.netlify.app/faq.html) and double check if your situation doesn't suit any case mentioned there before reporting.
When reporting a problem, please attach your log file (located at `storage/logs/laravel.log`) and the information of your server where the error occured on. You should also read this [guide](https://blessing.netlify.app/report.html) before reporting a problem.
## Related Links
- [User Manual](https://blessing.netlify.app/en/)
- [Plugins Development Documentation](https://bs-plugin.netlify.app/)
## Copyright & License
MIT License
Copyright (c) 2016-present The Blessing Skin Team

View File

@ -25,7 +25,6 @@ class BsInstallCommand extends Command
if (!$this->getLaravel()->runningUnitTests()) {
// @codeCoverageIgnoreStart
$this->call('key:generate');
$this->call('jwt:secret', ['--no-interaction' => true]);
$this->call('passport:keys', ['--no-interaction' => true]);
// @codeCoverageIgnoreEnd
}

View File

@ -37,10 +37,8 @@ class UpdateCommand extends Command
protected function procedures()
{
return collect([
'0.0.1' => function () {
// this is just for testing
event('__0.0.1');
},
// this is just for testing
'0.0.1' => fn () => event('__0.0.1'),
'5.0.0' => function () {
if (option('home_pic_url') === './app/bg.jpg') {
option(['home_pic_url' => './app/bg.webp']);

View File

@ -6,8 +6,7 @@ use App\Services\Plugin;
class PluginBootFailed extends Event
{
/** @var Plugin */
public $plugin;
public Plugin $plugin;
public function __construct(Plugin $plugin)
{

View File

@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Laravel\Passport\Exceptions\MissingScopeException;
use Throwable;
class Handler extends ExceptionHandler
@ -30,6 +31,8 @@ class Handler extends ExceptionHandler
if (Str::endsWith($model, 'Texture')) {
$exception = new ModelNotFoundException(trans('skinlib.non-existent'));
}
} elseif ($exception instanceof MissingScopeException) {
return json($exception->getMessage(), 403);
}
return parent::render($request, $exception);
@ -41,12 +44,8 @@ class Handler extends ExceptionHandler
'message' => $e->getMessage(),
'exception' => true,
'trace' => collect($e->getTrace())
->map(function ($trace) {
return Arr::only($trace, ['file', 'line']);
})
->filter(function ($trace) {
return Arr::has($trace, 'file');
})
->map(fn ($trace) => Arr::only($trace, ['file', 'line']))
->filter(fn ($trace) => Arr::has($trace, 'file'))
->map(function ($trace) {
$trace['file'] = str_replace(base_path().DIRECTORY_SEPARATOR, '', $trace['file']);
@ -54,8 +53,9 @@ class Handler extends ExceptionHandler
})
->filter(function ($trace) {
// @codeCoverageIgnoreStart
$isFromPlugins = !app()->runningUnitTests() &&
Str::contains($trace['file'], resolve('plugins')->getPluginsDirs()->all());
$isFromPlugins = !app()->runningUnitTests()
&& Str::contains($trace['file'], resolve('plugins')->getPluginsDirs()->all());
// @codeCoverageIgnoreEnd
return Str::startsWith($trace['file'], 'app') || $isFromPlugins;
})

View File

@ -47,32 +47,22 @@ class AdminController extends Controller
public function chartData()
{
$xAxis = Collection::times(31, function ($i) {
return Carbon::today()->subDays(31 - $i)->format('m-d');
});
$xAxis = Collection::times(31, fn ($i) => Carbon::today()->subDays(31 - $i)->isoFormat('l'));
$oneMonthAgo = Carbon::today()->subMonth();
$grouping = function ($field) {
return function ($item) use ($field) {
return substr($item->$field, 5, 5);
};
};
$mapping = function ($item) {
return count($item);
};
$aligning = function ($data) {
return function ($day) use ($data) {
return $data->get($day) ?? 0;
};
};
$grouping = fn ($field) => fn ($item) => Carbon::parse($item->$field)->isoFormat('l');
$mapping = fn ($item) => count($item);
$aligning = fn ($data) => fn ($day) => $data->get($day) ?? 0;
/** @var Collection */
$userRegistration = User::where('register_at', '>=', $oneMonthAgo)
->select('register_at')
->get()
->groupBy($grouping('register_at'))
->map($mapping);
/** @var Collection */
$textureUploads = Texture::where('upload_at', '>=', $oneMonthAgo)
->select('upload_at')
->get()
@ -96,7 +86,7 @@ class AdminController extends Controller
Request $request,
PluginManager $plugins,
Filesystem $filesystem,
Filter $filter
Filter $filter,
) {
$db = config('database.connections.'.config('database.default'));
$dbType = Arr::get([
@ -105,9 +95,9 @@ class AdminController extends Controller
'pgsql' => 'PostgreSQL',
], config('database.default'), '');
$enabledPlugins = $plugins->getEnabledPlugins()->map(function ($plugin) {
return ['title' => trans($plugin->title), 'version' => $plugin->version];
});
$enabledPlugins = $plugins->getEnabledPlugins()->map(fn ($plugin) => [
'title' => trans($plugin->title), 'version' => $plugin->version,
]);
if ($filesystem->exists(base_path('.git'))) {
$process = new \Symfony\Component\Process\Process(
@ -137,11 +127,7 @@ class AdminController extends Controller
'version' => config('app.version'),
'env' => config('app.env'),
'debug' => config('app.debug') ? trans('general.yes') : trans('general.no'),
'commit' => Str::limit(
$commit ?? resolve(\App\Services\Webpack::class)->commit,
16,
''
),
'commit' => Str::limit($commit ?? '', 16, ''),
'laravel' => app()->version(),
],
'server' => [

View File

@ -8,16 +8,16 @@ use App\Mail\ForgotPassword;
use App\Models\Player;
use App\Models\User;
use App\Rules;
use Auth;
use Blessing\Filter;
use Blessing\Rejection;
use Cache;
use Carbon\Carbon;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request;
use Mail;
use Session;
use URL;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\URL;
use Vectorface\Whip\Whip;
class AuthController extends Controller
@ -50,7 +50,7 @@ class AuthController extends Controller
Request $request,
Rules\Captcha $captcha,
Dispatcher $dispatcher,
Filter $filter
Filter $filter,
) {
$data = $request->validate([
'identification' => 'required',
@ -151,7 +151,7 @@ class AuthController extends Controller
Request $request,
Rules\Captcha $captcha,
Dispatcher $dispatcher,
Filter $filter
Filter $filter,
) {
$can = $filter->apply('can_register', null);
if ($can instanceof Rejection) {
@ -176,8 +176,8 @@ class AuthController extends Controller
$dispatcher->dispatch('auth.registration.attempt', [$data]);
if (
option('register_with_player_name') &&
Player::where('name', $playerName)->count() > 0
option('register_with_player_name')
&& Player::where('name', $playerName)->count() > 0
) {
return json(trans('user.player.add.repeated'), 1);
}
@ -248,7 +248,7 @@ class AuthController extends Controller
Request $request,
Rules\Captcha $captcha,
Dispatcher $dispatcher,
Filter $filter
Filter $filter,
) {
$data = $request->validate([
'email' => 'required|email',
@ -370,26 +370,4 @@ class AuthController extends Controller
return redirect()->route('user.home');
}
public function jwtLogin(Request $request)
{
$token = Auth::guard('jwt')->attempt([
'email' => $request->input('email'),
'password' => $request->input('password'),
]) ?: '';
return json(compact('token'));
}
public function jwtLogout()
{
Auth::guard('jwt')->logout();
return response('', 204);
}
public function jwtRefresh()
{
return json(['token' => Auth::guard('jwt')->refresh()]);
}
}

View File

@ -4,12 +4,12 @@ namespace App\Http\Controllers;
use App\Models\Texture;
use App\Models\User;
use Auth;
use Blessing\Filter;
use Blessing\Rejection;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ClosetController extends Controller
{
@ -51,14 +51,16 @@ class ClosetController extends Controller
return $user
->closet()
->when($category === 'cape', function (Builder $query) {
return $query->where('type', 'cape');
}, function (Builder $query) {
return $query->whereIn('type', ['steve', 'alex']);
})
->when($request->input('q'), function (Builder $query, $search) {
return $query->like('item_name', $search);
})
->when(
$category === 'cape',
fn (Builder $query) => $query->where('type', 'cape'),
fn (Builder $query) => $query->whereIn('type', ['steve', 'alex']),
)
->when(
$request->input('q'),
fn (Builder $query, $search) => $query->like('item_name', $search)
)
->orderBy('texture_tid', 'DESC')
->paginate((int) $request->input('perPage', 6));
}
@ -73,7 +75,7 @@ class ClosetController extends Controller
public function add(
Request $request,
Dispatcher $dispatcher,
Filter $filter
Filter $filter,
) {
['tid' => $tid, 'name' => $name] = $request->validate([
'tid' => 'required|integer',
@ -130,7 +132,7 @@ class ClosetController extends Controller
Request $request,
Dispatcher $dispatcher,
Filter $filter,
$tid
$tid,
) {
['name' => $name] = $request->validate(['name' => 'required']);
/** @var User */

View File

@ -17,8 +17,15 @@ class ClosetManagementController extends Controller
public function add(Request $request, Dispatcher $dispatcher, User $user)
{
$tid = $request->input('tid');
/** @var Texture */
$texture = Texture::findOrFail($tid);
$texture = Texture::find($tid);
if (!$texture) {
return json(trans('user.closet.add.not-found'), 1);
}
if ($user->closet()->where('tid', $request->tid)->count() > 0) {
return json(trans('user.closet.add.repeated'), 1);
}
$name = $texture->name;
$dispatcher->dispatch('closet.adding', [$tid, $name, $user]);
@ -35,10 +42,14 @@ class ClosetManagementController extends Controller
$tid = $request->input('tid');
$dispatcher->dispatch('closet.removing', [$tid, $user]);
/** @var Texture */
$texture = Texture::findOrFail($tid);
$item = $user->closet()->find($tid);
if (empty($item)) {
return json(trans('user.closet.remove.non-existent'), 1);
}
$user->closet()->detach($texture->tid);
$user->closet()->detach($tid);
$texture = Texture::find($tid);
$dispatcher->dispatch('closet.removed', [$texture, $user]);

View File

@ -2,12 +2,11 @@
namespace App\Http\Controllers;
use App\Services\Webpack;
use Illuminate\Support\Arr;
class HomeController extends Controller
{
public function index(Webpack $webpack)
public function index()
{
return view('home')
->with('user', auth()->user())
@ -15,13 +14,7 @@ class HomeController extends Controller
->with('transparent_navbar', (bool) option('transparent_navbar', false))
->with('fixed_bg', option('fixed_bg'))
->with('hide_intro', option('hide_intro'))
->with('home_pic_url', option('home_pic_url') ?: config('options.home_pic_url'))
->with('home_page_css', $webpack->url('home.css'))
->with(
'home_page_css_loader',
config('app.asset.env') === 'development' ? $webpack->url('home.js') : null
)
->with('app_js', $webpack->url('app.js'));
->with('home_pic_url', option('home_pic_url') ?: config('options.home_pic_url'));
}
public function apiRoot()
@ -32,6 +25,8 @@ class HomeController extends Controller
'Powered by Blessing Skin Server.',
'Proudly powered by Blessing Skin Server.',
'由 Blessing Skin Server 强力驱动。',
'采用 Blessing Skin Server 搭建。',
'使用 Blessing Skin Server 稳定运行。',
'自豪地采用 Blessing Skin Server。',
],
option_localized('copyright_prefer', 0)

View File

@ -45,12 +45,12 @@ class NotificationsController extends Controller
public function all()
{
return auth()->user()->unreadNotifications->map(function ($notification) {
return [
return auth()->user()
->unreadNotifications
->map(fn ($notification) => [
'id' => $notification->id,
'title' => $notification->data['title'],
];
});
]);
}
public function read($id)
@ -58,16 +58,14 @@ class NotificationsController extends Controller
$notification = auth()
->user()
->unreadNotifications
->first(function ($notification) use ($id) {
return $notification->id === $id;
});
->first(fn ($notification) => $notification->id === $id);
$notification->markAsRead();
$converter = new GithubFlavoredMarkdownConverter();
return [
'title' => $notification->data['title'],
'content' => $converter->convertToHtml($notification->data['content']),
'content' => $converter->convertToHtml($notification->data['content'] ?? '')->getContent(),
'time' => $notification->created_at->toDateTimeString(),
];
}

View File

@ -28,7 +28,9 @@ class OptionsController extends Controller
->option('1', 'Powered by Blessing Skin Server.')
->option('2', 'Proudly powered by Blessing Skin Server.')
->option('3', '由 Blessing Skin Server 强力驱动。')
->option('4', '自豪地采用 Blessing Skin Server。')
->option('4', '采用 Blessing Skin Server 搭建。')
->option('5', '使用 Blessing Skin Server 稳定运行。')
->option('6', '自豪地采用 Blessing Skin Server。')
->description();
$form->textarea('copyright_text')->rows(6)->description();
@ -161,9 +163,14 @@ class OptionsController extends Controller
->text('max_upload_file_size')->addon('KB')
->hint(trans('options.general.max_upload_file_size.hint', ['size' => ini_get('upload_max_filesize')]));
$form->group('max_texture_width')
->text('max_texture_width')->addon('px')
->hint(trans('options.general.max_texture_width.hint'));
$form->select('player_name_rule')
->option('official', trans('options.general.player_name_rule.official'))
->option('cjk', trans('options.general.player_name_rule.cjk'))
->option('utf8', trans('options.general.player_name_rule.utf8'))
->option('custom', trans('options.general.player_name_rule.custom'));
$form->text('custom_player_name_regexp')->hint()->placeholder();

View File

@ -10,11 +10,11 @@ use App\Models\Player;
use App\Models\Texture;
use App\Models\User;
use App\Rules;
use Auth;
use Blessing\Filter;
use Blessing\Rejection;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
class PlayerController extends Controller
@ -124,7 +124,7 @@ class PlayerController extends Controller
public function delete(
Dispatcher $dispatcher,
Filter $filter,
Player $player
Player $player,
) {
/** @var User */
$user = auth()->user();
@ -157,7 +157,7 @@ class PlayerController extends Controller
Request $request,
Dispatcher $dispatcher,
Filter $filter,
Player $player
Player $player,
) {
$name = $request->validate([
'name' => [
@ -194,7 +194,7 @@ class PlayerController extends Controller
Request $request,
Dispatcher $dispatcher,
Filter $filter,
Player $player
Player $player,
) {
/** @var User */
$user = auth()->user();
@ -234,7 +234,7 @@ class PlayerController extends Controller
Request $request,
Dispatcher $dispatcher,
Filter $filter,
Player $player
Player $player,
) {
$types = $request->input('type', []);

View File

@ -23,8 +23,8 @@ class PlayersManagementController extends Controller
$currentUser = $request->user();
if (
$owner->uid !== $currentUser->uid &&
$owner->permission >= $currentUser->permission
$owner->uid !== $currentUser->uid
&& $owner->permission >= $currentUser->permission
) {
return json(trans('admin.players.no-permission'), 1)
->setStatusCode(403);
@ -44,7 +44,7 @@ class PlayersManagementController extends Controller
public function name(
Player $player,
Request $request,
Dispatcher $dispatcher
Dispatcher $dispatcher,
) {
$name = $request->validate([
'player_name' => [
@ -70,7 +70,7 @@ class PlayersManagementController extends Controller
public function owner(
Player $player,
Request $request,
Dispatcher $dispatcher
Dispatcher $dispatcher,
) {
$uid = $request->validate(['uid' => 'required|integer'])['uid'];
@ -96,7 +96,7 @@ class PlayersManagementController extends Controller
public function texture(
Player $player,
Request $request,
Dispatcher $dispatcher
Dispatcher $dispatcher,
) {
$data = $request->validate([
'tid' => 'required|integer',
@ -123,7 +123,7 @@ class PlayersManagementController extends Controller
public function delete(
Player $player,
Dispatcher $dispatcher
Dispatcher $dispatcher,
) {
$dispatcher->dispatch('player.deleting', [$player]);

View File

@ -77,7 +77,7 @@ class ReportController extends Controller
public function review(
Report $report,
Request $request,
Dispatcher $dispatcher
Dispatcher $dispatcher,
) {
$data = $request->validate([
'action' => ['required', Rule::in(['delete', 'ban', 'reject'])],
@ -88,9 +88,9 @@ class ReportController extends Controller
if ($action == 'reject') {
if (
$report->informer &&
($score = option('reporter_score_modification', 0)) > 0 &&
$report->status == Report::PENDING
$report->informer
&& ($score = option('reporter_score_modification', 0)) > 0
&& $report->status == Report::PENDING
) {
$report->informer->score -= $score;
$report->informer->save();
@ -150,7 +150,11 @@ class ReportController extends Controller
public static function returnScore($report)
{
if ($report->status == Report::PENDING && ($score = option('reporter_score_modification', 0)) < 0) {
if (
$report->status == Report::PENDING
&& ($score = option('reporter_score_modification', 0)) < 0
&& $report->informer
) {
$report->informer->score -= $score;
$report->informer->save();
}
@ -158,7 +162,7 @@ class ReportController extends Controller
public static function giveAward($report)
{
if ($report->status == Report::PENDING) {
if ($report->status == Report::PENDING && $report->informer) {
$report->informer->score += option('reporter_reward_score', 0);
$report->informer->save();
}

View File

@ -20,7 +20,7 @@ class SetupController extends Controller
Request $request,
Filesystem $filesystem,
Connection $connection,
DatabaseManager $manager
DatabaseManager $manager,
) {
if ($request->isMethod('get')) {
try {
@ -52,7 +52,7 @@ class SetupController extends Controller
try {
$manager->connection('temp')->getPdo();
} catch (\Exception $e) {
$msg = iconv('gbk', 'utf-8', $e->getMessage());
$msg = $e->getMessage();
$type = Arr::get([
'mysql' => 'MySQL/MariaDB',
'sqlite' => 'SQLite',
@ -112,7 +112,6 @@ class SetupController extends Controller
'site_name' => 'required',
]);
$artisan->call('jwt:secret', ['--no-interaction' => true]);
$artisan->call('passport:keys', ['--no-interaction' => true]);
// Create tables
@ -122,7 +121,7 @@ class SetupController extends Controller
'database/migrations',
'vendor/laravel/passport/database/migrations',
],
]);
]);
$siteUrl = url('/');
if (Str::endsWith($siteUrl, '/index.php')) {

View File

@ -4,7 +4,6 @@ namespace App\Http\Controllers;
use App\Models\Texture;
use App\Models\User;
use Auth;
use Blessing\Filter;
use Blessing\Rejection;
use Illuminate\Contracts\Events\Dispatcher;
@ -12,10 +11,12 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Intervention\Image\Facades\Image;
use League\CommonMark\GithubFlavoredMarkdownConverter;
use Storage;
class SkinlibController extends Controller
{
@ -68,17 +69,13 @@ class SkinlibController extends Controller
$sortBy = $sort == 'time' ? 'upload_at' : $sort;
return Texture::orderBy($sortBy, 'desc')
->when($type === 'skin', function (Builder $query) {
return $query->whereIn('type', ['steve', 'alex']);
}, function (Builder $query) use ($type) {
return $query->where('type', $type);
})
->when($keyword, function (Builder $query, $keyword) {
return $query->like('name', $keyword);
})
->when($uploader, function (Builder $query, $uploader) {
return $query->where('uploader', $uploader);
})
->when(
$type === 'skin',
fn (Builder $query) => $query->whereIn('type', ['steve', 'alex']),
fn (Builder $query) => $query->where('type', $type),
)
->when($keyword, fn (Builder $query, $keyword) => $query->like('name', $keyword))
->when($uploader, fn (Builder $query, $uploader) => $query->where('uploader', $uploader))
->when($user, function (Builder $query, User $user) {
if (!$user->isAdmin()) {
// use closure-style `where` clause to lift up SQL priority
@ -138,7 +135,7 @@ class SkinlibController extends Controller
->with('texture', $texture)
->with('grid', $grid)
->with('extra', [
'download' => option('allow_downloading_texture'),
'download' => (bool) option('allow_downloading_texture'),
'currentUid' => $user ? $user->uid : 0,
'admin' => $user && $user->isAdmin(),
'inCloset' => $user && $user->closet()->where('tid', $texture->tid)->count() > 0,
@ -186,14 +183,14 @@ class SkinlibController extends Controller
'scorePrivate' => (int) option('private_score_per_storage'),
'closetItemCost' => (int) option('score_per_closet_item'),
'award' => (int) option('score_award_per_texture'),
'contentPolicy' => $converter->convertToHtml(option_localized('content_policy')),
'contentPolicy' => $converter->convertToHtml(option_localized('content_policy'))->getContent(),
]);
}
public function handleUpload(
Request $request,
Filter $filter,
Dispatcher $dispatcher
Dispatcher $dispatcher,
) {
$file = $request->file('file');
if ($file && !$file->isValid()) {
@ -223,24 +220,36 @@ class SkinlibController extends Controller
$type = $data['type'];
$size = getimagesize($file);
$maxWidth = option('max_texture_width', 8192);
if ($size[0] > $maxWidth) {
$message = trans('skinlib.upload.too-wide', [
'width' => $size[0],
'maxWidth' => $maxWidth,
]);
return json($message, 1);
}
if ($size[0] % 64 != 0 || $size[1] % 32 != 0) {
$message = trans('skinlib.upload.invalid-size', [
'type' => $type === 'cape' ? trans('general.cape') : trans('general.skin'),
'width' => $size[0],
'height' => $size[1],
]);
return json($message, 1);
}
$ratio = $size[0] / $size[1];
if ($type == 'steve' || $type == 'alex') {
if ($ratio != 2 && $ratio != 1) {
if ($ratio != 2 && $ratio != 1 || $type === 'alex' && $ratio === 2) {
$message = trans('skinlib.upload.invalid-size', [
'type' => trans('general.skin'),
'width' => $size[0],
'height' => $size[1],
]);
return json($message, 1);
}
if ($size[0] % 64 != 0 || $size[1] % 32 != 0) {
$message = trans('skinlib.upload.invalid-hd-skin', [
'type' => trans('general.skin'),
'width' => $size[0],
'height' => $size[1],
]);
return json($message, 1);
}
} elseif ($type == 'cape') {
@ -255,17 +264,25 @@ class SkinlibController extends Controller
}
}
$hash = hash_file('sha256', $file);
$hash = $filter->apply('uploaded_texture_hash', $hash, [$file]);
$image = Image::make($file);
$imagick = $image->getCore();
$imagick->setOption('png:compression-filter', '0');
$imagick->setOption('png:compression-level', '9');
$imagick->setOption('png:compression-strategy', '0');
$imagick->setOption('png:exclude-chunk', 'all');
$imagick->stripImage();
$sanitized = $image->encode('png')->getEncoded();
$hash = hash('sha256', $image->encoded);
$hash = $filter->apply('uploaded_texture_hash', $hash, [$image]);
/** @var User */
$user = Auth::user();
$duplicated = Texture::where('hash', $hash)
->where(function ($query) use ($user) {
return $query->where('public', true)
->orWhere('uploader', $user->uid);
})
->where(
fn (Builder $query) => $query->where('public', true)->orWhere('uploader', $user->uid)
)
->first();
if ($duplicated) {
// if the texture already uploaded was set to private,
@ -273,11 +290,11 @@ class SkinlibController extends Controller
return json(trans('skinlib.upload.repeated'), 2, ['tid' => $duplicated->tid]);
}
$size = ceil($file->getSize() / 1024);
$fileSize = ceil(strlen($sanitized) / 1024);
$isPublic = is_string($data['public'])
? $data['public'] === '1'
: $data['public'];
$cost = $size * (
$cost = $fileSize * (
$isPublic
? option('score_per_storage')
: option('private_score_per_storage')
@ -288,13 +305,13 @@ class SkinlibController extends Controller
return json(trans('skinlib.upload.lack-score'), 1);
}
$dispatcher->dispatch('texture.uploading', [$file, $name, $hash]);
$dispatcher->dispatch('texture.uploading', [$image, $name, $hash]);
$texture = new Texture();
$texture->name = $name;
$texture->type = $type;
$texture->hash = $hash;
$texture->size = $size;
$texture->size = $fileSize;
$texture->public = $isPublic;
$texture->uploader = $user->uid;
$texture->likes = 1;
@ -303,14 +320,14 @@ class SkinlibController extends Controller
/** @var FilesystemAdapter */
$disk = Storage::disk('textures');
if ($disk->missing($hash)) {
$file->storePubliclyAs('', $hash, ['disk' => 'textures']);
$disk->put($hash, $sanitized);
}
$user->score -= $cost;
$user->closet()->attach($texture->tid, ['item_name' => $name]);
$user->save();
$dispatcher->dispatch('texture.uploaded', [$texture, $file]);
$dispatcher->dispatch('texture.uploaded', [$texture, $image]);
return json(trans('skinlib.upload.success', ['name' => $name]), 0, [
'tid' => $texture->tid,
@ -389,7 +406,7 @@ class SkinlibController extends Controller
Request $request,
Dispatcher $dispatcher,
Filter $filter,
Texture $texture
Texture $texture,
) {
$data = $request->validate(['name' => [
'required',
@ -419,7 +436,7 @@ class SkinlibController extends Controller
Request $request,
Dispatcher $dispatcher,
Filter $filter,
Texture $texture
Texture $texture,
) {
$data = $request->validate([
'type' => ['required', Rule::in(['steve', 'alex', 'cape'])],

View File

@ -6,11 +6,11 @@ use App\Models\Player;
use App\Models\Texture;
use App\Models\User;
use Blessing\Minecraft;
use Cache;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Image;
use Storage;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;
class TextureController extends Controller
{
@ -71,7 +71,8 @@ class TextureController extends Controller
$lastModified = $disk->lastModified($hash);
return Image::make($image)
// TODO: refactor
return \Intervention\Image\ImageManagerStatic::configure(['driver' => 'gd'])->make($image)
->response($usePNG ? 'png' : 'webp', 100)
->setLastModified(Carbon::createFromTimestamp($lastModified));
}
@ -132,8 +133,12 @@ class TextureController extends Controller
return $this->avatar($minecraft, $request, $texture);
}
protected function avatar(Minecraft $minecraft, Request $request, Texture $texture = null)
protected function avatar(Minecraft $minecraft, Request $request, ?Texture $texture)
{
if (!empty($texture) && $texture->type !== 'steve' && $texture->type !== 'alex') {
return abort(422);
}
$size = (int) $request->query('size', 100);
$mode = $request->has('3d') ? '3d' : '2d';
$usePNG = $request->has('png') || !(imagetypes() & IMG_WEBP);
@ -141,7 +146,8 @@ class TextureController extends Controller
$disk = Storage::disk('textures');
if (is_null($texture) || $disk->missing($texture->hash)) {
return Image::make(resource_path("misc/textures/avatar$mode.png"))
// TODO: refactor
return \Intervention\Image\ImageManagerStatic::configure(['driver' => 'gd'])->make(resource_path("misc/textures/avatar$mode.png"))
->resize($size, $size)
->response($usePNG ? 'png' : 'webp', 100);
}
@ -161,7 +167,8 @@ class TextureController extends Controller
$lastModified = Carbon::createFromTimestamp($disk->lastModified($hash));
return Image::make($image)
// TODO: refactor
return \Intervention\Image\ImageManagerStatic::configure(['driver' => 'gd'])->make($image)
->resize($size, $size)
->response($usePNG ? 'png' : 'webp', 100)
->setLastModified($lastModified);

View File

@ -40,7 +40,7 @@ class TranslationsController extends Controller
Request $request,
Application $app,
JavaScript $js,
LanguageLine $line
LanguageLine $line,
) {
$data = $request->validate(['text' => 'required|string']);
@ -57,7 +57,7 @@ class TranslationsController extends Controller
public function delete(
Application $app,
JavaScript $js,
LanguageLine $line
LanguageLine $line,
) {
$line->delete();

View File

@ -12,7 +12,7 @@ use Illuminate\Support\Facades\Http;
class UpdateController extends Controller
{
const SPEC = 2;
public const SPEC = 2;
public function showUpdatePage()
{

View File

@ -6,16 +6,16 @@ use App\Events\UserProfileUpdated;
use App\Mail\EmailVerification;
use App\Models\Texture;
use App\Models\User;
use Auth;
use Blessing\Filter;
use Blessing\Rejection;
use Carbon\Carbon;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\URL;
use League\CommonMark\GithubFlavoredMarkdownConverter;
use Mail;
use Session;
use URL;
class UserController extends Controller
{
@ -330,9 +330,9 @@ class UserController extends Controller
}
if (
!$texture->public &&
$user->uid !== $texture->uploader &&
!$user->isAdmin()
!$texture->public
&& $user->uid !== $texture->uploader
&& !$user->isAdmin()
) {
return json(trans('skinlib.show.private'), 1);
}
@ -347,4 +347,14 @@ class UserController extends Controller
return json(trans('skinlib.non-existent'), 1);
}
}
public function toggleDarkMode()
{
/** @var User */
$user = auth()->user();
$user->is_dark_mode = !$user->is_dark_mode;
$user->save();
return response()->noContent();
}
}

View File

@ -18,8 +18,8 @@ class UsersManagementController extends Controller
$authUser = $request->user();
if (
$targetUser->isNot($authUser) &&
$targetUser->permission >= $authUser->permission
$targetUser->isNot($authUser)
&& $targetUser->permission >= $authUser->permission
) {
return json(trans('admin.users.operations.no-permission'), 1)
->setStatusCode(403);
@ -134,8 +134,8 @@ class UsersManagementController extends Controller
$permission = (int) $data['permission'];
if (
$permission === User::ADMIN &&
$request->user()->permission < User::SUPER_ADMIN
$permission === User::ADMIN
&& $request->user()->permission < User::SUPER_ADMIN
) {
return json(trans('admin.users.operations.no-permission'), 1)
->setStatusCode(403);

View File

@ -11,19 +11,20 @@ class Kernel extends HttpKernel
*
* These middleware are run during every request to your application.
*
* @var array
* @var array<int, class-string|string>
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\TrimStrings::class,
\App\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\DetectLanguagePrefer::class,
Middleware\ConvertEmptyStringsToNull::class,
Middleware\DetectLanguagePrefer::class,
];
/**
* The application's route middleware groups.
*
* @var array
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
@ -32,38 +33,39 @@ class Kernel extends HttpKernel
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
\App\Http\Middleware\EnforceEverGreen::class,
\App\Http\Middleware\RedirectToSetup::class,
'bindings',
Middleware\EnforceEverGreen::class,
Middleware\RedirectToSetup::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'bindings',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'authorize' => [
'auth:web',
\App\Http\Middleware\RejectBannedUser::class,
\App\Http\Middleware\EnsureEmailFilled::class,
\App\Http\Middleware\FireUserAuthenticated::class,
Middleware\RejectBannedUser::class,
Middleware\EnsureEmailFilled::class,
Middleware\FireUserAuthenticated::class,
],
];
/**
* The application's route middleware.
* The application's middleware aliases.
*
* These middleware may be assigned to groups or used individually.
* Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
*
* @var array
* @var array<string, class-string|string>
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
protected $middlewareAliases = [
'auth' => Middleware\Authenticate::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'role' => \App\Http\Middleware\CheckRole::class,
'setup' => \App\Http\Middleware\CheckInstallation::class,
'guest' => Middleware\RedirectIfAuthenticated::class,
'role' => Middleware\CheckRole::class,
'setup' => Middleware\CheckInstallation::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \App\Http\Middleware\CheckUserVerified::class,
'verified' => Middleware\CheckUserVerified::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
];
}

View File

@ -14,7 +14,7 @@ class Authenticate extends Middleware
'msg' => trans('auth.check.anonymous'),
]);
return '/auth/login';
return route('auth.login');
}
}
}

View File

@ -2,16 +2,17 @@
namespace App\Http\Middleware;
use App\Models\User;
use Closure;
use Illuminate\Http\Request;
class CheckRole
{
protected $roles = [
'banned' => -1,
'normal' => 0,
'admin' => 1,
'super-admin' => 2,
'banned' => User::BANNED,
'normal' => User::NORMAL,
'admin' => User::ADMIN,
'super-admin' => User::SUPER_ADMIN,
];
public function handle(Request $request, Closure $next, $role)

View File

@ -14,8 +14,8 @@ class DetectLanguagePrefer
?? $request->cookie('locale')
?? $request->getPreferredLanguage();
if (
($info = Arr::get(config('locales'), $locale)) &&
($alias = Arr::get($info, 'alias'))
($info = Arr::get(config('locales'), $locale))
&& ($alias = Arr::get($info, 'alias'))
) {
$locale = $alias;
}
@ -28,7 +28,9 @@ class DetectLanguagePrefer
/** @var Response */
$response = $next($request);
$response->cookie('locale', $locale, 120);
if (!in_array('api', optional($request->route())->middleware() ?? [])) {
$response->cookie('locale', $locale, 120);
}
return $response;
}

View File

@ -3,12 +3,16 @@
namespace App\Http\Middleware;
use App\Models\User;
use Closure;
use Illuminate\Http\Request;
class RejectBannedUser
{
public function handle($request, Closure $next)
public function handle(Request $request, \Closure $next)
{
if ($request->route()->getName() === 'auth.logout') {
return $next($request);
}
if ($request->user()->permission == User::BANNED) {
if ($request->expectsJson()) {
$response = json(trans('auth.check.banned'), -1);

View File

@ -3,39 +3,28 @@
namespace App\Http\View\Composers;
use App\Services\Translations\JavaScript;
use App\Services\Webpack;
use Blessing\Filter;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\View\View;
class FootComposer
{
/** @var Request */
protected $request;
protected Request $request;
/** @var Webpack */
protected $webpack;
protected JavaScript $javascript;
/** @var JavaScript */
protected $javascript;
protected Dispatcher $dispatcher;
/** @var Dispatcher */
protected $dispatcher;
/** @var Filter */
protected $filter;
protected Filter $filter;
public function __construct(
Request $request,
Webpack $webpack,
JavaScript $javascript,
Dispatcher $dispatcher,
Filter $filter
Filter $filter,
) {
$this->request = $request;
$this->webpack = $webpack;
$this->javascript = $javascript;
$this->dispatcher = $dispatcher;
$this->filter = $filter;
@ -50,47 +39,10 @@ class FootComposer
public function injectJavaScript(View $view)
{
$scripts = [];
$locale = app()->getLocale();
$scripts[] = [
'src' => $this->javascript->generate($locale),
];
if (Str::startsWith(config('app.asset.env'), 'dev')) {
$scripts[] = [
'src' => $this->webpack->url('style.js'),
'async' => true,
'defer' => true,
];
} elseif (!$this->request->is('/')) {
$scripts[] = [
'src' => 'https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js',
'integrity' => 'sha256-yUhvEmYVhZ/GGshIQKArLvySDSh6cdmdcIx0spR3UP4=',
'crossorigin' => 'anonymous',
];
$scripts[] = [
'src' => 'https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js',
'integrity' => 'sha256-vFt3l+illeNlwThbDUdoPTqF81M8WNSZZZt3HEjsbSU=',
'crossorigin' => 'anonymous',
];
}
$scripts[] = [
'src' => 'https://cdn.jsdelivr.net/npm/@blessing-skin/admin-lte@3.0.5/dist/admin-lte.min.js',
'integrity' => 'sha256-8RoBtV28TLYWlTMCRwqGv4NQW9bgc4jZphsQV3iLV4g=',
'crossorigin' => 'anonymous',
];
if ($this->request->is('/')) {
$scripts[] = [
'src' => $this->webpack->url('home.js'),
];
} else {
$scripts[] = [
'src' => $this->webpack->url('app.js'),
];
}
$scripts = $this->filter->apply('scripts', $scripts);
$view->with([
'i18n' => $this->javascript->generate(app()->getLocale()),
'scripts' => $scripts,
'inline_js' => option('custom_js'),
]);

View File

@ -2,7 +2,6 @@
namespace App\Http\View\Composers;
use App\Services\Webpack;
use Blessing\Filter;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request;
@ -12,25 +11,17 @@ use Illuminate\View\View;
class HeadComposer
{
/** @var Webpack */
protected $webpack;
protected Dispatcher $dispatcher;
/** @var Dispatcher */
protected $dispatcher;
protected Request $request;
/** @var Request */
protected $request;
/** @var Filter */
protected $filter;
protected Filter $filter;
public function __construct(
Webpack $webpack,
Dispatcher $dispatcher,
Request $request,
Filter $filter
Filter $filter,
) {
$this->webpack = $webpack;
$this->dispatcher = $dispatcher;
$this->request = $request;
$this->filter = $filter;
@ -89,37 +80,6 @@ class HeadComposer
public function injectStyles(View $view)
{
$links = [];
$links[] = [
'rel' => 'stylesheet',
'href' => 'https://cdn.jsdelivr.net/npm/@blessing-skin/admin-lte@3.0.5/dist/admin-lte.min.css',
'integrity' => 'sha256-zG+BobiRcnWbKPGtQ++LoogyH9qSHjhBwhbFFP8HbDM=',
'crossorigin' => 'anonymous',
];
$links[] = [
'rel' => 'preload',
'as' => 'font',
'href' => 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.13.0/webfonts/fa-solid-900.woff2',
'crossorigin' => 'anonymous',
];
$links[] = [
'rel' => 'preload',
'as' => 'font',
'href' => 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.13.0/webfonts/fa-regular-400.woff2',
'crossorigin' => 'anonymous',
];
$links[] = [
'rel' => 'stylesheet',
'href' => 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.13.0/css/all.min.css',
'integrity' => 'sha256-h20CPZ0QyXlBuAw7A+KluUYx/3pK+c7lYEpqLTlxjYQ=',
'crossorigin' => 'anonymous',
];
if (!$this->request->is('/') && config('app.asset.env') !== 'development') {
$links[] = [
'rel' => 'stylesheet',
'href' => $this->webpack->url('style.css'),
'crossorigin' => 'anonymous',
];
}
$links = $this->filter->apply('head_links', $links);
$view->with('links', $links);
$view->with('inline_css', option('custom_css'));

View File

@ -8,8 +8,7 @@ use Illuminate\View\View;
class LanguagesMenuComposer
{
/** @var Request */
protected $request;
protected Request $request;
public function __construct(Request $request)
{
@ -22,9 +21,7 @@ class LanguagesMenuComposer
$path = $this->request->path();
$langs = collect(config('locales'))
->reject(function ($locale) {
return Arr::has($locale, 'alias');
})
->reject(fn ($locale) => Arr::has($locale, 'alias'))
->map(function ($locale, $id) use ($query, $path) {
$query = array_merge($query, ['lang' => $id]);
$locale['url'] = url($path.'?'.http_build_query($query));

View File

@ -3,6 +3,7 @@
namespace App\Http\View\Composers;
use App\Events;
use App\Services\Plugin;
use App\Services\PluginManager;
use Blessing\Filter;
use Illuminate\Http\Request;
@ -11,11 +12,9 @@ use Illuminate\View\View;
class SideMenuComposer
{
/** @var Request */
protected $request;
protected Request $request;
/** @var Filter */
protected $filter;
protected Filter $filter;
public function __construct(Request $request, Filter $filter)
{
@ -44,9 +43,7 @@ class SideMenuComposer
$menu = $menu[$type];
$menu = $this->filter->apply('side_menu', $menu, [$type]);
$view->with('items', array_map(function ($item) {
return $this->transform($item);
}, $menu));
$view->with('items', array_map(fn ($item) => $this->transform($item), $menu));
}
public function transform(array $item): array
@ -66,9 +63,10 @@ class SideMenuComposer
}
if (Arr::has($item, 'children')) {
$item['children'] = array_map(function ($item) {
return $this->transform($item);
}, $item['children']);
$item['children'] = array_map(
fn ($item) => $this->transform($item),
$item['children'],
);
}
$item['classes'] = $classes;
@ -82,9 +80,7 @@ class SideMenuComposer
if (Arr::get($item, 'id') === 'plugin-configs') {
$pluginConfigs = resolve(PluginManager::class)
->getEnabledPlugins()
->filter(function ($plugin) {
return $plugin->hasConfig();
})
->filter(fn (Plugin $plugin) => $plugin->hasConfig())
->map(function ($plugin) {
return [
'title' => trans($plugin->title),
@ -95,7 +91,7 @@ class SideMenuComposer
// Don't display this menu item when no plugin config is available
if ($pluginConfigs->isNotEmpty()) {
$item['children'] = array_merge($item['children'], $pluginConfigs->values()->all());
array_push($item['children'], ...$pluginConfigs->values()->all());
return $item;
}

View File

@ -8,11 +8,9 @@ use Illuminate\View\View;
class UserMenuComposer
{
/** @var Request */
protected $request;
protected Request $request;
/** @var Filter */
protected $filter;
protected Filter $filter;
public function __construct(Request $request, Filter $filter)
{
@ -25,8 +23,34 @@ class UserMenuComposer
$user = auth()->user();
$avatarUrl = route('avatar.texture', ['tid' => $user->avatar, 'size' => 36], false);
$avatar = $this->filter->apply('user_avatar', $avatarUrl, [$user]);
$cli = $this->request->is('admin', 'admin/*');
$avatarPNG = route(
'avatar.texture',
['tid' => $user->avatar, 'size' => 36, 'png' => true],
false
);
$avatarPNG = $this->filter->apply('user_avatar', $avatarPNG, [$user]);
$view->with(compact('user', 'avatar', 'cli'));
$menuItems = [
['label' => trans('general.user-center'), 'link' => route('user.home')],
['label' => trans('general.profile'), 'link' => route('user.profile.view')],
];
if ($user->isAdmin()) {
array_push(
$menuItems,
['label' => '', 'link' => '#divider'],
['label' => trans('general.admin-panel'), 'link' => route('admin.view')],
['label' => trans('general.user-manage'), 'link' => route('admin.users.view')],
['label' => trans('general.report-manage'), 'link' => route('admin.reports.view')],
['label' => 'Web CLI', 'link' => '#launch-cli'],
);
}
$menuItems = $this->filter->apply('user_menu', $menuItems, [$user]);
$view->with([
'user' => $user,
'avatar' => $avatar,
'avatar_png' => $avatarPNG,
'menu' => $menuItems,
]);
}
}

View File

@ -9,11 +9,9 @@ use Illuminate\View\View;
class UserPanelComposer
{
/** @var Dispatcher */
protected $dispatcher;
protected Dispatcher $dispatcher;
/** @var Filter */
protected $filter;
protected Filter $filter;
public function __construct(Dispatcher $dispatcher, Filter $filter)
{
@ -27,6 +25,12 @@ class UserPanelComposer
$user = auth()->user();
$avatarUrl = route('avatar.texture', ['tid' => $user->avatar, 'size' => 45], false);
$avatar = $this->filter->apply('user_avatar', $avatarUrl, [$user]);
$avatarPNG = route(
'avatar.texture',
['tid' => $user->avatar, 'size' => 45, 'png' => true],
false
);
$avatarPNG = $this->filter->apply('user_avatar', $avatarPNG, [$user]);
$badges = [];
if ($user->isAdmin()) {
@ -35,6 +39,11 @@ class UserPanelComposer
$this->dispatcher->dispatch(new \App\Events\RenderingBadges($badges));
$badges = $this->filter->apply('user_badges', $badges, [$user]);
$view->with(compact('user', 'avatar', 'badges'));
$view->with([
'user' => $user,
'avatar' => $avatar,
'avatar_png' => $avatarPNG,
'badges' => $badges,
]);
}
}

View File

@ -7,8 +7,7 @@ use Symfony\Component\Finder\SplFileInfo;
class CleanUpFrontEndLocaleFiles
{
/** @var Filesystem */
protected $filesystem;
protected Filesystem $filesystem;
public function __construct(Filesystem $filesystem)
{

View File

@ -7,8 +7,7 @@ use Illuminate\Filesystem\Filesystem;
class CopyPluginAssets
{
/** @var Filesystem */
protected $filesystem;
protected Filesystem $filesystem;
public function __construct(Filesystem $filesystem)
{

View File

@ -3,7 +3,7 @@
namespace App\Listeners;
use App\Models\User;
use Event;
use Illuminate\Support\Facades\Event;
class NotifyFailedPlugin
{

View File

@ -7,8 +7,7 @@ use Illuminate\Http\Request;
class SetAppLocale
{
/** @var Request */
protected $request;
protected Request $request;
public function __construct(Request $request)
{

View File

@ -4,6 +4,7 @@ namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Headers;
use Illuminate\Queue\SerializesModels;
class EmailVerification extends Mailable
@ -26,4 +27,13 @@ class EmailVerification extends Mailable
->subject(trans('user.verification.mail.title', ['sitename' => $site_name]))
->view('mails.email-verification');
}
public function headers(): Headers
{
return new Headers(
text: [
'Auto-Submitted' => 'auto-generated',
]
);
}
}

View File

@ -4,6 +4,7 @@ namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Headers;
use Illuminate\Queue\SerializesModels;
class ForgotPassword extends Mailable
@ -26,4 +27,13 @@ class ForgotPassword extends Mailable
->subject(trans('auth.forgot.mail.title', ['sitename' => $site_name]))
->view('mails.password-reset');
}
public function headers(): Headers
{
return new Headers(
text: [
'Auto-Submitted' => 'auto-generated',
]
);
}
}

View File

@ -3,8 +3,8 @@
namespace App\Models;
use App\Events\PlayerProfileUpdated;
use App\Models;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Lorisleiva\LaravelSearchString\Concerns\SearchString;
@ -23,6 +23,7 @@ use Lorisleiva\LaravelSearchString\Concerns\SearchString;
*/
class Player extends Model
{
use HasFactory;
use SearchString;
public const CREATED_AT = null;
@ -55,17 +56,17 @@ class Player extends Model
public function user()
{
return $this->belongsTo(Models\User::class, 'uid');
return $this->belongsTo(User::class, 'uid');
}
public function skin()
{
return $this->belongsTo(Models\Texture::class, 'tid_skin');
return $this->belongsTo(Texture::class, 'tid_skin');
}
public function cape()
{
return $this->belongsTo(Models\Texture::class, 'tid_cape');
return $this->belongsTo(Texture::class, 'tid_cape');
}
public function getModelAttribute()

19
app/Models/Scope.php Normal file
View File

@ -0,0 +1,19 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property string $name
* @property string $description
*/
class Scope extends Model
{
public $timestamps = false;
protected $fillable = [
'name', 'description',
];
}

View File

@ -4,6 +4,7 @@ namespace App\Models;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
@ -23,6 +24,8 @@ use Illuminate\Support\Carbon;
*/
class Texture extends Model
{
use HasFactory;
public $primaryKey = 'tid';
public const CREATED_AT = 'upload_at';
public const UPDATED_AT = null;

View File

@ -4,11 +4,11 @@ namespace App\Models;
use App\Models\Concerns\HasPassword;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
use Lorisleiva\LaravelSearchString\Concerns\SearchString;
use Tymon\JWTAuth\Contracts\JWTSubject;
/**
* @property int $uid
@ -20,6 +20,7 @@ use Tymon\JWTAuth\Contracts\JWTSubject;
* @property int $score
* @property int $permission
* @property string $ip
* @property bool $is_dark_mode
* @property string $last_sign_at
* @property string $register_at
* @property bool $verified
@ -27,17 +28,18 @@ use Tymon\JWTAuth\Contracts\JWTSubject;
* @property Collection $players
* @property Collection $closet
*/
class User extends Authenticatable implements JWTSubject
class User extends Authenticatable
{
use Notifiable;
use HasFactory;
use HasPassword;
use HasApiTokens;
use SearchString;
const BANNED = -1;
const NORMAL = 0;
const ADMIN = 1;
const SUPER_ADMIN = 2;
public const BANNED = -1;
public const NORMAL = 0;
public const ADMIN = 1;
public const SUPER_ADMIN = 2;
protected $primaryKey = 'uid';
public $timestamps = false;
@ -52,6 +54,7 @@ class User extends Authenticatable implements JWTSubject
'avatar' => 'integer',
'permission' => 'integer',
'verified' => 'bool',
'is_dark_mode' => 'bool',
];
protected $hidden = ['password', 'remember_token'];
@ -64,6 +67,7 @@ class User extends Authenticatable implements JWTSubject
'last_sign_at' => ['date' => true],
'register_at' => ['date' => true],
'verified' => ['boolean' => true],
'is_dark_mode' => ['boolean' => true],
];
public function isAdmin(): bool
@ -108,14 +112,4 @@ class User extends Authenticatable implements JWTSubject
{
return $this->uid;
}
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [];
}
}

View File

@ -25,8 +25,6 @@ class SiteMessage extends Notification implements ShouldQueue
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
*
* @return array
*/
public function via($notifiable)
@ -37,8 +35,6 @@ class SiteMessage extends Notification implements ShouldQueue
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @return array
*/
public function toArray($notifiable)

View File

@ -0,0 +1,33 @@
<?php
namespace App\Observers;
use App\Models\Scope;
use Illuminate\Support\Facades\Cache;
class ScopeObserver
{
/**
* Handle the Scope "saved" event.
*/
public function saved(): void
{
$this->refreshCachedScopes();
}
/**
* Handle the Scope "deleted" event.
*/
public function deleted(): void
{
$this->refreshCachedScopes();
}
protected function refreshCachedScopes(): void
{
Cache::forget('scopes');
Cache::rememberForever('scopes', function () {
return Scope::pluck('description', 'name')->toArray();
});
}
}

View File

@ -4,20 +4,23 @@ namespace App\Providers;
use App\Services;
use Illuminate\Http\Request;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register()
public function register(): void
{
$this->app->singleton('cipher', 'App\Services\Cipher\\'.config('secure.cipher'));
$this->app->singleton(Services\Option::class);
$this->app->alias(Services\Option::class, 'options');
$this->app->singleton(Services\Webpack::class);
}
public function boot(Request $request)
public function boot(Request $request): void
{
Paginator::useBootstrap();
$this->configureUrlGenerator($request);
}
@ -33,8 +36,8 @@ class AppServiceProvider extends ServiceProvider
// Replace HTTP_HOST with site_url set in options,
// to prevent CDN source problems.
if ($this->app['url']->isValidUrl($rootUrl)) {
$this->app['url']->forceRootUrl($rootUrl);
if (URL::isValidUrl($rootUrl)) {
URL::forceRootUrl($rootUrl);
}
}
@ -50,7 +53,7 @@ class AppServiceProvider extends ServiceProvider
|| $request->server('HTTP_X_FORWARDED_SSL') === 'on';
if (option('force_ssl') || $isRequestSecure) {
$this->app['url']->forceScheme('https');
URL::forceScheme('https');
}
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Providers;
use App\Models\Scope;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
];
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
$defaultScopes = [
'User.Read' => 'auth.oauth.scope.user.read',
'Notification.Read' => 'auth.oauth.scope.notification.read',
'Notification.ReadWrite' => 'auth.oauth.scope.notification.readwrite',
'Player.Read' => 'auth.oauth.scope.player.read',
'Player.ReadWrite' => 'auth.oauth.scope.player.readwrite',
'Closet.Read' => 'auth.oauth.scope.closet.read',
'Closet.ReadWrtie' => 'auth.oauth.scope.closet.readwrite',
'UsersManagement.Read' => 'auth.oauth.scope.users-management.read',
'UsersManagement.ReadWrite' => 'auth.oauth.scope.users-management.readwrite',
'PlayersManagement.Read' => 'auth.oauth.scope.players-management.read',
'PlayersManagement.ReadWrite' => 'auth.oauth.scope.players-management.readwrite',
'ClosetManagement.Read' => 'auth.oauth.scope.closet-management.read',
'ClosetManagement.ReadWrite' => 'auth.oauth.scope.closet-management.readwrite',
'ReportsManagement.Read' => 'auth.oauth.scope.reports-management.read',
'ReportsManagement.ReadWrite' => 'auth.oauth.scope.reports-management.readwrite',
];
/*
* Return empty scopes if running unit tests or before installation.
* In these cases, migrations arent run yet, so DB queries will fail.
* OAuth isnt tested in unit tests, so returning empty scopes should be fine...?
* Maybe the best approach is to run migrations before bootstrap in tests,
* but this seems impossible for DB_DATABASE=:memory:;
* Or change how scopes are registered so they don't depend on the database,
* but that may introduce BREAKING CHANGES and plugin incompatibility.
* PRs welcome for better solutions!
*/
$scopes = (app()->runningUnitTests() || !Storage::disk('root')->exists('storage/install.lock')) ? [] : Cache::rememberForever('scopes', function () {
return Scope::pluck('description', 'name')->toArray();
});
Passport::tokensCan(array_merge($defaultScopes, $scopes));
Passport::setDefaultScope(['User.Read']);
}
}

View File

@ -3,11 +3,17 @@
namespace App\Providers;
use App\Listeners;
use App\Models\Scope;
use App\Observers\ScopeObserver;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
// The event listener mappings for the application.
/**
* The event to listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [
'App\Events\PluginWasEnabled' => [
Listeners\CopyPluginAssets::class,
@ -39,4 +45,12 @@ class EventServiceProvider extends ServiceProvider
Listeners\SetAppLocale::class,
],
];
/**
* Register any events for your application.
*/
public function boot(): void
{
Scope::observe(ScopeObserver::class);
}
}

View File

@ -7,13 +7,13 @@ use Illuminate\Support\ServiceProvider;
class PluginServiceProvider extends ServiceProvider
{
public function register()
public function register(): void
{
$this->app->singleton(PluginManager::class);
$this->app->alias(PluginManager::class, 'plugins');
}
public function boot(PluginManager $plugins)
public function boot(PluginManager $plugins): void
{
$plugins->boot();
}

View File

@ -5,9 +5,8 @@ namespace App\Providers;
use App\Events\ConfigureRoutes;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Laravel\Passport\Passport;
use Route;
class RouteServiceProvider extends ServiceProvider
{
@ -20,7 +19,7 @@ class RouteServiceProvider extends ServiceProvider
/**
* Define the routes for the application.
*/
public function map(Router $router)
public function map(Router $router): void
{
$this->mapStaticRoutes($router);
@ -28,10 +27,9 @@ class RouteServiceProvider extends ServiceProvider
$this->mapApiRoutes();
Passport::routes();
foreach ($router->getRoutes()->getRoutesByName() as $name => $route) {
if (Str::startsWith($name, ['passport.authorizations', 'passport.tokens', 'passport.clients'])) {
$route->middleware('verified');
$route->middleware(['auth', 'verified']);
}
}
@ -42,7 +40,7 @@ class RouteServiceProvider extends ServiceProvider
* Define the "web" routes for the application.
* These routes all receive session state, CSRF protection, etc.
*/
protected function mapWebRoutes(Router $router)
protected function mapWebRoutes(Router $router): void
{
Route::middleware(['web'])
->namespace($this->namespace)
@ -53,7 +51,7 @@ class RouteServiceProvider extends ServiceProvider
* Define the "static" routes for the application.
* These routes will not load session, etc.
*/
protected function mapStaticRoutes(Router $router)
protected function mapStaticRoutes(Router $router): void
{
Route::namespace($this->namespace)
->group(base_path('routes/static.php'));
@ -63,7 +61,7 @@ class RouteServiceProvider extends ServiceProvider
* Define the "api" routes for the application.
* These routes are typically stateless.
*/
protected function mapApiRoutes()
protected function mapApiRoutes(): void
{
Route::prefix('api')
->middleware(

View File

@ -3,14 +3,12 @@
namespace App\Providers;
use App\Http\View\Composers;
use App\Services\Webpack;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use View;
class ViewServiceProvider extends ServiceProvider
{
public function boot(Webpack $webpack)
public function boot(): void
{
View::composer([
'home',
@ -24,6 +22,7 @@ class ViewServiceProvider extends ServiceProvider
'site_name' => option_localized('site_name'),
'navbar_color' => $color,
'color_mode' => in_array($color, $lightColors) ? 'light' : 'dark',
'dark_mode' => (bool) optional(auth()->user())->is_dark_mode,
'locale' => str_replace('_', '-', app()->getLocale()),
]);
});
@ -48,7 +47,11 @@ class ViewServiceProvider extends ServiceProvider
View::composer('shared.user-menu', Composers\UserMenuComposer::class);
View::composer('shared.sidebar', function ($view) {
$view->with('sidebar_color', option('sidebar_color'));
$isDarkMode = (bool) optional(auth()->user())->is_dark_mode;
$color = option('sidebar_color');
$color = $isDarkMode ? str_replace('light', 'dark', $color) : $color;
$view->with('sidebar_color', $color);
});
View::composer('shared.side-menu', Composers\SideMenuComposer::class);
@ -66,14 +69,14 @@ class ViewServiceProvider extends ServiceProvider
View::composer('shared.foot', Composers\FootComposer::class);
View::composer(['errors.*', 'setup.*'], function ($view) use ($webpack) {
// @codeCoverageIgnoreStart
if (Str::startsWith(config('app.asset.env'), 'dev')) {
$view->with(['scripts' => [$webpack->url('spectre.js')]]);
} else {
$view->with('styles', [$webpack->url('spectre.css')]);
}
// @codeCoverageIgnoreEnd
View::composer('shared.dark-mode', function ($view) {
$view->with([
'dark_mode' => (bool) optional(auth()->user())->is_dark_mode,
]);
});
View::composer('assets.*', function ($view) {
$view->with('cdn_base', option('cdn_address', ''));
});
}
}

View File

@ -21,6 +21,9 @@ class PlayerName implements Rule
$regexp = '/^[A-Za-z0-9_§\x{4e00}-\x{9fff}]+$/u';
break;
case 'utf8':
return mb_check_encoding($value, 'UTF-8') && !preg_match('/\s/', $value);
case 'custom':
$regexp = option('custom_player_name_regexp') ?: $regexp;
break;

View File

@ -8,10 +8,10 @@ use App\Events;
use App\Notifications;
use Blessing\Filter;
use Closure;
use Event;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Str;
use Notification;
class Hook
{
@ -66,9 +66,7 @@ class Hook
$urls = collect($urls);
$pages = collect($pages);
resolve(Filter::class)->add('head_links', function ($links) use ($urls, $pages) {
$matched = $pages->some(function ($page) {
return request()->is($page);
});
$matched = $pages->some(fn ($page) => request()->is($page));
if ($matched) {
$urls->each(function ($url) use (&$links) {
$links[] = [
@ -88,9 +86,7 @@ class Hook
$urls = collect($urls);
$pages = collect($pages);
resolve(Filter::class)->add('scripts', function ($scripts) use ($urls, $pages) {
$matched = $pages->some(function ($page) {
return request()->is($page);
});
$matched = $pages->some(fn ($page) => request()->is($page));
if ($matched) {
$urls->each(function ($url) use (&$scripts) {
$scripts[] = ['src' => $url, 'crossorigin' => 'anonymous'];

View File

@ -2,10 +2,10 @@
namespace App\Services;
use DB;
use Illuminate\Database\QueryException;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
class Option
{
@ -20,13 +20,14 @@ class Option
return;
}
try {
$this->items = DB::table('options')->get()->mapWithKeys(function ($item) {
return [$item->option_name => $item->option_value];
});
} catch (QueryException $e) {
if (!file_exists(storage_path('install.lock')) || app()->runningUnitTests()) {
$this->items = collect();
return;
}
$this->items = DB::table('options')
->get()
->mapWithKeys(fn ($item) => [$item->option_name => $item->option_value]);
}
public function get($key, $default = null, $raw = false)

View File

@ -2,10 +2,10 @@
namespace App\Services;
use App\Services\Facades\Option;
use BadMethodCallException;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Option;
use ReflectionClass;
/**
@ -21,7 +21,7 @@ class OptionForm
* Pass this value to tell generator to
* load text from language files automatically.
*/
const AUTO_DETECT = 0x97ab1;
public const AUTO_DETECT = 0x97AB1;
protected $id;
protected $title;
@ -38,7 +38,7 @@ class OptionForm
protected $hookBefore;
protected $hookAfter;
protected $alwaysCallback = null;
protected $alwaysCallback;
protected $renderWithoutTable = false;
protected $renderInputTagsOnly = false;
@ -59,7 +59,7 @@ class OptionForm
}
/**
* @throws \BadMethodCallException
* @throws BadMethodCallException
*/
public function __call(string $method, array $params): OptionFormItem
{
@ -105,7 +105,6 @@ class OptionForm
* Add a piece of data to the option form.
*
* @param string|array $key
* @param mixed $value
*/
public function with($key, $value = null): self
{
@ -204,7 +203,7 @@ class OptionForm
/**
* Handle the HTTP post request and update modified options.
*/
public function handle(callable $callback = null): self
public function handle(?callable $callback = null): self
{
$request = request();
$allPostData = $request->all();
@ -329,7 +328,7 @@ class OptionForm
$this->assignValues();
return view('forms.form')
->with(array_merge(get_object_vars($this)))
->with(get_object_vars($this))
->render();
}
@ -352,7 +351,7 @@ class OptionFormItem
public $format;
public $value = null;
public $value;
public $disabled;

View File

@ -9,25 +9,17 @@ use Illuminate\Support\Str;
class Plugin
{
const README_FILES = [
public const README_FILES = [
'README.md',
'readme.md',
'README.MD',
];
/**
* The full path of this plugin.
*
* @var string
*/
protected $path;
/** The full path of this plugin. */
protected string $path;
/**
* package.json of the package.
*
* @var array
*/
protected $manifest;
/** package.json of the package. */
protected array $manifest;
protected $enabled = false;
@ -61,9 +53,10 @@ class Plugin
public function getReadme()
{
return Arr::first(self::README_FILES, function ($filename) {
return file_exists($this->path.'/'.$filename);
});
return Arr::first(
self::README_FILES,
fn ($filename) => file_exists($this->path.'/'.$filename)
);
}
public function hasConfig(): bool

View File

@ -16,35 +16,27 @@ use Illuminate\Support\Str;
class PluginManager
{
/** @var bool */
protected $booted = false;
protected bool $booted = false;
/** @var Application */
protected $app;
protected Application $app;
/** @var Option */
protected $option;
protected Option $option;
/** @var Dispatcher */
protected $dispatcher;
protected Dispatcher $dispatcher;
/** @var Filesystem */
protected $filesystem;
protected Filesystem $filesystem;
/** @var ClassLoader */
protected $loader;
protected ClassLoader $loader;
/** @var Collection|null */
protected $plugins;
protected ?Collection $plugins;
/** @var Collection */
protected $enabled;
protected Collection $enabled;
public function __construct(
Application $app,
Option $option,
Dispatcher $dispatcher,
Filesystem $filesystem
Filesystem $filesystem,
) {
$this->app = $app;
$this->option = $option;
@ -56,28 +48,20 @@ class PluginManager
public function all(): Collection
{
if (filled($this->plugins)) {
if (isset($this->plugins)) {
return $this->plugins;
}
$this->enabled = collect(json_decode($this->option->get('plugins_enabled', '[]'), true))
->reject(function ($item) {
return is_string($item);
})
->mapWithKeys(function ($item) {
return [$item['name'] => ['version' => $item['version']]];
});
->reject(fn ($item) => is_string($item))
->mapWithKeys(fn ($item) => [$item['name'] => ['version' => $item['version']]]);
$plugins = collect();
$versionChanged = [];
$this->getPluginsDirs()
->flatMap(function ($directory) {
return $this->filesystem->directories($directory);
})
->flatMap(fn ($dir) => $this->filesystem->directories($dir))
->unique()
->filter(function ($directory) {
return $this->filesystem->exists($directory.DIRECTORY_SEPARATOR.'package.json');
})
->filter(fn ($dir) => $this->filesystem->exists($dir.DIRECTORY_SEPARATOR.'package.json'))
->each(function ($directory) use (&$plugins, &$versionChanged) {
$manifest = json_decode(
$this->filesystem->get($directory.DIRECTORY_SEPARATOR.'package.json'),
@ -126,18 +110,12 @@ class PluginManager
return;
}
$this->all()->each(function ($plugin) {
$this->loadViewsAndTranslations($plugin);
});
$this->all()->each(fn ($plugin) => $this->loadViewsAndTranslations($plugin));
$enabled = $this->getEnabledPlugins();
$enabled->each(function ($plugin) {
$this->registerPlugin($plugin);
});
$enabled->each(fn ($plugin) => $this->registerPlugin($plugin));
$this->loader->register();
$enabled->each(function ($plugin) {
$this->bootPlugin($plugin);
});
$enabled->each(fn ($plugin) => $this->bootPlugin($plugin));
$this->registerLifecycleHooks();
$this->booted = true;
@ -324,9 +302,7 @@ class PluginManager
public function getEnabledPlugins(): Collection
{
return $this->all()->filter(function ($plugin) {
return $plugin->isEnabled();
});
return $this->all()->filter(fn ($plugin) => $plugin->isEnabled());
}
/**
@ -334,9 +310,13 @@ class PluginManager
*/
protected function saveEnabled()
{
$this->option->set('plugins_enabled', $this->enabled->map(function ($info, $name) {
return array_merge(compact('name'), $info);
})->values()->toJson());
$this->option->set(
'plugins_enabled',
$this->enabled
->map(fn ($info, $name) => array_merge(['name' => $name], $info))
->values()
->toJson()
);
}
public function getUnsatisfied(Plugin $plugin)
@ -386,7 +366,7 @@ class PluginManager
*/
public function formatUnresolved(
Collection $unsatisfied,
Collection $conflicts
Collection $conflicts,
): array {
$unsatisfied = $unsatisfied->map(function ($detail, $name) {
if ($name === 'blessing-skin-server') {
@ -419,9 +399,7 @@ class PluginManager
$config = config('plugins.directory');
if ($config) {
return collect(preg_split('/,\s*/', $config))
->map(function ($directory) {
return realpath($directory) ?: $directory;
});
->map(fn ($directory) => realpath($directory) ?: $directory);
} else {
return collect([base_path('plugins')]);
}

View File

@ -9,21 +9,18 @@ use Illuminate\Filesystem\Filesystem;
class JavaScript
{
/** @var Filesystem */
protected $filesystem;
protected Filesystem $filesystem;
/** @var Repository */
protected $cache;
protected Repository $cache;
/** @var PluginManager */
protected $plugins;
protected PluginManager $plugins;
protected $prefix = 'front-end-trans-';
public function __construct(
Filesystem $filesystem,
Repository $cache,
PluginManager $plugins
PluginManager $plugins,
) {
$this->filesystem = $filesystem;
$this->cache = $cache;
@ -34,16 +31,10 @@ class JavaScript
{
$plugins = $this->plugins->getEnabledPlugins();
$sourceFiles = $plugins
->map(function (Plugin $plugin) use ($locale) {
return $plugin->getPath()."/lang/$locale/front-end.yml";
})
->filter(function ($path) {
return $this->filesystem->exists($path);
});
->map(fn (Plugin $plugin) => $plugin->getPath()."/lang/$locale/front-end.yml")
->filter(fn ($path) => $this->filesystem->exists($path));
$sourceFiles->push(resource_path("lang/$locale/front-end.yml"));
$sourceModified = $sourceFiles->max(function ($path) {
return $this->filesystem->lastModified($path);
});
$sourceModified = $sourceFiles->max(fn ($path) => $this->filesystem->lastModified($path));
$compiled = public_path("lang/$locale.js");
$compiledModified = (int) $this->cache->get($this->prefix.$locale, 0);
@ -51,10 +42,7 @@ class JavaScript
if ($sourceModified > $compiledModified || !$this->filesystem->exists($compiled)) {
$translations = trans('front-end');
foreach ($plugins as $plugin) {
$translations = array_merge(
$translations,
[$plugin->name => trans($plugin->namespace.'::front-end')]
);
$translations[$plugin->name] = trans($plugin->namespace.'::front-end');
}
$content = 'blessing.i18n = '.json_encode($translations, JSON_UNESCAPED_UNICODE);

View File

@ -6,14 +6,15 @@ use Spatie\TranslationLoader\TranslationLoaderManager;
class Loader extends TranslationLoaderManager
{
protected function loadPath($path, $locale, $group)
protected function loadPaths(array $paths, $locale, $group)
{
$translations = parent::loadPath($path, $locale, $group);
return collect($paths)
->reduce(function ($output, $path) use ($locale, $group) {
if ($this->files->exists($full = "{$path}/{$locale}/{$group}.yml")) {
$output = resolve(Yaml::class)->parse($full);
}
$full = "{$path}/{$locale}/{$group}.yml";
return count($translations) === 0 && $this->files->exists($full)
? resolve(Yaml::class)->parse($full)
: [];
return $output;
}, []);
}
}

View File

@ -7,8 +7,7 @@ use Symfony\Component\Yaml\Yaml as YamlParser;
class Yaml
{
/** @var Repository */
protected $cache;
protected Repository $cache;
protected $prefix = 'yaml-trans-';

View File

@ -10,11 +10,9 @@ use ZipArchive;
class Unzip
{
/** @var Filesystem */
protected $filesystem;
protected Filesystem $filesystem;
/** @var ZipArchive */
protected $zipper;
protected ZipArchive $zipper;
public function __construct(Filesystem $filesystem, ZipArchive $zipper)
{

View File

@ -1,53 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Services;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class Webpack
{
protected $manifest = [];
/** @var Option */
protected $options;
protected $urlGenerator;
public function __construct(
Filesystem $filesystem,
Option $options,
UrlGenerator $urlGenerator
) {
$path = public_path('app/manifest.json');
if ($filesystem->exists($path)) {
$this->manifest = json_decode($filesystem->get($path), true);
}
$this->options = $options;
$this->urlGenerator = $urlGenerator;
}
public function __get(string $path)
{
return Arr::get($this->manifest, $path, '');
}
public function url(string $path): string
{
if (Str::startsWith(config('app.asset.env'), 'dev')) {
$root = config('app.asset.url').':8080';
return $this->urlGenerator->assetFrom($root, $path);
} else {
$path = $this->$path;
$cdn = $this->options->get('cdn_address');
return $this->urlGenerator->assetFrom($cdn, "/app/$path");
}
}
}

View File

@ -52,21 +52,11 @@ if (!function_exists('option')) {
* Get / set the specified option value.
*
* If an array is passed as the key, we will assume you want to set an array of values.
*
* @param array|string $key
* @param mixed $default
* @param bool $raw return raw value without convertion
*
* @return mixed
*/
function option($key = null, $default = null, $raw = false)
function option(string|array $key, mixed $default = null, bool $raw = false)
{
$options = app('options');
if (is_null($key)) {
return $options;
}
if (is_array($key)) {
$options->set($key);

View File

@ -29,27 +29,7 @@ ini_set('display_errors', true);
file_put_contents($envPath, preg_replace('/APP_KEY\s*=\s*/', 'APP_KEY='.$key."\n\n", $envFile));
}
$requiredFunctions = ['symlink', 'readlink', 'putenv', 'realpath'];
$disabledFunctions = preg_split('/,\s*/', ini_get('disable_functions'));
foreach ($requiredFunctions as $fn) {
if (in_array($fn, $disabledFunctions)) {
die_with_utf8_encoding(
'[Error] Please don\'t disable built-in function "'.$fn.'", which is specified in "php.ini" file.<br>'.
"[错误] 请不要在 php.ini 中禁用 $fn 函数。".
'<strong>我们不建议使用您使用宝塔等面板软件,因为容易引起兼容性问题。</strong>'
);
}
}
if (!empty(ini_get('open_basedir'))) {
die_with_utf8_encoding(
'[Error] Please disable "open_basedir" option by editing "php.ini" file.<br>'.
'[错误] 请修改 php.ini 以关闭 "open_basedir" 选项。'.
'<strong>我们不建议使用您使用宝塔等面板软件,因为容易引起兼容性问题。</strong>'
);
}
$requiredVersion = '7.2.5';
$requiredVersion = '8.1.0';
preg_match('/(\d+\.\d+\.\d+)/', PHP_VERSION, $matches);
$version = $matches[1];
if (version_compare($version, $requiredVersion, '<')) {
@ -71,6 +51,7 @@ ini_set('display_errors', true);
'json',
'fileinfo',
'zip',
'imagick',
],
'write_permission' => [
'bootstrap/cache',

View File

@ -3,9 +3,10 @@
"description": "A web application brings your custom skins back in offline Minecraft servers.",
"license": "MIT",
"require": {
"php": ">=7.2.5",
"php": "^8.1",
"ext-ctype": "*",
"ext-gd": "*",
"ext-imagick": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
@ -15,46 +16,43 @@
"ext-zip": "*",
"blessing/filter": "^1.0",
"blessing/rejection": "^1.0",
"blessing/texture-renderer": "^0.1.1",
"blessing/texture-renderer": "^0.2",
"composer/ca-bundle": "^1.2",
"composer/semver": "^1.5",
"doctrine/dbal": "^2.10",
"doctrine/inflector": "^1.3",
"facade/ignition": "^2.0",
"composer/semver": "^3.2",
"doctrine/dbal": "^3.0",
"doctrine/inflector": "^2.0",
"spatie/laravel-ignition": "^2.0",
"gregwar/captcha": "1.*",
"guzzlehttp/guzzle": "^7.0",
"intervention/image": "^2.5",
"laravel/framework": "^7.0",
"laravel/passport": "^9.2",
"lorisleiva/laravel-search-string": "^0.1.6",
"intervention/image": "^2.7",
"laravel/framework": "^10.0",
"laravel/passport": "^11.0",
"lorisleiva/laravel-search-string": "^1.0",
"nesbot/carbon": "^2.0",
"nunomaduro/collision": "^4.1",
"rcrowe/twigbridge": "^0.11.3",
"spatie/laravel-translation-loader": "^2.6",
"symfony/process": "^5.0",
"nunomaduro/collision": "^7.0",
"rcrowe/twigbridge": "^0.14",
"spatie/laravel-translation-loader": "^2.7",
"symfony/process": "^6.0",
"symfony/yaml": "^5.0",
"twig/twig": "^2.11",
"tymon/jwt-auth": "dev-develop",
"vectorface/whip": "^0.3.2"
"twig/twig": "^3.0",
"vectorface/whip": "^0.4.0"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.2",
"fzaninotto/faker": "~1.9",
"laravel/browser-kit-testing": "^6.0",
"laravel/tinker": "^2.2",
"mockery/mockery": "1.3.*",
"phpdocumentor/reflection-docblock": "^5.1",
"phpunit/phpunit": "^8.5",
"symfony/css-selector": "^5.0",
"symfony/dom-crawler": "^5.0"
"barryvdh/laravel-debugbar": "^3.5",
"barryvdh/laravel-ide-helper": "^2.10",
"fakerphp/faker": "^1.13",
"friendsofphp/php-cs-fixer": "^3.13",
"laravel/browser-kit-testing": "^7.0",
"laravel/tinker": "^2.4",
"mockery/mockery": "^1.4",
"phpunit/phpunit": "^10.0",
"symfony/css-selector": "^6.2",
"symfony/dom-crawler": "^6.2"
},
"autoload": {
"classmap": [
"app/Models",
"database"
],
"psr-4": {
"App\\": "app/"
"App\\": "app/",
"Database\\Factories\\": "database/factories/"
},
"files": [
"app/helpers.php"
@ -73,6 +71,7 @@
]
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
},

20795
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
<?php
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\ServiceProvider;
return [
/*
|--------------------------------------------------------------------------
@ -9,7 +12,8 @@ return [
| Version of Blessing Skin Server.
|
*/
'version' => '5.2.0',
'version' => '6.0.2',
/*
|--------------------------------------------------------------------------
@ -19,12 +23,24 @@ return [
| Where to get information of new versions.
|
*/
'update_source' => env(
'UPDATE_SOURCE',
'https://dev.azure.com/blessing-skin/51010f6d-9f99-40f1-a262-0a67f788df32/_apis/git/'.
'repositories/a9ff8df7-6dc3-4ff8-bb22-4871d3a43936/Items?path=%2Fupdate.json'
),
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
*/
'name' => env('APP_NAME', 'blessing_skin'),
/*
@ -66,19 +82,6 @@ return [
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Assets
|--------------------------------------------------------------------------
|
| This is related to front-end assets. The asset URL is only available for
| development, not for production.
*/
'asset' => [
'env' => env('ASSET_ENV', 'production'),
'url' => env('ASSET_URL', 'http://localhost'),
],
/*
|--------------------------------------------------------------------------
| Application Timezone
@ -146,6 +149,24 @@ return [
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => 'file',
// 'store' => 'redis',
],
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
@ -157,39 +178,21 @@ return [
|
*/
'providers' => [
'providers' => ServiceProvider::defaultProviders()->merge([
/*
* Laravel Framework Service Providers...
* Package Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\PluginServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\ViewServiceProvider::class,
],
])->toArray(),
/*
|--------------------------------------------------------------------------
@ -202,44 +205,7 @@ return [
|
*/
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Http' => Illuminate\Support\Facades\Http::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
/*
* Blessing Skin
*/
'Option' => App\Services\Facades\Option::class,
],
'aliases' => Facade::defaultAliases()->merge([
'Option' => App\Services\Facades\Option::class,
])->toArray(),
];

View File

@ -1,7 +1,6 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
@ -103,5 +102,4 @@ return [
'expire' => 60,
],
],
];

View File

@ -1,7 +1,6 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
@ -11,7 +10,7 @@ return [
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "redis", "log", "null"
| Supported: "pusher", "ably", "redis", "log", "null"
|
*/
@ -29,7 +28,6 @@ return [
*/
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
@ -37,8 +35,20 @@ return [
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true,
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https'),
'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
'ably' => [
'driver' => 'ably',
'key' => env('ABLY_KEY'),
],
'redis' => [
@ -53,7 +63,5 @@ return [
'null' => [
'driver' => 'null',
],
],
];

View File

@ -3,7 +3,6 @@
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
@ -13,9 +12,6 @@ return [
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
| Supported: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb"
|
*/
'default' => env('CACHE_DRIVER', 'file'),
@ -29,10 +25,12 @@ return [
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb", "octane", "null"
|
*/
'stores' => [
'apc' => [
'driver' => 'apc',
],
@ -46,11 +44,13 @@ return [
'driver' => 'database',
'table' => 'cache',
'connection' => null,
'lock_connection' => null,
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'memcached' => [
@ -75,6 +75,7 @@ return [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
],
'dynamodb' => [
@ -86,6 +87,9 @@ return [
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
],
/*
@ -93,12 +97,11 @@ return [
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing a RAM based store such as APC or Memcached, there might
| be other applications utilizing the same cache. So, we'll specify a
| value to get prefixed to all our keys so we can avoid collisions.
| When utilizing the APC, database, memcached, Redis, or DynamoDB cache
| stores there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'blessing_skin'), '_').'_cache'),
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'blessing_skin'), '_').'_cache_'),
];

View File

@ -3,7 +3,6 @@
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
@ -34,7 +33,6 @@ return [
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DATABASE_URL'),
@ -56,7 +54,7 @@ return [
'collation' => 'utf8mb4_unicode_ci',
'prefix' => env('DB_PREFIX', ''),
'prefix_indexes' => true,
'strict' => false,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
@ -74,7 +72,7 @@ return [
'charset' => 'utf8',
'prefix' => env('DB_PREFIX', ''),
'prefix_indexes' => true,
'schema' => 'public',
'search_path' => 'public',
'sslmode' => 'prefer',
],
@ -89,8 +87,9 @@ return [
'charset' => 'utf8',
'prefix' => env('DB_PREFIX', ''),
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
@ -118,7 +117,6 @@ return [
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
@ -129,7 +127,8 @@ return [
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
@ -137,11 +136,10 @@ return [
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
];

View File

@ -1,7 +1,6 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Debugbar Settings
@ -16,7 +15,8 @@ return [
'enabled' => env('DEBUGBAR_ENABLED', null),
'except' => [
//
'telescope*',
'horizon*',
],
/*
@ -32,13 +32,57 @@ return [
|
*/
'storage' => [
'enabled' => true,
'driver' => 'file', // redis, file, pdo, custom
'path' => storage_path('debugbar'), // For file driver
'enabled' => true,
'driver' => 'file', // redis, file, pdo, socket, custom
'path' => storage_path('debugbar'), // For file driver
'connection' => null, // Leave null for default connection (Redis/PDO)
'provider' => '', // Instance of StorageInterface for custom driver
'provider' => '', // Instance of StorageInterface for custom driver
'hostname' => '127.0.0.1', // Hostname to use with the "socket" driver
'port' => 2304, // Port to use with the "socket" driver
],
/*
|--------------------------------------------------------------------------
| Editor
|--------------------------------------------------------------------------
|
| Choose your preferred editor to use when clicking file name.
|
| Supported: "phpstorm", "vscode", "vscode-insiders", "vscode-remote",
| "vscode-insiders-remote", "vscodium", "textmate", "emacs",
| "sublime", "atom", "nova", "macvim", "idea", "netbeans",
| "xdebug", "espresso"
|
*/
'editor' => env('DEBUGBAR_EDITOR', 'phpstorm'),
/*
|--------------------------------------------------------------------------
| Remote Path Mapping
|--------------------------------------------------------------------------
|
| If you are using a remote dev server, like Laravel Homestead, Docker, or
| even a remote VPS, it will be necessary to specify your path mapping.
|
| Leaving one, or both of these, empty or null will not trigger the remote
| URL changes and Debugbar will treat your editor links as local files.
|
| "remote_sites_path" is an absolute base path for your sites or projects
| in Homestead, Vagrant, Docker, or another remote development server.
|
| Example value: "/home/vagrant/Code"
|
| "local_sites_path" is an absolute base path for your sites or projects
| on your local computer where your IDE or code editor is running on.
|
| Example values: "/Users/<name>/Code", "C:\Users\<name>\Documents\Code"
|
*/
'remote_sites_path' => env('DEBUGBAR_REMOTE_SITES_PATH', ''),
'local_sites_path' => env('DEBUGBAR_LOCAL_SITES_PATH', ''),
/*
|--------------------------------------------------------------------------
| Vendors
@ -47,7 +91,7 @@ return [
| Vendor files are included by default, but can be set to false.
| This can also be set to 'js' or 'css', to only include javascript or css vendor files.
| Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
| and for js: jquery and and highlight.js
| and for js: jquery and highlight.js
| So if you want syntax highlighting, set it to true.
| jQuery is set to not conflict with existing jQuery scripts.
|
@ -64,6 +108,9 @@ return [
| you can use this option to disable sending the data through the headers.
|
| Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools.
|
| Note for your request to be identified as ajax requests they must either send the header
| X-Requested-With with the value XMLHttpRequest (most JS libraries send this), or have application/json as a Accept header.
*/
'capture_ajax' => true,
@ -101,27 +148,29 @@ return [
*/
'collectors' => [
'phpinfo' => true, // Php version
'messages' => true, // Messages
'time' => true, // Time Datalogger
'memory' => true, // Memory usage
'exceptions' => true, // Exception displayer
'log' => true, // Logs from Monolog (merged in messages if enabled)
'db' => true, // Show database (PDO) queries and bindings
'views' => true, // Views with their data
'route' => true, // Current route information
'auth' => true, // Display Laravel authentication status
'gate' => false, // Display Laravel Gate checks
'session' => true, // Display session data
'phpinfo' => true, // Php version
'messages' => true, // Messages
'time' => true, // Time Datalogger
'memory' => true, // Memory usage
'exceptions' => true, // Exception displayer
'log' => true, // Logs from Monolog (merged in messages if enabled)
'db' => true, // Show database (PDO) queries and bindings
'views' => true, // Views with their data
'route' => true, // Current route information
'auth' => false, // Display Laravel authentication status
'gate' => true, // Display Laravel Gate checks
'session' => true, // Display session data
'symfony_request' => true, // Only one can be enabled..
'mail' => false, // Catch mail messages
'laravel' => false, // Laravel version and environment
'events' => true, // All events fired
'mail' => true, // Catch mail messages
'laravel' => false, // Laravel version and environment
'events' => false, // All events fired
'default_request' => false, // Regular or special Symfony request logger
'logs' => false, // Add the latest log messages
'files' => false, // Show the included files
'config' => false, // Display config settings
'cache' => false, // Display cache events
'logs' => false, // Add the latest log messages
'files' => false, // Show the included files
'config' => false, // Display config settings
'cache' => false, // Display cache events
'models' => true, // Display models
'livewire' => true, // Display Livewire (when available)
],
/*
@ -138,20 +187,26 @@ return [
'show_name' => true, // Also show the users name/email in the debugbar
],
'db' => [
'with_params' => true, // Render SQL with the parameters substituted
'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
'timeline' => false, // Add the queries to the timeline
'with_params' => true, // Render SQL with the parameters substituted
'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
'backtrace_exclude_paths' => [], // Paths to exclude from backtrace. (in addition to defaults)
'timeline' => false, // Add the queries to the timeline
'duration_background' => true, // Show shaded background on each query relative to how long it took to execute.
'explain' => [ // Show EXPLAIN output on queries
'enabled' => false,
'types' => ['SELECT'], // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+
'types' => ['SELECT'], // Deprecated setting, is always only SELECT
],
'hints' => true, // Show hints for common mistakes
'hints' => false, // Show hints for common mistakes
'show_copy' => false, // Show copy button next to the query,
'slow_threshold' => false, // Only track queries that last longer than this time in ms
],
'mail' => [
'full_log' => false,
],
'views' => [
'data' => false, //Note: Can slow down the application, because the data can be quite large..
'timeline' => false, // Add the views to the timeline (Experimental)
'data' => false, // Note: Can slow down the application, because the data can be quite large..
'exclude_paths' => [], // Add the paths which you don't want to appear in the views
],
'route' => [
'label' => true, // show complete route on bar
@ -198,4 +253,24 @@ return [
| To override default domain, specify it as a non-empty value.
*/
'route_domain' => null,
/*
|--------------------------------------------------------------------------
| DebugBar theme
|--------------------------------------------------------------------------
|
| Switches between light and dark theme. If set to auto it will respect system preferences
| Possible values: auto, light, dark
*/
'theme' => env('DEBUGBAR_THEME', 'auto'),
/*
|--------------------------------------------------------------------------
| Backtrace stack limit
|--------------------------------------------------------------------------
|
| By default, the DebugBar limits the number of frames returned by the 'debug_backtrace()' function.
| If you need larger stacktraces, you can increase this number. Setting it to 0 will result in no limit.
*/
'debug_backtrace_limit' => 50,
];

Some files were not shown because too many files have changed in this diff Show More