Compare commits

...

No commits in common. "main" and "XlXi/Rewrite-2" have entirely different histories.

7941 changed files with 98279 additions and 19421 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
################################################################################
/.vs
/web.old

View File

@ -1,2 +1,31 @@
# Graphictoria React App
This branch of the Graphictoria Web Platform is heavily outdated. Please see the "XlXi/Rewrite-2" branch.
<p align="center">
<img src="etc/readmecontent/WPLogoText.png?raw=true" alt="Virtubrick Logo With Web Platform Text" />
</p>
---
## Licensing
It is important that you understand licensing before accessing this repository; it's in plain english... You do not have permission to use, distribute, modify, or share the contents of this repository per[^1] [XlXi](mailto:wagnessgaming2014@gmail.com)'s discretion[^2].
[^1]: Per: For each.
[^2]: Discretion: The freedom to decide what should be done in a particular situation.
---
## Branch Rules
When committing a major change, it is highly suggested that you create a new branch following the naming scheme below:
`Username/Change`
Where "Username" is your name and "Change" is the major change you are making. Ex: `XlXi/Rewrite-2`.
These branch rules allow me to merge changes using GitHub's web interface rather than Visual Studio. It's a major time saver, so just follow the branch rules.
---
## Contacts
- [XlXi](mailto:wagnessgaming2014@gmail.com) (Owner, Primary code owner)
---
Copyright © XlXi 2023
Virtubrick

BIN
etc/art/assetbusy.pdn Normal file

Binary file not shown.

BIN
etc/art/banner.pdn Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
REM : XlXi 2022
REM : Put this in the directory of your PNG export.
REM : ImageMagick is required.
magick convert -delay 333,10000 -loop 0 -alpha set -dispose previous *.png ani.gif

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
etc/art/deleted.pdn Normal file

Binary file not shown.

BIN
etc/art/gamebusy.pdn Normal file

Binary file not shown.

BIN
etc/art/pending.pdn Normal file

Binary file not shown.

BIN
etc/art/token.psd Normal file

Binary file not shown.

BIN
etc/art/troll.pdn Normal file

Binary file not shown.

BIN
etc/art/unavailable.pdn Normal file

Binary file not shown.

BIN
etc/art/userbusy.pdn Normal file

Binary file not shown.

BIN
etc/art/vbrick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 KiB

BIN
etc/art/virtubrick.blend Normal file

Binary file not shown.

20
etc/cert/RootCA.crt Normal file
View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDNzCCAh+gAwIBAgIUeRbZ6wwayGbn2Ava8XTPh6reXu0wDQYJKoZIhvcNAQEL
BQAwKzELMAkGA1UEBhMCVVMxHDAaBgNVBAMME1ZpcnR1QnJpY2stTG9jYWwtQ0Ew
HhcNMjIxMjI0MDQwNzM1WhcNMjUxMDEzMDQwNzM1WjArMQswCQYDVQQGEwJVUzEc
MBoGA1UEAwwTVmlydHVCcmljay1Mb2NhbC1DQTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAJdBvaTyxCo7+nJV3pSMVKFXc4VOLhlg0BZrAytZR64lC2jg
0JOI1qwQKeBRFKVN+ZsmfEOyIg82Hbtu+7ZzbvX1wKdcU/m9FT8JgEZ0rT8koN4C
S2idiTbNSiNXKkicbSjw6CfZEEL+LZaziR+Qt1hh/PMC6U5haz0bUtm8cKV1tfv8
aGs/GXyoZ35cUJq6YNWlE0Bxyt7LvEH7C/H5c2TIrUOLp+zTcMlWTxelkeBp51x9
cgOP5fILPOgs7IZ2wi8yUjfGBSfVWt6VyDp7vaws7/HozWta/tiGiLRImbulJeAw
BhPesBz6VUnaDeovHiwIFuFJHugPqv4TKNdXoOMCAwEAAaNTMFEwHQYDVR0OBBYE
FLo8qb1zkIVtEwu1whODHUf4Lq5dMB8GA1UdIwQYMBaAFLo8qb1zkIVtEwu1whOD
HUf4Lq5dMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADqVAf8P
aZ2M8YmmJnCxyKNmH1eeWwvPQB8+3H15auPDPXblqN48lw+pbN1dciBFlvpRSCEU
Ykdodo2W+Jyy1h8/zCVT4Vjz6OuQvKQSs/7sbxnx7txZ+3UTf0JJELXhyYtn4Sx+
+z9IjULy2zy+8QgK7c/QliBCXkTgStg0NayNhqVNM9lKAphpYJM99cgdiW7Mun+R
hMFj/QORCMOKGNYsR3ktlYPzXb8qzGhgX94RAY8wYfRtqCXbOq+Mpi8BRNF2G/0n
n+Pay/7yfdhiTtORrSjewsjSTElS9QzXrk26PreWWJWTAYuPa6l09Vxd4r8bYcqa
Nk9CdC6FVWP6x/I=
-----END CERTIFICATE-----

28
etc/cert/RootCA.key Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXQb2k8sQqO/py
Vd6UjFShV3OFTi4ZYNAWawMrWUeuJQto4NCTiNasECngURSlTfmbJnxDsiIPNh27
bvu2c2719cCnXFP5vRU/CYBGdK0/JKDeAktonYk2zUojVypInG0o8Ogn2RBC/i2W
s4kfkLdYYfzzAulOYWs9G1LZvHCldbX7/GhrPxl8qGd+XFCaumDVpRNAccrey7xB
+wvx+XNkyK1Di6fs03DJVk8XpZHgaedcfXIDj+XyCzzoLOyGdsIvMlI3xgUn1Vre
lcg6e72sLO/x6M1rWv7Yhoi0SJm7pSXgMAYT3rAc+lVJ2g3qLx4sCBbhSR7oD6r+
EyjXV6DjAgMBAAECggEAJTi80fwZ0Ojcm0lNUlHsO535JBtWldEws+uWppK8ZHri
QWddfIjmhwYdQAHD6l/zZj5EwyXxYrvm+ip4D/B9JFLbG3RJiAIDWfVdFzl8Lrc1
TRzUcMSfsRg4v9Sh56dy52nNP40Xhtzk3BqHSnjC3aGTbTvQrQSlLq7sJv/L2rNp
YmE3yyQ3evXEw2GHXbhd2kE1e6vI/C1U5h91NSYk2D8DvK54gB3xcOzHG6xfUger
i4gUjAFexEQoBMHcmSgYiaGvkbp7O9IjHyKQOsIOYw5i9fQPqs4IlzAi8HBB88BJ
WUEIwil06mz+IKKK/2weZkddB3ZmIwbHm8jkpzAUiQKBgQC7LD8U1xqKhmOU+TNW
UTDcTlLhz4bX8Jl09fqwGlbK19gJFFTAMuVWWx2au4ATJic+UGPLA6hs7q7kKsrU
tP17afZTNuFBFBzQPfDgzRZOfJWmRSE7PFbVA7nRNtIW8e549cIg9PhuDQjEdyr7
/1Zk6LqnKIYrMYo8aVe+Gu10mwKBgQDO4H4PUygQhtU2UITVcN43suqrlvJJuC6H
+WXfXlcf2YITHwtEGcyNvOETBbs85M0x+AzLJnJGNzqIE3F8FT4a2EduBi/G26he
CNW9st66u+yfnduuTDpvxDNICn71E7pbRQuI3FqyarW0s/lO63jDYkaEw8t1aJ2I
ouYwaFI1WQKBgFgFgtTmI5EpigMw/jeFjxjLrKaM1bkPaNtcIjxW5qIVx37dlM2e
IcPurYlqy9w1gRcI7yU13yr7RDwA88n8I4i6WCDiGiRktPgpaPIPAIKAjE/ZCru0
sJEUY3pD8aDgvWkdCeI90ebPMmRSNmCzv4lM+RpLMG13qq6mS6EXQLwpAoGAIPCH
weLotzDw0/QgImgVSeMYEHg5XdmQtx/Tw6wezThxyJ+hyJcfBdZ0M/YuqPR5Y6Ju
Tw5xFZMHo9EMucgcGmQjg3ZCtzQUa9yWQ90uL6dqdsPIzcjfaRphW/An5NfeTVgC
DSmg21W4VuxtHs7JbLrCJbOTOr+Mjcv6QOzrYgkCgYEAt6T0WimGNXumAdGyIi8j
zWC/Oa7tSYwLrgpnXYdtBKa4oxJDQr3L2zkTZ0K76J5dQuTM3q/IKbGmP9p8K9MB
Nx5U5BlFFpeUC1pPt7noJwmG4KcCt9GekPIhV0+44hLOvIEFznugc5/Y5XvqDFGq
ZSEy8mDXVww7JCsG4avq0Fk=
-----END PRIVATE KEY-----

20
etc/cert/RootCA.pem Normal file
View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDNzCCAh+gAwIBAgIUeRbZ6wwayGbn2Ava8XTPh6reXu0wDQYJKoZIhvcNAQEL
BQAwKzELMAkGA1UEBhMCVVMxHDAaBgNVBAMME1ZpcnR1QnJpY2stTG9jYWwtQ0Ew
HhcNMjIxMjI0MDQwNzM1WhcNMjUxMDEzMDQwNzM1WjArMQswCQYDVQQGEwJVUzEc
MBoGA1UEAwwTVmlydHVCcmljay1Mb2NhbC1DQTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAJdBvaTyxCo7+nJV3pSMVKFXc4VOLhlg0BZrAytZR64lC2jg
0JOI1qwQKeBRFKVN+ZsmfEOyIg82Hbtu+7ZzbvX1wKdcU/m9FT8JgEZ0rT8koN4C
S2idiTbNSiNXKkicbSjw6CfZEEL+LZaziR+Qt1hh/PMC6U5haz0bUtm8cKV1tfv8
aGs/GXyoZ35cUJq6YNWlE0Bxyt7LvEH7C/H5c2TIrUOLp+zTcMlWTxelkeBp51x9
cgOP5fILPOgs7IZ2wi8yUjfGBSfVWt6VyDp7vaws7/HozWta/tiGiLRImbulJeAw
BhPesBz6VUnaDeovHiwIFuFJHugPqv4TKNdXoOMCAwEAAaNTMFEwHQYDVR0OBBYE
FLo8qb1zkIVtEwu1whODHUf4Lq5dMB8GA1UdIwQYMBaAFLo8qb1zkIVtEwu1whOD
HUf4Lq5dMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADqVAf8P
aZ2M8YmmJnCxyKNmH1eeWwvPQB8+3H15auPDPXblqN48lw+pbN1dciBFlvpRSCEU
Ykdodo2W+Jyy1h8/zCVT4Vjz6OuQvKQSs/7sbxnx7txZ+3UTf0JJELXhyYtn4Sx+
+z9IjULy2zy+8QgK7c/QliBCXkTgStg0NayNhqVNM9lKAphpYJM99cgdiW7Mun+R
hMFj/QORCMOKGNYsR3ktlYPzXb8qzGhgX94RAY8wYfRtqCXbOq+Mpi8BRNF2G/0n
n+Pay/7yfdhiTtORrSjewsjSTElS9QzXrk26PreWWJWTAYuPa6l09Vxd4r8bYcqa
Nk9CdC6FVWP6x/I=
-----END CERTIFICATE-----

9
etc/cert/domains.ext Normal file
View File

@ -0,0 +1,9 @@
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = virtubrick.local
DNS.3 = *.virtubrick.local
DNS.4 = *.*.virtubrick.local

4
etc/cert/gen.bat Normal file
View File

@ -0,0 +1,4 @@
openssl req -x509 -nodes -new -sha256 -days 1024 -newkey rsa:2048 -keyout RootCA.key -out RootCA.pem -subj "/C=US/CN=VirtuBrick-Local-CA"
openssl x509 -outform pem -in RootCA.pem -out RootCA.crt
openssl req -new -nodes -newkey rsa:2048 -keyout vbrick.key -out vbrick.csr -subj "/C=US/ST=California/L=San Mateo/O=VirtuBrick Local Test/CN=virtubrick.local"
openssl x509 -req -sha256 -days 1024 -in vbrick.csr -CA RootCA.pem -CAkey RootCA.key -CAcreateserial -extfile domains.ext -out vbrick.crt

23
etc/cert/vbrick.crt Normal file
View File

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID2DCCAsCgAwIBAgIUSs0NnP/ZRmvsjJWv0ULFldiMwfowDQYJKoZIhvcNAQEL
BQAwKzELMAkGA1UEBhMCVVMxHDAaBgNVBAMME1ZpcnR1QnJpY2stTG9jYWwtQ0Ew
HhcNMjIxMjI0MDQwNzM4WhcNMjUxMDEzMDQwNzM4WjBxMQswCQYDVQQGEwJVUzET
MBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJU2FuIE1hdGVvMR4wHAYDVQQK
DBVWaXJ0dUJyaWNrIExvY2FsIFRlc3QxGTAXBgNVBAMMEHZpcnR1YnJpY2subG9j
YWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCjIun15WSkYDojtmUM
TOhSvCNZiMFADx7SFtZ9BmT94aURmJkQ/OU9vEBiaPBigNaByNEjp9FyKK+oCbgC
zdZjCtHnYeOTKpK8huHYFM0PE5MmMmmEf0M6ERrSpJ0IjAjoOhBC6iz3ouDeaRaa
Ha9GuB54RF1Jo4e0nX5A8OcUuzZA1DTdlZ1wrRFymgsjYPqE9b1If9Ni9ABehlNA
+6BUtwuef1jEyKTyXSd9hK/ngo2t/wKurxsjOOZiaNDZjdY0btGVSMQS0pqxVpiz
DI7RqqkdbRhHe/3hGKvCPXs0p6RjXINKeY6lhI+Tv3qogpnHqjfrnN/mfJMt7NW2
D9VJAgMBAAGjga0wgaowHwYDVR0jBBgwFoAUujypvXOQhW0TC7XCE4MdR/gurl0w
CQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwUAYDVR0RBEkwR4IJbG9jYWxob3N0ghB2
aXJ0dWJyaWNrLmxvY2FsghIqLnZpcnR1YnJpY2subG9jYWyCFCouKi52aXJ0dWJy
aWNrLmxvY2FsMB0GA1UdDgQWBBQIQHg8iyx/ED9q8mtu9VKSPxBwYzANBgkqhkiG
9w0BAQsFAAOCAQEAKmwhDHvhytS81dDOWjzYXaQXzxOn3/6NSuL0FogY2sM8Nt3L
cT9JQQqqxevG2vbnUrODC2eGMSApnoBI1mJuP2SkyZGCVbi1QLca5WII/We/HPAs
OaC6CLKs1ywpClBV0GoFO7aNWEtSbQ0haNiGoEQsk8kvPFA3kLSCUF3SN0csFOc4
vcU8GwqrbO53hpclP3SZrwjzh1VlW985EMaWGd14/e/ASYu/+FSnIjbkZmha8CXu
8dI8BPAZ6D0nVj4kBICEy3bonzdJP/MhentCrE6WH2Eg5SqA2a14sSVx5K025OmS
QLjrqRLxlwMGYC+pQ+RHPmAl/ZVrJc7vli4CsA==
-----END CERTIFICATE-----

17
etc/cert/vbrick.csr Normal file
View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICtjCCAZ4CAQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx
EjAQBgNVBAcMCVNhbiBNYXRlbzEeMBwGA1UECgwVVmlydHVCcmljayBMb2NhbCBU
ZXN0MRkwFwYDVQQDDBB2aXJ0dWJyaWNrLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAoyLp9eVkpGA6I7ZlDEzoUrwjWYjBQA8e0hbWfQZk/eGl
EZiZEPzlPbxAYmjwYoDWgcjRI6fRciivqAm4As3WYwrR52HjkyqSvIbh2BTNDxOT
JjJphH9DOhEa0qSdCIwI6DoQQuos96Lg3mkWmh2vRrgeeERdSaOHtJ1+QPDnFLs2
QNQ03ZWdcK0RcpoLI2D6hPW9SH/TYvQAXoZTQPugVLcLnn9YxMik8l0nfYSv54KN
rf8Crq8bIzjmYmjQ2Y3WNG7RlUjEEtKasVaYswyO0aqpHW0YR3v94Rirwj17NKek
Y1yDSnmOpYSPk796qIKZx6o365zf5nyTLezVtg/VSQIDAQABoAAwDQYJKoZIhvcN
AQELBQADggEBAH9T2qdl5mWAEkNh0mHc7io7EqKCAwGSkWudj+QqWse9cn3AELLv
VbXUcE/GkcCTYiLyY8mJxwYaGiRa/tLcl4IHgXtG/a4oAoMdB5moQorT9YiWlXrs
3bzl/vZJaSmp12k2gN0Mu8H4ED5Eg4trKIcORzRsQV6zzYhF/4oUllr3cFg30CCl
nm7Mwwk6k4yB/zNuoPm6laukeMi99TLQ8LK57odi4Wqz4mZvd5lryhGEm6dZ6Cz+
Cahdba8vzvN3RIAWMhDubMxNfaAQvV1Z0yEZ6fcmEDWzof1tD1VtA82al5YJvLy3
RZcycbaB90F0XUhb20iElnxzig2XDNcbWR0=
-----END CERTIFICATE REQUEST-----

28
etc/cert/vbrick.key Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCjIun15WSkYDoj
tmUMTOhSvCNZiMFADx7SFtZ9BmT94aURmJkQ/OU9vEBiaPBigNaByNEjp9FyKK+o
CbgCzdZjCtHnYeOTKpK8huHYFM0PE5MmMmmEf0M6ERrSpJ0IjAjoOhBC6iz3ouDe
aRaaHa9GuB54RF1Jo4e0nX5A8OcUuzZA1DTdlZ1wrRFymgsjYPqE9b1If9Ni9ABe
hlNA+6BUtwuef1jEyKTyXSd9hK/ngo2t/wKurxsjOOZiaNDZjdY0btGVSMQS0pqx
VpizDI7RqqkdbRhHe/3hGKvCPXs0p6RjXINKeY6lhI+Tv3qogpnHqjfrnN/mfJMt
7NW2D9VJAgMBAAECggEABDCrSFAUiWF/v/2sbPT0Vw+snsOaJxf17cwW+HSEfb4U
D2m9rYe5Wzngs3mVI9dbhLWqPYHoqZR6Jbt3xcaqSRzBiHGYtuWpVpZ/6lA0D70s
ErD8lqHukZxX3CCZq4460/Anc8VWoCOt8x7i4DsYwem/vc1C673IbtV7i5Wq2C4h
e9rTlYE87D7mHfCIFwlV4JOqkqPYbR6sr8cf5rdAdy5L73bU/0ff4+TwY9UxnufM
crdvhf2nWJIQHh7zCfQapFTwUIlrsyAu6t30lRkdxNje2JVxpPDaEqHnDh/Nuni5
m4t4OUXZTvLhWblIIw5iwX/M9le9MmobHWSu5odd2wKBgQDcRh/DRv57adXInweX
AVL599H1LywjQAI/2eOV9BghBHb1Ln2LTidUS59pap+RZ2NL2OnKHhDX8u1UJi2G
DGibOHQep9pE56Gq/u1WJMpKnetIGr/hMSn7Uglg01BzmopiJ5OyN3cO7KdXH71J
oySKjISI7m0D+yrp93/O74YDgwKBgQC9mGmdBKYfv3sNYwoAQFCwKKcFYxlmaY5V
u7P1ViR0V+fkh7TW844FLXtI8cI9EbE3r6huM0YMlmL6fXb1zVoQgJUtT87pTMIf
ZcAq3kzHwkIA3vwFGZ8B1MsOKDybN379a8Ev3FM+Afyr1lrv/B4L14ZWcK2QbsNi
NJ3M2HlOQwKBgDdEFNYO6uyV+kByvhnCCEqVRgVpR360oZvZlENcUe1+zEXp2y/n
dPCuZJmzWcPGl+BA1S+T6Y/08FzLk2JtnJVNTb0fSiE9qI9ZlLynUGX2R0D5DSl4
B1t6EsZLXoUSwiOsYF83kVSaRcYTPY/LMfDdHqeogoucKgE3ysZlzE73AoGAT3dr
hPmm25W5cOH7FiHe8AcKAnMH+Wny07Jp+kHR48XYl2vd+154P2lRzEAwpsIconeI
xC/Pg4UhFVazHtGkl1gdrrFNy3F1yA0w34bvbabZXV4ZCdY0VGMpfNSWmoRHQNcC
URoq9cmFa0zcLsxgl5wNXXu689fJIwdkwLBXH9sCgYB52keZwt6rRu6n1q03tFWV
hZzxpxHX1hlw3jtLvWERpF8gLp5CRwX9xoMTciS82f3W/AUBx+eIiQ1HOUJ5W1S1
3GJBqCkWD8oipPxVW9XvKCdRHiY+2Jc+dKN2KTHjZSZLWDAZYkHL3/LC+gmQhM5j
bIyBpf2Yn45e1NIxAngLCw==
-----END PRIVATE KEY-----

Binary file not shown.

90
etc/graphictoria.conf Normal file
View File

@ -0,0 +1,90 @@
# Allow read in the public directories.
<Directory "C:/graphictoria/COMMIT_HASH/web/public/">
Require all granted
AllowOverride All
</Directory>
<Directory "C:/graphictoria/COMMIT_HASH/web/public_api/">
Require all granted
AllowOverride All
</Directory>
# Disallow access to admin JS
<Directory "C:/graphictoria/COMMIT_HASH/web/public/js/adm/">
Require all denied
AllowOverride All
</Directory>
# Defaults for the proceding virtualhosts on *.gtoria.net
<VirtualHost *:80 *:443>
ServerAdmin admin@gtoria.net
ServerName gtoria.net
ServerAlias www.gtoria.net
SSLEngine on
SSLCertificateFile "C:/graphictoria/COMMIT_HASH/etc/cert/cloudflare.crt"
SSLCertificateKeyFile "C:/graphictoria/COMMIT_HASH/etc/cert/cloudflare.key"
ErrorLog "C:/graphictoria/logs/gt-error.log"
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Host}i\" \"%{Referer}i\" \"%{User-agent}i\"" gtoria
CustomLog "C:/graphictoria/logs/gt-access.log" gtoria
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}
DocumentRoot "C:/graphictoria/COMMIT_HASH/web/public"
</VirtualHost>
# Blog/Wiki
<VirtualHost *:80 *:443>
ServerName gtoria.net
ServerAlias blog.gtoria.net
ServerAlias wiki.gtoria.net
SSLEngine on
SSLCertificateFile "C:/graphictoria/COMMIT_HASH/etc/cert/cloudflare.crt"
SSLCertificateKeyFile "C:/graphictoria/COMMIT_HASH/etc/cert/cloudflare.key"
ErrorLog "C:/xampp/logs/gt-error.log"
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Host}i\" \"%{Referer}i\" \"%{User-agent}i\"" gtoria
CustomLog "C:/xampp/logs/gt-access.log" gtoria
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}
DocumentRoot "C:/graphictoria/COMMIT_HASH/web/public"
</VirtualHost>
# Api endpoints.
<VirtualHost *:80 *:443>
ServerName gtoria.net
ServerAlias api.gtoria.net
ServerAlias apis.gtoria.net
ServerAlias assetgame.gtoria.net
ServerAlias cdn.gtoria.net
ServerAlias clientsettings.api.gtoria.net
ServerAlias data.gtoria.net
ServerAlias ecsv2.gtoria.net
ServerAlias ephemeralcounters.api.gtoria.net
ServerAlias gamepersistence.gtoria.net
ServerAlias logging.service.gtoria.net
ServerAlias setup.gtoria.net
ServerAlias test.public.ecs.gtoria.net
ServerAlias versioncompatibility.api.gtoria.net
SSLEngine on
SSLCertificateFile "C:/graphictoria/COMMIT_HASH/etc/cert/cloudflare.crt"
SSLCertificateKeyFile "C:/graphictoria/COMMIT_HASH/etc/cert/cloudflare.key"
ErrorLog "C:/graphictoria/logs/gt-error.log"
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Host}i\" \"%{Referer}i\" \"%{User-agent}i\"" gtoria
CustomLog "C:/graphictoria/logs/gt-access.log" gtoria
DocumentRoot "C:/graphictoria/COMMIT_HASH/web/public_api"
</VirtualHost>

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

View File

@ -1,4 +0,0 @@
@echo off
cd web
php artisan serve --port=42070

19
web/.dockerignore Normal file
View File

@ -0,0 +1,19 @@
/node_modules
/public/mix-manifest.json
/public/js
/public/css
/public/fonts
/public/hot
/public/storage
/storage
/bootstrap/cache
/vendor
.env
.env.backup
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
/.idea
/.vscode

View File

@ -1,32 +1,39 @@
TRUSTED_HOSTS=localhost,gtoria.local
APP_NAME=Graphictoria
APP_NAME=VirtuBrick
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://gtoria.local
APP_URL=http://virtubrick.net
MIX_APP_URL=http://virtubrick.net
IS_TEST_ENVIRONMENT=true
ROBLOX_COOKIE=
GAMESERVER_IP=127.0.0.1
THUMBNAILER_IP=127.0.0.1
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql-primary
RECAPTCHA_SITE_KEY=ClientKeyHere
RECAPTCHA_SECRET_KEY=ServerKeyHere
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=service-laravel
DB_DATABASE=virtubrick
DB_USERNAME=root
DB_PASSWORD=
DB_PRIMARY_DATABASE=gtoriadev_primary
DB_MEMBERSHIP_DATABASE=gtoriadev_membership
DB_FFLAG_DATABASE=gtoriadev_fflag
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DRIVER=local
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=memcached
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
@ -38,7 +45,7 @@ MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
@ -53,4 +60,4 @@ PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

11
web/.gitattributes vendored
View File

@ -1,5 +1,10 @@
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
*.js linguist-vendored
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore

7
web/.gitignore vendored
View File

@ -1,4 +1,8 @@
/node_modules
/public/mix-manifest.json
/public/js
/public/css
/public/fonts
/public/hot
/public/storage
/storage/*.key
@ -6,12 +10,9 @@
.env
.env.backup
.phpunit.result.cache
docker-compose.override.yml
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
/.idea
/.vscode
/public/css
/public/js

View File

@ -1,12 +1,10 @@
php:
preset: laravel
version: 8
disabled:
- no_unused_imports
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:

37
web/Dockerfile Normal file
View File

@ -0,0 +1,37 @@
FROM node:18-alpine AS node
FROM composer:2.3.5
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
COPY --from=node /usr/local/bin/node /usr/local/bin/node
RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm
WORKDIR /vbrick
COPY . .
RUN mkdir ./bootstrap/cache \
&& mkdir ./storage \
&& mkdir ./storage/logs \
&& mkdir ./storage/framework \
&& mkdir ./storage/framework/cache \
&& mkdir ./storage/framework/sessions \
&& mkdir ./storage/framework/testing \
&& mkdir ./storage/framework/views \
&& mkdir ./storage/app \
&& mkdir ./storage/app/public
RUN composer install \
--no-interaction \
--no-plugins \
--no-scripts \
--no-dev \
--prefer-dist \
&& composer dump-autoload
RUN npm install && npm run prod
RUN php artisan optimize:clear
CMD php artisan serve --host=0.0.0.0 --port=1024
EXPOSE 1024

View File

@ -1,2 +0,0 @@
## Graphictoria web
This is the laravel Graphictoria website.

View File

@ -5,17 +5,10 @@ namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use App\Jobs\UpdateUsageCounters;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
//
];
/**
* Define the application's command schedule.
*
@ -24,7 +17,7 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')->hourly();
$schedule->job(new UpdateUsageCounters)->everyMinute()->evenInMaintenanceMode();
}
/**

View File

@ -2,16 +2,24 @@
namespace App\Exceptions;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [
//
];
/**
* A list of the exception types that are not reported.
*
* @var array
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
//
@ -20,7 +28,7 @@ class Handler extends ExceptionHandler
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
@ -29,14 +37,14 @@ class Handler extends ExceptionHandler
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->renderable(function (NotFoundHttpException $e, $request) {
return response()->view('main', [], 404);
});
}
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
}
}

View File

@ -1,18 +1,18 @@
<?php
/*
Graphictoria 2022
XlXi 2022
This file handles communications between the arbiter and the website.
*/
// TODO: Comment the return values and descriptions of the soap service functions.
// TODO: Make new classes in App\Grid to automate the generation of thumbnails and game servers.
namespace App\Grid;
use Illuminate\Support\Facades\Storage;
use SoapClient;
use App\Models\ArbiterSoapFault;
use App\Helpers\GridHelper;
class SoapService
{
/**
@ -30,11 +30,12 @@ class SoapService
* @param string $arbiterAddr
* @return null
*/
public function __construct($arbiterAddr) {
public function __construct($arbiterType) {
$arbiter = GridHelper::{strtolower($arbiterType) . 'Arbiter'}();
$this->Client = new SoapClient(
Storage::path('grid/RCCService.wsdl'),
Storage::path('grid/RCCService.wsdl'), // Arbiter WCF service WSDL should not be used for RCCService calls.
[
'location' => $arbiterAddr,
'location' => $arbiter,
'uri' => 'http://roblox.com/',
'exceptions' => false
]
@ -52,72 +53,17 @@ class SoapService
$soapResult = $this->Client->{$name}($args);
if(is_soap_fault($soapResult)) {
// TODO: log faults
ArbiterSoapFault::create([
'function' => $name,
'code' => $soapResult->getCode(),
'message' => $soapResult->getMessage(),
'job_arguments' => json_encode($args)
]);
}
return $soapResult;
}
/* Job constructors */
public static function LuaValue($value)
{
switch ($value) {
case is_bool(json_encode($value)) || $value == 1:
return json_encode($value);
default:
return $value;
}
}
public static function CastType($value)
{
$luaTypeConversions = [
'NULL' => 'LUA_TNIL',
'boolean' => 'LUA_TBOOLEAN',
'integer' => 'LUA_TNUMBER',
'double' => 'LUA_TNUMBER',
'string' => 'LUA_TSTRING',
'array' => 'LUA_TTABLE',
'object' => 'LUA_TNIL'
];
return $luaTypeConversions[gettype($value)];
}
public static function ToLuaArguments($luaArguments = [])
{
$luaValue = ['LuaValue' => []];
foreach ($luaArguments as $argument) {
array_push(
$luaValue['LuaValue'],
[
'type' => SoapService::CastType($argument),
'value' => SoapService::LuaValue($argument)
]
);
}
return $luaValue;
}
public static function MakeJobJSON($jobID, $expiration, $category, $cores, $scriptName, $script, $scriptArgs = [])
{
return [
'job' => [
'id' => $jobID,
'expirationInSeconds' => $expiration,
'category' => $category,
'cores' => $cores
],
'script' => [
'name' => $scriptName,
'script' => $script,
'arguments' => SoapService::ToLuaArguments($scriptArgs)
]
];
}
/* Service functions */
public function HelloWorld()

View File

@ -0,0 +1,140 @@
<?php
/*
XlXi 2022
Asset Helper
*/
namespace App\Helpers;
use GuzzleHttp\Cookie\CookieJar;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use App\Helpers\CdnHelper;
use App\Models\Asset;
use App\Models\AssetVersion;
use App\Models\RobloxAsset;
use App\Models\UserAsset;
class AssetHelper
{
private static function cookiedRequest()
{
$cookieJar = CookieJar::fromArray([
'.ROBLOXSECURITY' => env('app.robloxcookie')
], '.roblox.com');
return Http::withOptions(['cookies' => $cookieJar, 'headers' => ['User-Agent' => 'Roblox/WinInet', 'Requester' => 'Server']]);
}
public static function newAsset($props, $hash)
{
$asset = Asset::create($props);
$assetVersion = AssetVersion::create([
'parentAsset' => $asset->id,
'localVersion' => 1,
'contentURL' => $hash
]);
$asset->assetVersionId = $assetVersion->id;
$asset->save();
UserAsset::createSerialed($asset->creatorId, $asset->id);
return $asset;
}
private static function getRBXMarketplaceInfo($assetId)
{
$marketplaceResult = self::cookiedRequest()->get('https://api.roblox.com/marketplace/productinfo?assetId=' . $assetId);
if(!$marketplaceResult->ok())
return false;
$marketplaceResult = $marketplaceResult->json();
$assetTypeId = $marketplaceResult['AssetTypeId'];
if(
$assetTypeId == 41 || // Hair Accessory
$assetTypeId == 42 || // Face Accessory
$assetTypeId == 43 || // Neck Accessory
$assetTypeId == 44 || // Shoulder Accessory
$assetTypeId == 45 || // Front Accessory
$assetTypeId == 46 || // Back Accessory
$assetTypeId == 47 // Waist Accessory
) {
$assetTypeId = 8;
}
$marketplaceResult['AssetTypeId'] = $assetTypeId;
return $marketplaceResult;
}
public static function uploadRobloxAsset($id, $uploadToHolder = false)
{
{
$uploadedAsset = RobloxAsset::where('robloxAssetId', $id)->first();
if($uploadedAsset)
return $uploadedAsset->asset;
}
$marketplaceResult = self::getRBXMarketplaceInfo($id);
$assetResult = self::cookiedRequest()->get('https://assetdelivery.roblox.com/v2/asset?id=' . $id);
if(!$marketplaceResult || !$assetResult->ok())
return false;
$assetContent = Http::get($assetResult['locations'][0]['location']);
$hash = CdnHelper::SaveContent($assetContent->body(), $assetContent->header('Content-Type'));
$asset = self::newAsset([
'creatorId' => ($uploadToHolder ? 1 : Auth::user()->id),
'name' => $marketplaceResult['Name'],
'description' => $marketplaceResult['Description'],
'approved' => true,
'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0,
'onSale' => $marketplaceResult['IsForSale'],
'assetTypeId' => $marketplaceResult['AssetTypeId'],
'assetVersionId' => 0
], $hash);
RobloxAsset::create([
'robloxAssetId' => $id,
'localAssetId' => $asset->id
]);
return $asset;
}
public static function uploadCustomRobloxAsset($id, $uploadToHolder = false, $b64Content)
{
{
$uploadedAsset = RobloxAsset::where('robloxAssetId', $id)->first();
if($uploadedAsset)
return $uploadedAsset->asset;
}
$marketplaceResult = self::getRBXMarketplaceInfo($id);
if(!$marketplaceResult)
return false;
$hash = CdnHelper::SaveContentB64($b64Content, 'application/octet-stream');
$asset = self::newAsset([
'creatorId' => ($uploadToHolder ? 1 : Auth::user()->id),
'name' => $marketplaceResult['Name'],
'description' => $marketplaceResult['Description'],
'approved' => true,
'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0,
'onSale' => $marketplaceResult['IsForSale'],
'assetTypeId' => $marketplaceResult['AssetTypeId'],
'assetVersionId' => 0
], $hash);
RobloxAsset::create([
'robloxAssetId' => $id,
'localAssetId' => $asset->id
]);
return $asset;
}
}

View File

@ -1,81 +0,0 @@
<?php
/*
Graphictoria 2022
Authentication helper
Written because FUCK WHOEVER IS BEHIND THE Illuminate\Foundation\Auth\User DEV TEAM AHHHHHH
my frustration is immeasureable ~ xlxi
*/
namespace App\Helpers;
use Illuminate\Http\Request;
use Carbon\Carbon;
use App\Models\User;
use App\Models\User\UserSession;
use App\Helpers\Random;
class AuthHelper
{
/**
* "Guards" a page in the sense where it kills the request if the user is logged in.
*
* @return boolean
*/
public static function Guard(Request $request) {
if(AuthHelper::GetCurrentUser($request))
return true;
}
/**
* Returns the current user.
*
* @return User?
*/
public static function GetCurrentUser(Request $request) {
if($request->session()->exists('authentication')) {
$session = UserSession::where('token', $request->session()->get('authentication'))->first();
if($session)
return User::where('id', $session->user)->first();
return;
}
return;
}
/**
* Remove a session.
*
* @return User?
*/
public static function RemoveSession(Request $request) {
if($request->session()->exists('authentication')) {
$session = UserSession::where('token', $request->session()->get('authentication'))->first();
$session->delete();
return;
}
return;
}
/**
* Grants a session.
*
* @return UserSession
*/
public static function GrantSession($request, $id) {
$session = new UserSession();
$session->user = $id;
// formerly cookies
$session->token = 'DO_NOT_SHARE_OR_YOUR_ITEMS_WILL_BE_STOLEN_|' . Random::Str(64);
$session->ip = $request->ip();
$session->last_seen = Carbon::now();
$session->save();
return $session;
}
}

View File

@ -0,0 +1,851 @@
<?php
/*
XlXi 2023
Brick color helper.
*/
namespace App\Helpers;
class BrickColorHelper
{
private static $brickColors = [
1 => [
'Color' => [242, 243, 243],
'Name' => 'White'
],
2 => [
'Color' => [161, 165, 162],
'Name' => 'Grey'
],
3 => [
'Color' => [249, 233, 153],
'Name' => 'Light yellow'
],
5 => [
'Color' => [215, 197, 154],
'Name' => 'Brick yellow'
],
6 => [
'Color' => [194, 218, 184],
'Name' => 'Light green (Mint)'
],
9 => [
'Color' => [232, 186, 200],
'Name' => 'Light reddish violet'
],
11 => [
'Color' => [0x80, 0xbb, 0xdb],
'Name' => 'Pastel Blue'
],
12 => [
'Color' => [203, 132, 66],
'Name' => 'Light orange brown'
],
18 => [
'Color' => [204, 142, 105],
'Name' => 'Nougat'
],
21 => [
'Color' => [196, 40, 28],
'Name' => 'Bright red'
],
22 => [
'Color' => [196, 112, 160],
'Name' => 'Med. reddish violet'
],
23 => [
'Color' => [13, 105, 172],
'Name' => 'Bright blue'
],
24 => [
'Color' => [245, 205, 48],
'Name' => 'Bright yellow'
],
25 => [
'Color' => [98, 71, 50],
'Name' => 'Earth orange'
],
26 => [
'Color' => [27, 42, 53],
'Name' => 'Black'
],
27 => [
'Color' => [109, 110, 108],
'Name' => 'Dark grey'
],
28 => [
'Color' => [40, 127, 71],
'Name' => 'Dark green'
],
29 => [
'Color' => [161, 196, 140],
'Name' => 'Medium green'
],
36 => [
'Color' => [243, 207, 155],
'Name' => 'Lig. Yellowich orange'
],
37 => [
'Color' => [75, 151, 75],
'Name' => 'Bright green'
],
38 => [
'Color' => [160, 95, 53],
'Name' => 'Dark orange'
],
39 => [
'Color' => [193, 202, 222],
'Name' => 'Light bluish violet'
],
40 => [
'Color' => [236, 236, 236],
'Name' => 'Transparent'
],
41 => [
'Color' => [205, 84, 75],
'Name' => 'Tr. Red'
],
42 => [
'Color' => [193, 223, 240],
'Name' => 'Tr. Lg blue'
],
43 => [
'Color' => [123, 182, 232],
'Name' => 'Tr. Blue'
],
44 => [
'Color' => [247, 241, 141],
'Name' => 'Tr. Yellow'
],
45 => [
'Color' => [180, 210, 228],
'Name' => 'Light blue'
],
47 => [
'Color' => [217, 133, 108],
'Name' => 'Tr. Flu. Reddish orange'
],
48 => [
'Color' => [132, 182, 141],
'Name' => 'Tr. Green'
],
49 => [
'Color' => [248, 241, 132],
'Name' => 'Tr. Flu. Green'
],
50 => [
'Color' => [236, 232, 222],
'Name' => 'Phosph. White'
],
100 => [
'Color' => [238, 196, 182],
'Name' => 'Light red'
],
101 => [
'Color' => [218, 134, 122],
'Name' => 'Medium red'
],
102 => [
'Color' => [110, 153, 202],
'Name' => 'Medium blue'
],
103 => [
'Color' => [199, 193, 183],
'Name' => 'Light grey'
],
104 => [
'Color' => [107, 50, 124],
'Name' => 'Bright violet'
],
105 => [
'Color' => [226, 155, 64],
'Name' => 'Br. yellowish orange'
],
106 => [
'Color' => [218, 133, 65],
'Name' => 'Bright orange'
],
107 => [
'Color' => [0, 143, 156],
'Name' => 'Bright bluish green'
],
108 => [
'Color' => [104, 92, 67],
'Name' => 'Earth yellow'
],
110 => [
'Color' => [67, 84, 147],
'Name' => 'Bright bluish violet'
],
111 => [
'Color' => [191, 183, 177],
'Name' => 'Tr. Brown'
],
112 => [
'Color' => [104, 116, 172],
'Name' => 'Medium bluish violet'
],
113 => [
'Color' => [228, 173, 200],
'Name' => 'Tr. Medi. reddish violet'
],
115 => [
'Color' => [199, 210, 60],
'Name' => 'Med. yellowish green'
],
116 => [
'Color' => [85, 165, 175],
'Name' => 'Med. bluish green'
],
118 => [
'Color' => [183, 215, 213],
'Name' => 'Light bluish green'
],
119 => [
'Color' => [164, 189, 71],
'Name' => 'Br. yellowish green'
],
120 => [
'Color' => [217, 228, 167],
'Name' => 'Lig. yellowish green'
],
121 => [
'Color' => [231, 172, 88],
'Name' => 'Med. yellowish orange'
],
123 => [
'Color' => [211, 111, 76],
'Name' => 'Br. reddish orange'
],
124 => [
'Color' => [146, 57, 120],
'Name' => 'Bright reddish violet'
],
125 => [
'Color' => [234, 184, 146],
'Name' => 'Light orange'
],
126 => [
'Color' => [165, 165, 203],
'Name' => 'Tr. Bright bluish violet'
],
127 => [
'Color' => [220, 188, 129],
'Name' => 'Gold'
],
128 => [
'Color' => [174, 122, 89],
'Name' => 'Dark nougat'
],
131 => [
'Color' => [156, 163, 168],
'Name' => 'Silver'
],
133 => [
'Color' => [213, 115, 61],
'Name' => 'Neon orange'
],
134 => [
'Color' => [216, 221, 86],
'Name' => 'Neon green'
],
135 => [
'Color' => [116, 134, 157],
'Name' => 'Sand blue'
],
136 => [
'Color' => [135, 124, 144],
'Name' => 'Sand violet'
],
137 => [
'Color' => [224, 152, 100],
'Name' => 'Medium orange'
],
138 => [
'Color' => [149, 138, 115],
'Name' => 'Sand yellow'
],
140 => [
'Color' => [32, 58, 86],
'Name' => 'Earth blue'
],
141 => [
'Color' => [39, 70, 45],
'Name' => 'Earth green'
],
143 => [
'Color' => [207, 226, 247],
'Name' => 'Tr. Flu. Blue'
],
145 => [
'Color' => [121, 136, 161],
'Name' => 'Sand blue metallic'
],
146 => [
'Color' => [149, 142, 163],
'Name' => 'Sand violet metallic'
],
147 => [
'Color' => [147, 135, 103],
'Name' => 'Sand yellow metallic'
],
148 => [
'Color' => [87, 88, 87],
'Name' => 'Dark grey metallic'
],
149 => [
'Color' => [22, 29, 50],
'Name' => 'Black metallic'
],
150 => [
'Color' => [171, 173, 172],
'Name' => 'Light grey metallic'
],
151 => [
'Color' => [120, 144, 130],
'Name' => 'Sand green'
],
153 => [
'Color' => [149, 121, 119],
'Name' => 'Sand red'
],
154 => [
'Color' => [123, 46, 47],
'Name' => 'Dark red'
],
157 => [
'Color' => [255, 246, 123],
'Name' => 'Tr. Flu. Yellow'
],
158 => [
'Color' => [225, 164, 194],
'Name' => 'Tr. Flu. Red'
],
168 => [
'Color' => [117, 108, 98],
'Name' => 'Gun metallic'
],
176 => [
'Color' => [151, 105, 91],
'Name' => 'Red flip/flop'
],
178 => [
'Color' => [180, 132, 85],
'Name' => 'Yellow flip/flop'
],
179 => [
'Color' => [137, 135, 136],
'Name' => 'Silver flip/flop'
],
180 => [
'Color' => [215, 169, 75],
'Name' => 'Curry'
],
190 => [
'Color' => [249, 214, 46],
'Name' => 'Fire Yellow'
],
191 => [
'Color' => [232, 171, 45],
'Name' => 'Flame yellowish orange'
],
192 => [
'Color' => [105, 64, 40],
'Name' => 'Reddish brown'
],
193 => [
'Color' => [207, 96, 36],
'Name' => 'Flame reddish orange'
],
194 => [
'Color' => [163, 162, 165],
'Name' => 'Medium stone grey'
],
195 => [
'Color' => [70, 103, 164],
'Name' => 'Royal blue'
],
196 => [
'Color' => [35, 71, 139],
'Name' => 'Dark Royal blue'
],
198 => [
'Color' => [142, 66, 133],
'Name' => 'Bright reddish lilac'
],
199 => [
'Color' => [99, 95, 98],
'Name' => 'Dark stone grey'
],
200 => [
'Color' => [130, 138, 93],
'Name' => 'Lemon metalic'
],
208 => [
'Color' => [229, 228, 223],
'Name' => 'Light stone grey'
],
209 => [
'Color' => [176, 142, 68],
'Name' => 'Dark Curry'
],
210 => [
'Color' => [112, 149, 120],
'Name' => 'Faded green'
],
211 => [
'Color' => [121, 181, 181],
'Name' => 'Turquoise'
],
212 => [
'Color' => [159, 195, 233],
'Name' => 'Light Royal blue'
],
213 => [
'Color' => [108, 129, 183],
'Name' => 'Medium Royal blue'
],
216 => [
'Color' => [143, 76, 42],
'Name' => 'Rust'
],
217 => [
'Color' => [124, 92, 70],
'Name' => 'Brown'
],
218 => [
'Color' => [150, 112, 159],
'Name' => 'Reddish lilac'
],
219 => [
'Color' => [107, 98, 155],
'Name' => 'Lilac'
],
220 => [
'Color' => [167, 169, 206],
'Name' => 'Light lilac'
],
221 => [
'Color' => [205, 98, 152],
'Name' => 'Bright purple'
],
222 => [
'Color' => [228, 173, 200],
'Name' => 'Light purple'
],
223 => [
'Color' => [220, 144, 149],
'Name' => 'Light pink'
],
224 => [
'Color' => [240, 213, 160],
'Name' => 'Light brick yellow'
],
225 => [
'Color' => [235, 184, 127],
'Name' => 'Warm yellowish orange'
],
226 => [
'Color' => [253, 234, 141],
'Name' => 'Cool yellow'
],
232 => [
'Color' => [125, 187, 221],
'Name' => 'Dove blue'
],
268 => [
'Color' => [52, 43, 117],
'Name' => 'Medium lilac'
],
301 => [
'Color' => [80, 109, 84],
'Name' => 'Slime green'
],
302 => [
'Color' => [91, 93, 105],
'Name' => 'Smoky grey'
],
303 => [
'Color' => [0, 16, 176],
'Name' => 'Dark blue'
],
304 => [
'Color' => [44, 101, 29],
'Name' => 'Parsley green'
],
305 => [
'Color' => [82, 124, 174],
'Name' => 'Steel blue'
],
306 => [
'Color' => [51, 88, 130],
'Name' => 'Storm blue'
],
307 => [
'Color' => [16, 42, 220],
'Name' => 'Lapis'
],
308 => [
'Color' => [61, 21, 133],
'Name' => 'Dark indigo'
],
309 => [
'Color' => [52, 142, 64],
'Name' => 'Sea green'
],
310 => [
'Color' => [91, 154, 76],
'Name' => 'Shamrock'
],
311 => [
'Color' => [159, 161, 172],
'Name' => 'Fossil'
],
312 => [
'Color' => [89, 34, 89],
'Name' => 'Mulberry'
],
313 => [
'Color' => [31, 128, 29],
'Name' => 'Forest green'
],
314 => [
'Color' => [159, 173, 192],
'Name' => 'Cadet blue'
],
315 => [
'Color' => [9, 137, 207],
'Name' => 'Electric blue'
],
316 => [
'Color' => [123, 0, 123],
'Name' => 'Eggplant'
],
317 => [
'Color' => [124, 156, 107],
'Name' => 'Moss'
],
318 => [
'Color' => [138, 171, 133],
'Name' => 'Artichoke'
],
319 => [
'Color' => [185, 196, 177],
'Name' => 'Sage green'
],
320 => [
'Color' => [202, 203, 209],
'Name' => 'Ghost grey'
],
321 => [
'Color' => [167, 94, 155],
'Name' => 'Lilac'
],
322 => [
'Color' => [123, 47, 123],
'Name' => 'Plum'
],
323 => [
'Color' => [148, 190, 129],
'Name' => 'Olivine'
],
324 => [
'Color' => [168, 189, 153],
'Name' => 'Laurel green'
],
325 => [
'Color' => [223, 223, 222],
'Name' => 'Quill grey'
],
327 => [
'Color' => [151, 0, 0],
'Name' => 'Crimson'
],
328 => [
'Color' => [177, 229, 166],
'Name' => 'Mint'
],
329 => [
'Color' => [152, 194, 219],
'Name' => 'Baby blue'
],
330 => [
'Color' => [255, 152, 220],
'Name' => 'Carnation pink'
],
331 => [
'Color' => [255, 89, 89],
'Name' => 'Persimmon'
],
332 => [
'Color' => [117, 0, 0],
'Name' => 'Maroon'
],
333 => [
'Color' => [239, 184, 56],
'Name' => 'Gold'
],
334 => [
'Color' => [248, 217, 109],
'Name' => 'Daisy orange'
],
335 => [
'Color' => [231, 231, 236],
'Name' => 'Pearl'
],
336 => [
'Color' => [199, 212, 228],
'Name' => 'Fog'
],
337 => [
'Color' => [255, 148, 148],
'Name' => 'Salmon'
],
338 => [
'Color' => [190, 104, 98],
'Name' => 'Terra Cotta'
],
339 => [
'Color' => [86, 36, 36],
'Name' => 'Cocoa'
],
340 => [
'Color' => [241, 231, 199],
'Name' => 'Wheat'
],
341 => [
'Color' => [254, 243, 187],
'Name' => 'Buttermilk'
],
342 => [
'Color' => [224, 178, 208],
'Name' => 'Mauve'
],
343 => [
'Color' => [212, 144, 189],
'Name' => 'Sunrise'
],
344 => [
'Color' => [150, 85, 85],
'Name' => 'Tawny'
],
345 => [
'Color' => [143, 76, 42],
'Name' => 'Rust'
],
346 => [
'Color' => [211, 190, 150],
'Name' => 'Cashmere'
],
347 => [
'Color' => [226, 220, 188],
'Name' => 'Khaki'
],
348 => [
'Color' => [237, 234, 234],
'Name' => 'Lily white'
],
349 => [
'Color' => [233, 218, 218],
'Name' => 'Seashell'
],
350 => [
'Color' => [136, 62, 62],
'Name' => 'Burgundy'
],
351 => [
'Color' => [188, 155, 93],
'Name' => 'Cork'
],
352 => [
'Color' => [199, 172, 120],
'Name' => 'Burlap'
],
353 => [
'Color' => [202, 191, 163],
'Name' => 'Beige'
],
354 => [
'Color' => [187, 179, 178],
'Name' => 'Oyster'
],
355 => [
'Color' => [108, 88, 75],
'Name' => 'Pine Cone'
],
356 => [
'Color' => [160, 132, 79],
'Name' => 'Fawn brown'
],
357 => [
'Color' => [149, 137, 136],
'Name' => 'Hurricane grey'
],
358 => [
'Color' => [171, 168, 158],
'Name' => 'Cloudy grey'
],
359 => [
'Color' => [175, 148, 131],
'Name' => 'Linen'
],
360 => [
'Color' => [150, 103, 102],
'Name' => 'Copper'
],
361 => [
'Color' => [86, 66, 54],
'Name' => 'Dirt brown'
],
362 => [
'Color' => [126, 104, 63],
'Name' => 'Bronze'
],
363 => [
'Color' => [105, 102, 92],
'Name' => 'Flint'
],
364 => [
'Color' => [90, 76, 66],
'Name' => 'Dark taupe'
],
365 => [
'Color' => [106, 57, 9],
'Name' => 'Burnt Sienna'
],
1001 => [
'Color' => [248, 248, 248],
'Name' => 'Institutional white'
],
1002 => [
'Color' => [205, 205, 205],
'Name' => 'Mid gray'
],
1003 => [
'Color' => [17, 17, 17],
'Name' => 'Really black'
],
1004 => [
'Color' => [255, 0, 0],
'Name' => 'Really red'
],
1005 => [
'Color' => [255, 175, 0],
'Name' => 'Deep orange'
],
1006 => [
'Color' => [180, 128, 255],
'Name' => 'Alder'
],
1007 => [
'Color' => [163, 75, 75],
'Name' => 'Dusty Rose'
],
1008 => [
'Color' => [193, 190, 66],
'Name' => 'Olive'
],
1009 => [
'Color' => [255, 255, 0],
'Name' => 'New Yeller'
],
1010 => [
'Color' => [0, 0, 255],
'Name' => 'Really blue'
],
1011 => [
'Color' => [0, 32, 96],
'Name' => 'Navy blue'
],
1012 => [
'Color' => [33, 84, 185],
'Name' => 'Deep blue'
],
1013 => [
'Color' => [4, 175, 236],
'Name' => 'Cyan'
],
1014 => [
'Color' => [170, 85, 0],
'Name' => 'CGA brown'
],
1015 => [
'Color' => [170, 0, 170],
'Name' => 'Magenta'
],
1016 => [
'Color' => [255, 102, 204],
'Name' => 'Pink'
],
1017 => [
'Color' => [255, 175, 0],
'Name' => 'Deep orange'
],
1018 => [
'Color' => [18, 238, 212],
'Name' => 'Teal'
],
1019 => [
'Color' => [0, 255, 255],
'Name' => 'Toothpaste'
],
1020 => [
'Color' => [0, 255, 0],
'Name' => 'Lime green'
],
1021 => [
'Color' => [58, 125, 21],
'Name' => 'Camo'
],
1022 => [
'Color' => [127, 142, 100],
'Name' => 'Grime'
],
1023 => [
'Color' => [140, 91, 159],
'Name' => 'Lavender'
],
1024 => [
'Color' => [175, 221, 255],
'Name' => 'Pastel light blue'
],
1025 => [
'Color' => [255, 201, 201],
'Name' => 'Pastel orange'
],
1026 => [
'Color' => [177, 167, 255],
'Name' => 'Pastel violet'
],
1027 => [
'Color' => [159, 243, 233],
'Name' => 'Pastel blue-green'
],
1028 => [
'Color' => [204, 255, 204],
'Name' => 'Pastel green'
],
1029 => [
'Color' => [255, 255, 204],
'Name' => 'Pastel yellow'
],
1030 => [
'Color' => [255, 204, 153],
'Name' => 'Pastel brown'
],
1031 => [
'Color' => [98, 37, 209],
'Name' => 'Royal purple'
],
1032 => [
'Color' => [255, 0, 191],
'Name' => 'Hot pink'
]
];
public static function IsValidColor($colorId)
{
return array_key_exists($colorId, self::$brickColors);
}
}

View File

@ -1,15 +0,0 @@
<?php
/*
Graphictoria 2022
JSON Pretty Printer
*/
namespace App\Helpers;
class COMHelper
{
public static function isCOM() {
return (php_sapi_name() === 'cli');
}
}

View File

@ -0,0 +1,66 @@
<?php
/*
XlXi 2022
CDN helper.
*/
namespace App\Helpers;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request;
use App\Models\CdnHash;
class CdnHelper
{
public static function GetDisk()
{
return Storage::build([
'driver' => 'local',
'root' => storage_path('app/content'),
]);
}
public static function Hash($content)
{
return hash('sha256', $content);
}
public static function SaveContent($content, $mime)
{
$disk = self::GetDisk();
$hash = self::Hash($content);
if(!$disk->exists($hash) || !CdnHash::where('hash', $hash)->exists()) {
$disk->put($hash, $content);
$cdnItem = new CdnHash();
$cdnItem->hash = $hash;
$cdnItem->mime_type = $mime;
$cdnItem->save();
}
return $hash;
}
public static function Delete($hash)
{
$disk = self::GetDisk();
$cdnHash = CdnHash::where('hash', $hash);
if($disk->exists($hash) && $cdnHash->exists()) {
$cdnHash->delete();
$disk->delete($hash);
return true;
}
return false;
}
public static function SaveContentB64($contentB64, $mime)
{
return self::SaveContent(base64_decode($contentB64), $mime);
}
}

View File

@ -0,0 +1,26 @@
<?php
/*
XlXi 2022
Domain helper.
*/
namespace App\Helpers;
use Illuminate\Http\Request;
class DomainHelper
{
public static function TopLevelDomain()
{
$baseurl = config('app.url');
$baseurl = str_replace(['http://', 'https://'], '', $baseurl);
return $baseurl;
}
public static function DotLeadTopLevelDomain()
{
return '.' . DomainHelper::TopLevelDomain();
}
}

View File

@ -1,21 +0,0 @@
<?php
/*
Graphictoria 2022
Error helper
*/
namespace App\Helpers;
class ErrorHelper
{
/**
* Returns a JSON array with the error code and message.
*
* @return Response
*/
private static function error($data, $code = 400)
{
return response(['errors' => [$data]], 400);
}
}

View File

@ -1,36 +1,200 @@
<?php
/*
Graphictoria 2022
XlXi 2022
Grid helper
*/
namespace App\Helpers;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request;
use App\Helpers\COMHelper;
use App\Models\WebsiteConfiguration;
use App\Models\DynamicWebConfiguration;
class GridHelper
{
public static function isIpWhitelisted(Request $request) {
$ip = $request->ip();
$whitelistedIps = explode(';', WebsiteConfiguration::where('name', 'WhitelistedIPs')->first()->value);
public static function isIpWhitelisted()
{
$ip = request()->ip();
$whitelistedIps = explode(';', DynamicWebConfiguration::where('name', 'WhitelistedIPs')->first()->value);
return in_array($ip, $whitelistedIps);
}
public static function isAccessKeyValid(Request $request) {
$accessKey = WebsiteConfiguration::where('name', 'ComputeServiceAccessKey')->first()->value;
public static function isAccessKeyValid()
{
$accessKey = DynamicWebConfiguration::where('name', 'ComputeServiceAccessKey')->first()->value;
return ($request->header('AccessKey') == $accessKey);
return (request()->header('AccessKey') == $accessKey);
}
public static function hasAllAccess(Request $request) {
if(COMHelper::isCOM()) return true;
if(GridHelper::isIpWhitelisted($request) && GridHelper::isAccessKeyValid($request)) return true;
public static function hasAllAccess()
{
if(app()->runningInConsole()) return true;
if(self::isIpWhitelisted() && self::isAccessKeyValid()) return true;
return false;
}
public static function LuaValue($value)
{
switch ($value) {
case is_bool(json_encode($value)) || $value == 1:
return json_encode($value);
default:
return $value;
}
}
public static function CastType($value)
{
$luaTypeConversions = [
'NULL' => 'LUA_TNIL',
'boolean' => 'LUA_TBOOLEAN',
'integer' => 'LUA_TNUMBER',
'double' => 'LUA_TNUMBER',
'string' => 'LUA_TSTRING',
'array' => 'LUA_TTABLE',
'object' => 'LUA_TNIL'
];
return $luaTypeConversions[gettype($value)];
}
public static function ToLuaArguments($luaArguments = [])
{
$luaValue = ['LuaValue' => []];
foreach ($luaArguments as $argument) {
array_push(
$luaValue['LuaValue'],
[
'type' => self::CastType($argument),
'value' => self::LuaValue($argument)
]
);
}
return $luaValue;
}
public static function Job($jobID, $expiration, $category, $cores, $scriptName, $script, $scriptArgs = [])
{
return [
'job' => [
'id' => $jobID,
'expirationInSeconds' => $expiration,
'category' => $category,
'cores' => $cores
],
'script' => [
'name' => $scriptName,
'script' => $script,
'arguments' => self::ToLuaArguments($scriptArgs)
]
];
}
public static function JobTemplate($jobID, $expiration, $category, $cores, $scriptName, $templateName, $scriptArgs = [])
{
$disk = Storage::build([
'driver' => 'local',
'root' => storage_path('app/grid/scripts'),
]);
$fileName = sprintf('%s.lua', $templateName);
if(!$disk->exists($fileName))
throw new Exception('Unable to locate template file.');
$job = self::Job($jobID, $expiration, $category, $cores, $scriptName, '', $scriptArgs);
$job['script']['script'] = $disk->get($fileName);
return $job;
}
private static function getThumbDisk()
{
return Storage::build([
'driver' => 'local',
'root' => storage_path('app/grid/thumbnails'),
]);
}
public static function getDefaultThumbnail($fileName)
{
$disk = self::getThumbDisk();
if(!$disk->exists($fileName))
throw new Exception('Unable to locate template file.');
return $disk->get($fileName);
}
public static function getUnknownThumbnail()
{
return self::getDefaultThumbnail('UnknownThumbnail.png');
}
private static function getGameDisk()
{
return Storage::build([
'driver' => 'local',
'root' => storage_path('app/grid/game'),
]);
}
private static function getXMLFromGameDisk($fileName)
{
$disk = self::getGameDisk();
if(!$disk->exists($fileName))
throw new Exception('Unable to locate template file.');
return $disk->get($fileName);
}
public static function getBodyColorsXML()
{
return self::getXMLFromGameDisk('BodyColors.xml');
}
public static function getBodyPartXML()
{
return self::getXMLFromGameDisk('BodyPart.xml');
}
public static function getFaceXML()
{
return self::getXMLFromGameDisk('Face.xml');
}
public static function getArbiter($name)
{
$query = DynamicWebConfiguration::where('name', sprintf('%sArbiterIP', $name))->first();
if(!$query)
throw new Exception('Unknown arbiter.');
return $query->value;
}
public static function gameArbiter()
{
return sprintf('http://%s:64989', self::getArbiter('Game'));
}
public static function thumbnailArbiter()
{
return sprintf('http://%s:64989', self::getArbiter('Thumbnail'));
}
public static function gameArbiterMonitor()
{
return sprintf('http://%s:64990', self::getArbiter('Game'));
}
public static function thumbnailArbiterMonitor()
{
return sprintf('http://%s:64990', self::getArbiter('Thumbnail'));
}
}

View File

@ -1,21 +0,0 @@
<?php
/*
Graphictoria 2022
JSON Pretty Printer
*/
namespace App\Helpers;
class JSON
{
public static function EncodeResponse($array) {
$json = json_encode(
$array,
JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS
);
return response($json)
->header('Content-Type', 'application/json');
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
XlXi 2022
Maintenance helper
*/
namespace App\Helpers;
use Illuminate\Http\Request;
use Illuminate\Foundation\Http\MaintenanceModeBypassCookie;
class MaintenanceHelper
{
public static function isDown()
{
if(GridHelper::isIpWhitelisted())
return false;
if(!file_exists(storage_path('framework/down')))
return false;
$data = json_decode(file_get_contents(storage_path('framework/down')), true);
return (app()->isDownForMaintenance() && !MaintenanceHelper::hasValidBypassCookie($data));
}
protected static function hasValidBypassCookie(array $data)
{
$request = request();
return isset($data['secret']) &&
$request->cookie('vb_constraint') &&
MaintenanceModeBypassCookie::isValid(
$request->cookie('vb_constraint'),
$data['secret']
);
}
}

View File

@ -0,0 +1,43 @@
<?php
/*
XlXi 2022
A bit of a hacky way to implement markdown with laravel.
*/
namespace App\Helpers;
use Illuminate\Support\HtmlString;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
use League\CommonMark\Extension\DefaultAttributes\DefaultAttributesExtension;
use League\CommonMark\Extension\Table\TableExtension;
use League\CommonMark\MarkdownConverter;
class MarkdownHelper
{
// TODO: XlXi: Add a non-nav alert mode for links.
// XlXi: This bit was partially taken from https://github.com/laravel/framework/blob/b9203fca96960ef9cd8860cb4ec99d1279353a8d/src/Illuminate/Mail/Markdown.php line 106
public static function parse($text) {
$environment = new Environment([
'default_attributes' => [
Link::class => [
'class' => ['text-decoration-none']
],
Image::class => [
'classes' => ['img-fluid']
]
]
]);
$environment->addExtension(new CommonMarkCoreExtension);
$environment->addExtension(new TableExtension);
$environment->addExtension(new DefaultAttributesExtension);
$converter = new MarkdownConverter($environment);
return new HtmlString($converter->convert($text)->getContent());
}
}

View File

@ -0,0 +1,31 @@
<?php
/*
XlXi 2022
Number helper.
*/
namespace App\Helpers;
class NumberHelper
{
public static function Abbreviate($number)
{
$divisors = array(
pow(1000, 0) => '', // 1000^0 == 1
pow(1000, 1) => 'K+', // Thousand
pow(1000, 2) => 'M+', // Million
pow(1000, 3) => 'B+', // Billion
pow(1000, 4) => 'T+', // Trillion
pow(1000, 5) => 'Qa+', // Quadrillion
pow(1000, 6) => 'Qi+', // Quintillion
);
foreach ($divisors as $divisor => $shorthand) {
if (abs($number) < ($divisor * 1000))
break;
}
return number_format(floor($number / $divisor), 0) . $shorthand;
}
}

View File

@ -0,0 +1,154 @@
<?php
/*
XlXi 2022
Quick Administration and Management Bar helper
*/
namespace App\Helpers;
use App\Models\UsageCounter;
class QAaMBHelper
{
public static function wmiWBemLocatorQuery($query)
{
if(class_exists('\\COM')) {
try {
$WbemLocator = new \COM( "WbemScripting.SWbemLocator" );
$WbemServices = $WbemLocator->ConnectServer( '127.0.0.1', 'root\CIMV2' );
$WbemServices->Security_->ImpersonationLevel = 3;
return $WbemServices->ExecQuery( $query );
} catch ( \com_exception $e ) {
echo $e->getMessage();
}
} elseif ( ! extension_loaded( 'com_dotnet' ) )
trigger_error( 'It seems that the COM is not enabled in your php.ini', E_USER_WARNING );
else {
$err = error_get_last();
trigger_error( $err['message'], E_USER_WARNING );
}
return false;
}
public static function getSystemMemoryInfo($output_key = '')
{
return cache()->remember('QAaMB-Memory-Info', 5, function(){
$keys = array('MemTotal', 'MemFree', 'MemAvailable', 'SwapTotal', 'SwapFree');
$result = array();
try {
if (strtoupper(substr(PHP_OS, 0, 3)) != 'WIN') {
// LINUX
$proc_dir = '/proc/';
$data = _dir_in_allowed_path( $proc_dir ) ? @file( $proc_dir . 'meminfo' ) : false;
if ( is_array( $data ) )
foreach ( $data as $d ) {
if ( 0 == strlen( trim( $d ) ) )
continue;
$d = preg_split( '/:/', $d );
$key = trim( $d[0] );
if ( ! in_array( $key, $keys ) )
continue;
$value = 1000 * floatval( trim( str_replace( ' kB', '', $d[1] ) ) );
$result[$key] = $value;
}
} else {
// WINDOWS
$wmi_found = false;
if ( $wmi_query = self::wmiWBemLocatorQuery(
"SELECT FreePhysicalMemory,FreeVirtualMemory,TotalSwapSpaceSize,TotalVirtualMemorySize,TotalVisibleMemorySize FROM Win32_OperatingSystem" ) ) {
foreach($wmi_query as $r) {
$result['MemFree'] = $r->FreePhysicalMemory * 1024;
$result['MemAvailable'] = $r->FreeVirtualMemory * 1024;
$result['SwapFree'] = $r->TotalSwapSpaceSize * 1024;
$result['SwapTotal'] = $r->TotalVirtualMemorySize * 1024;
$result['MemTotal'] = $r->TotalVisibleMemorySize * 1024;
$wmi_found = true;
}
}
}
} catch(Exception $e) {
echo $e->getMessage();
}
return empty($output_key) || !isset($result[$output_key]) ? $result : $result[$output_key];
});
}
public static function getSystemCpuInfo($output_key = '')
{
$result = 0;
try {
if (strtoupper(substr(PHP_OS, 0, 3)) != 'WIN') {
// LINUX
return sys_getloadavg();
} else {
// WINDOWS
$wmi_found = false;
if ( $wmi_query = self::wmiWBemLocatorQuery(
"SELECT LoadPercentage FROM Win32_Processor" ) ) {
foreach($wmi_query as $r) {
$result = $r->LoadPercentage / 100;
$wmi_found = true;
}
}
}
} catch(Exception $e) {
echo $e->getMessage();
}
return empty($output_key) || !isset($result[$output_key]) ? $result : $result[$output_key];
}
public static function memoryString($memory)
{
$unit = array('B', 'KB', 'MB', 'GB', 'TB', 'PB');
return @round($memory/pow(1024,($i=floor(log($memory,1024)))),2).' '.$unit[$i];
}
public static function getMemoryUsage()
{
$memoryInfo = json_decode(UsageCounter::where('name', 'Memory')->first()->value, true);
// XlXi: the -2 is required so it fits inside of the bar thing
// XlXi: change this if theres ever a separate graph
return sprintf(
'%s of %s (%s%%) <br/> %s Free',
self::memoryString($memoryInfo['MemTotal'] - $memoryInfo['MemFree']),
self::memoryString($memoryInfo['MemTotal']),
round(self::getMemoryPercentage() * (100-2)),
self::memoryString($memoryInfo['MemFree'])
);
}
public static function getMemoryPercentage()
{
$memoryInfo = json_decode(UsageCounter::where('name', 'Memory')->first()->value, true);
return ($memoryInfo['MemTotal'] - $memoryInfo['MemFree']) / $memoryInfo['MemTotal'];
}
public static function getCpuUsage()
{
$cpuInfo = UsageCounter::where('name', 'CPU')->first();
// XlXi: the -2 is required so it fits inside of the bar thing
// XlXi: change this if theres ever a separate graph
return sprintf(
'%s%% CPU Usage',
round(floatval($cpuInfo->value) * (100-2))
);
}
public static function getCpuPercentage()
{
$cpuInfo = UsageCounter::where('name', 'CPU')->first();
return $cpuInfo->value;
}
}

View File

@ -1,28 +0,0 @@
<?php
/*
Graphictoria 2022
Random generator
*/
namespace App\Helpers;
class Random
{
/**
* Creates a random string of the specified length.
*
* @return String
*/
public static function Str($length) {
// https://stackoverflow.com/questions/4356289/php-random-string-generator
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
}

View File

@ -0,0 +1,29 @@
<?php
/*
XlXi 2022
Validation Helper
*/
namespace App\Helpers;
use Illuminate\Validation\Validator;
class ValidationHelper
{
public static function generateValidatorError($validator) {
return response(self::generateErrorJSON($validator), 400);
}
public static function generateErrorJSON(Validator $validator) {
$errorModel = [
'errors' => []
];
foreach($validator->errors()->all() as $error) {
array_push($errorModel['errors'], ['code' => 400, 'message' => $error]);
}
return $errorModel;
}
}

View File

@ -0,0 +1,353 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use App\Helpers\AssetHelper;
use App\Helpers\CdnHelper;
use App\Helpers\GridHelper;
use App\Helpers\ValidationHelper;
use App\Http\Controllers\Controller;
use App\Jobs\AppDeployment;
use App\Models\AssetType;
use App\Models\Deployment;
use App\Models\RobloxAsset;
use App\Rules\AppDeploymentFilenameRule;
class AdminController extends Controller
{
// Moderator+
// Admin+
function manualAssetUpload(Request $request)
{
$validator = Validator::make($request->all(), [
'asset-type-id' => ['required', 'int'],
'name' => ['required', 'string'],
'description' => ['string', 'nullable'],
'roblox-id' => ['int', 'min:0', 'nullable'],
'on-sale' => ['required', 'boolean'],
'price' => ['required_if:on-sale,true', 'int', 'min:0'],
'content' => ['nullable'],
'mesh-id' => ['int', 'nullable'],
'base-id' => ['int', 'nullable'],
'overlay-id' => ['int', 'nullable'],
],[
'asset-type-id.required' => 'An asset type ID must be provided.',
'roblox-id.integer' => 'Roblox ID must be an integer.'
]);
if($validator->fails())
return ValidationHelper::generateValidatorError($validator);
$valid = $validator->valid();
$isRobloxAsset = ($request->has('roblox-id') && $valid['roblox-id'] > 0);
if($isRobloxAsset)
{
$uploadedAsset = RobloxAsset::where('robloxAssetId', $valid['roblox-id'])->first();
if($uploadedAsset)
{
$validator->errors()->add('roblox-id', 'This asset has already been uploaded!');
return ValidationHelper::generateValidatorError($validator);
}
}
$assetType = AssetType::where('id', $valid['asset-type-id'])
->where('adminCreatable', 1)
->first();
if(!$assetType)
{
$validator->errors()->add('asset-type-id', 'Invalid asset type for admin upload.');
return ValidationHelper::generateValidatorError($validator);
}
$assetFunction = 'Unknown';
$assetFunctionArgs = [];
switch($assetType->id)
{
case 27: // Torso
case 28: // Right Arm
case 29: // Left Arm
case 30: // Left Leg
case 31: // Right Leg
$assetFunctionArgs = [$assetType->id, $valid];
$assetFunction = 'BodyPart';
break;
case 18: // Face
$assetFunctionArgs = [$validator, $valid];
$assetFunction = 'Face';
break;
default:
$assetFunctionArgs = [$validator];
$assetFunction = 'Generic';
break;
}
$assetContent = $this->{ 'manualAssetUpload' . $assetFunction }($request, ...$assetFunctionArgs);
$hash = CdnHelper::SaveContent($assetContent, 'application/octet-stream');
$asset = AssetHelper::newAsset([
'creatorId' => 1,
'name' => $valid['name'],
'description' => $valid['description'],
'approved' => true,
'priceInTokens' => $valid['price'],
'onSale' => $valid['on-sale'] == 1 ? true : false,
'assetTypeId' => $assetType->id,
'assetVersionId' => 0
], $hash);
$asset->logAdminUpload(Auth::user()->id);
if($isRobloxAsset)
{
RobloxAsset::create([
'robloxAssetId' => $valid['roblox-id'],
'localAssetId' => $asset->id
]);
}
return response([
'success' => true,
'message' => 'Your asset has been successfully uploaded!',
'assetId' => $asset->id
]);
}
function manualAssetUploadBodyPart(Request $request, $assetTypeId, $valid)
{
$bodyParts = [
27 => 1, // Torso
28 => 3, // Right Arm
29 => 2, // Left Arm
30 => 4, // Left Leg
31 => 5 // Right Leg
];
$document = simplexml_load_string(GridHelper::getBodyPartXML());
$document->xpath('//int[@name="BaseTextureId"]')[0][0] = $valid['base-id'] ?: 0;
$document->xpath('//token[@name="BodyPart"]')[0][0] = $bodyParts[$assetTypeId];
$document->xpath('//int[@name="MeshId"]')[0][0] = $valid['mesh-id'];
$document->xpath('//string[@name="Name"]')[0][0] = $valid['name'];
$document->xpath('//int[@name="OverlayTextureId"]')[0][0] = $valid['overlay-id'] ?: 0;
$domXML = dom_import_simplexml($document);
$assetContent = $domXML->ownerDocument->saveXML($domXML->ownerDocument->documentElement);
return $assetContent;
}
function manualAssetUploadFace(Request $request, $validator, $valid)
{
if(!$request->has('content'))
{
$validator->errors()->add('content', 'Asset content cannot be blank!');
return ValidationHelper::generateValidatorError($validator);
}
$hash = CdnHelper::SaveContent(
file_get_contents($request->file('content')->path()),
'application/octet-stream'
);
$imageAsset = AssetHelper::newAsset([
'creatorId' => 1,
'name' => $valid['name'],
'approved' => true,
'onSale' => false,
'assetTypeId' => 1, // Image
'assetVersionId' => 0
], $hash);
$imageAsset->logAdminUpload(Auth::user()->id);
$imageAssetUrl = route('client.asset', ['id' => $imageAsset->id]);
$document = simplexml_load_string(GridHelper::getFaceXML());
$document->xpath('//Content[@name="Texture"]')[0][0]->addChild('url', $imageAssetUrl);
$domXML = dom_import_simplexml($document);
$assetContent = $domXML->ownerDocument->saveXML($domXML->ownerDocument->documentElement);
return $assetContent;
}
function manualAssetUploadGeneric(Request $request, $validator)
{
if(!$request->has('content'))
{
$validator->errors()->add('content', 'Asset content cannot be blank!');
return ValidationHelper::generateValidatorError($validator);
}
$assetContent = file_get_contents($request->file('content')->path());
return $assetContent;
}
function manualAssetUploadUnknown(Request $request)
{
throw new \BadMethodCallException('Not implemented');
}
// Owner+
function deploy(Request $request)
{
$validator = Validator::make($request->all(), [
'version' => ['regex:/version\\-[a-fA-F0-9]{16}/'],
'type' => ['required_without:version', 'regex:/(Deploy|Revert)/i'],
'app' => ['required_without:version', 'regex:/(Client|Studio)/i']
]);
if($validator->fails())
return ValidationHelper::generateValidatorError($validator);
$valid = $validator->valid();
$response = [
'status' => 'Loading',
'version' => null,
'message' => 'Please wait...',
'progress' => 0
];
if(!$request->has('version'))
{
$deployment = Deployment::newVersionHash($valid);
$response['version'] = $deployment->version;
$response['message'] = 'Created deployment.';
$response['progress'] = 0;
return response($response);
}
$deployment = Deployment::where('version', $valid['version'])->first();
if($deployment === null || !$deployment->isValid()) {
$validator->errors()->add('version', 'Unknown version deployment hash.');
return ValidationHelper::generateValidatorError($validator);
}
$response['version'] = $deployment->version;
if($deployment->error != null)
{
$response['status'] = 'Error';
$response['message'] = sprintf('Failed to deploy %s. Error: %s', $deployment->version, $deployment->error);
$response['progress'] = 1;
return response($response);
}
$steps = 5;
$response['progress'] = $deployment->step/$steps;
switch($deployment->step)
{
case 0:
$response['message'] = 'Files uploading.';
break;
case 1:
$response['message'] = 'Batching deployment.';
break;
case 2:
$response['message'] = 'Unpacking files.';
break;
case 3:
$response['message'] = 'Updating version security.';
break;
case 4:
$response['message'] = 'Pushing deployment to setup.';
break;
case 5:
$response['status'] = 'Success';
$response['message'] = sprintf('Deploy completed. Successfully deployed %s %s', $deployment->app, $deployment->version);
break;
}
return response($response);
}
function deployVersion(Request $request, string $version)
{
$validator = Validator::make($request->all(), [
'file.*' => ['required']
]);
if($validator->fails())
return ValidationHelper::generateValidatorError($validator);
$valid = $validator->valid();
$deployment = Deployment::where('version', $version)->first();
if($deployment === null || !$deployment->isValid() || $deployment->step != 0) {
$validator->errors()->add('version', 'Unknown version deployment hash.');
return ValidationHelper::generateValidatorError($validator);
}
$deploymentRule = new AppDeploymentFilenameRule($deployment->app);
if(!$deploymentRule->passes('file', $request->file('file')))
{
$deployment->error = 'Missing files.';
$deployment->save();
$validator->errors()->add('file', $deployment->error);
return ValidationHelper::generateValidatorError($validator);
}
foreach($request->file('file') as $file)
{
$file->storeAs(
'setuptmp',
sprintf('%s-%s', $version, $file->getClientOriginalName())
);
}
$deployment->step = 1; // Batching deployment.
$deployment->save();
AppDeployment::dispatch($deployment);
}
// RCC Only
function uploadRobloxAsset(Request $request)
{
$validator = Validator::make($request->all(), [
'contentId' => ['required', 'int']
]);
if($validator->fails())
return ValidationHelper::generateValidatorError($validator);
if(!GridHelper::hasAllAccess())
{
$validator->errors()->add('contentId', 'This API can only be called by the web service.');
return ValidationHelper::generateValidatorError($validator);
}
$valid = $validator->valid();
$asset = AssetHelper::uploadRobloxAsset($valid['contentId'], true);
return route('client.asset', ['id' => $asset->id]);
}
function uploadAsset(Request $request)
{
$validator = Validator::make($request->all(), [
'contentId' => ['required', 'int']
]);
if($validator->fails())
return ValidationHelper::generateValidatorError($validator);
if(!GridHelper::hasAllAccess())
{
$validator->errors()->add('contentId', 'This API can only be called by the web service.');
return ValidationHelper::generateValidatorError($validator);
}
$valid = $validator->valid();
$asset = AssetHelper::uploadCustomRobloxAsset($valid['contentId'], true, base64_encode($request->getContent()));
return route('client.asset', ['id' => $asset->id]);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ApiController extends Controller
{
public function index()
{
// TODO: XlXi: Add some checks here, such as pinging api.virtubrick.net, checking commonly used API functions, etc...
return response('API OK!')
->header('Content-Type', 'text/plain');
}
}

View File

@ -0,0 +1,259 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Support\Str;
use App\Helpers\BrickColorHelper;
use App\Helpers\ValidationHelper;
use App\Http\Controllers\Controller;
use App\Models\AvatarAsset;
use App\Models\AvatarColor;
use App\Models\UserAsset;
class AvatarController extends Controller
{
protected $validAssetTypeIds = [
'17', // Heads
'18', // Faces
'8', // Hats
'2', // T-Shirts
'11', // Shirts
'12', // Pants
'19', // Gear
'27', // Torsos
'29', // Left Arms
'28', // Right Arms
'30', // Left Legs
'31', // Right Legs
'32' // Packages
];
public function redrawUser()
{
Auth::user()->redraw();
return response(['success' => true]);
}
public static function GetUserAssets($userId)
{
return UserAsset::where('owner_id', $userId)
->whereRelation('asset', 'moderated', false)
->orderByDesc('id');
}
public function listAssets(Request $request)
{
$validator = Validator::make($request->all(), [
'assetTypeId' => ['required', 'int']
]);
if($validator->fails()) {
return ValidationHelper::generateValidatorError($validator);
}
$valid = $validator->valid();
if(!in_array($valid['assetTypeId'], $this->validAssetTypeIds)) {
$validator->errors()->add('assetTypeId', 'Invalid assetTypeId supplied.');
return ValidationHelper::generateValidatorError($validator);
}
$userAssets = self::GetUserAssets(Auth::user()->id)
->whereRelation('asset', 'assetTypeId', $valid['assetTypeId'])
->groupBy('asset_id')
->paginate(12);
$data = [];
foreach($userAssets as $userAsset)
{
$asset = $userAsset->asset;
array_push($data, [
'id' => $asset->id,
'Url' => $asset->getShopUrl(),
'Thumbnail' => $asset->getThumbnail(),
'Name' => $asset->name,
'Wearing' => Auth::user()->isWearing($asset->id)
]);
}
return response([
'data' => $data,
'pages' => ($userAssets->hasPages() ? $userAssets->lastPage() : 1)
]);
}
public function listWearing(Request $request)
{
$avatarAssets = AvatarAsset::where('owner_id', Auth::user()->id)->get();
$data = [];
foreach($avatarAssets as $avatarAsset)
{
$asset = $avatarAsset->asset;
array_push($data, [
'id' => $asset->id,
'Url' => $asset->getShopUrl(),
'Thumbnail' => $asset->getThumbnail(),
'Name' => $asset->name,
'Wearing' => true
]);
}
return response([
'data' => $data
]);
}
public function wearAsset(Request $request)
{
$validator = Validator::make($request->all(), [
'id' => [
'required',
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
return $query->where('moderated', false);
})
]
]);
if($validator->fails()) {
return ValidationHelper::generateValidatorError($validator);
}
$valid = $validator->valid();
$userAsset = self::GetUserAssets(Auth::user()->id)
->where('asset_id', $valid['id'])
->first();
if(!$userAsset) {
$validator->errors()->add('id', 'User does not own asset.');
return ValidationHelper::generateValidatorError($validator);
}
if(Auth::user()->isWearing($valid['id']) && $userAsset->asset->assetTypeId == 8) { // 8 = hat
$validator->errors()->add('id', 'User is already wearing asset.');
return ValidationHelper::generateValidatorError($validator);
}
if(!in_array($userAsset->asset->assetTypeId, $this->validAssetTypeIds)) {
$validator->errors()->add('id', 'This asset cannot be worn.');
return ValidationHelper::generateValidatorError($validator);
}
$wornItems = AvatarAsset::where('owner_id', Auth::user()->id)
->whereRelation('asset', 'assetTypeId', $userAsset->asset->assetTypeId);
if($userAsset->asset->assetTypeId != 8 && $wornItems->exists()) // 8 = hat
{
$wornItems->delete();
}
elseif($userAsset->asset->assetTypeId == 8 && $wornItems->count() >= 10)
{
$validator->errors()->add('id', 'User has hit the wearing limit on this asset type.');
return ValidationHelper::generateValidatorError($validator);
}
Auth::user()->wearAsset($valid['id']);
Auth::user()->redraw();
return response(['success' => true]);
}
public function removeAsset(Request $request)
{
$validator = Validator::make($request->all(), [
'id' => [
'required',
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
return $query->where('moderated', false);
})
]
]);
if($validator->fails()) {
return ValidationHelper::generateValidatorError($validator);
}
$valid = $validator->valid();
if(!Auth::user()->isWearing($valid['id'])) {
$validator->errors()->add('id', 'User is not wearing asset.');
return ValidationHelper::generateValidatorError($validator);
}
AvatarAsset::where('owner_id', Auth::user()->id)
->where('asset_id', $valid['id'])
->delete();
Auth::user()->redraw();
return response(['success' => true]);
}
public function setBodyColor(Request $request)
{
$validator = Validator::make($request->all(), [
'part' => ['required', 'regex:/(Head|Torso|LeftArm|RightArm|LeftLeg|RightLeg)/i'],
'color' => ['required', 'int']
]);
if($validator->fails()) {
return ValidationHelper::generateValidatorError($validator);
}
$valid = $validator->valid();
if(!BrickColorHelper::isValidColor($valid['color'])) {
$validator->errors()->add('color', 'Invalid color id.');
return ValidationHelper::generateValidatorError($validator);
}
$part = strtolower($valid['part']);
switch($part)
{
case 'leftarm':
$part = 'leftArm';
break;
case 'rightarm':
$part = 'rightArm';
break;
case 'leftleg':
$part = 'leftLeg';
break;
case 'rightleg':
$part = 'rightLeg';
break;
}
$bodyColors = Auth::user()->getBodyColors();
$bodyColors->{$part} = $valid['color'];
$bodyColors->save();
Auth::user()->redraw();
return response(['success' => true]);
}
public function getBodyColors(Request $request)
{
$bodyColors = Auth::user()->getBodyColors();
return response([
'data' => [
'Head' => $bodyColors->head,
'Torso' => $bodyColors->torso,
'RightArm' => $bodyColors->rightArm,
'LeftArm' => $bodyColors->leftArm,
'RightLeg' => $bodyColors->rightLeg,
'LeftLeg' => $bodyColors->leftLeg
]
]);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use App\Http\Controllers\Controller;
use App\Models\NegotiationTicket;
class ClientController extends Controller
{
function generateAuthTicket()
{
$ticket = Str::random(100);
NegotiationTicket::create([
'ticket' => $ticket,
'userId' => Auth::user()->id
]);
return response($ticket)
->header('Content-Type', 'text/plain');
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Carbon\Carbon;
use App\Helpers\ValidationHelper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Comment;
class CommentsController extends Controller
{
protected function listJson(Request $request)
{
$validator = Validator::make($request->all(), [
'assetId' => [
'required',
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
return $query->where('moderated', false);
})
]
]);
if($validator->fails())
return ValidationHelper::generateValidatorError($validator);
$valid = $validator->valid();
$comments = Comment::where('asset_id', $valid['assetId'])
->where('deleted', false)
->orderByDesc('id')
->cursorPaginate(15);
$prevCursor = $comments->previousCursor();
$nextCursor = $comments->nextCursor();
$result = [
'data' => [],
'prev_cursor' => ($prevCursor ? $prevCursor->encode() : null),
'next_cursor' => ($nextCursor ? $nextCursor->encode() : null)
];
foreach($comments as $comment) {
$poster = $comment->user->userToJson();
$postDate = $comment['updated_at'];
if(Carbon::now()->greaterThan($postDate->copy()->addDays(2)))
$postDate = $postDate->isoFormat('lll');
else
$postDate = $postDate->calendar();
array_push($result['data'], [
'commentId' => $comment->id,
'poster' => $poster,
'content' => $comment->content,
'time' => $postDate
]);
}
return $result;
}
protected function share(Request $request)
{
$validator = Validator::make($request->all(), [
'assetId' => [
'required',
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
return $query->where('moderated', false);
})
],
'content' => ['required', 'max:200']
]);
if($validator->fails())
return ValidationHelper::generateValidatorError($validator);
$valid = $validator->valid();
$comment = new Comment();
$comment->asset_id = $valid['assetId'];
$comment->author_id = Auth::id();
$comment->content = $valid['content'];
$comment->save();
return response(['success' => true]);
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\Api;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
use App\Models\Friend;
use App\Models\Shout;
use App\Models\User;
class FeedController extends Controller
{
protected function listJson()
{
// TODO: XlXi: Group shouts.
$postsQuery = Shout::getPosts()
->orderByDesc('id')
->cursorPaginate(15);
/* */
$prevCursor = $postsQuery->previousCursor();
$nextCursor = $postsQuery->nextCursor();
$posts = [
'data' => [],
'prev_cursor' => ($prevCursor ? $prevCursor->encode() : null),
'next_cursor' => ($nextCursor ? $nextCursor->encode() : null)
];
foreach($postsQuery as $post) {
// TODO: XlXi: groups
$poster = [];
if($post['poster_type'] == 'user') {
$user = User::where('id', $post['poster_id'])->first();
$poster = $user->userToJson();
}
/* */
$postDate = $post['updated_at'];
if(Carbon::now()->greaterThan($postDate->copy()->addDays(2)))
$postDate = $postDate->isoFormat('lll');
else
$postDate = $postDate->calendar();
/* */
array_push($posts['data'], [
'postId' => $post['id'],
'poster' => $poster,
'content' => $post['content'],
'time' => $postDate
]);
}
return response($posts);
}
protected function share(Request $request)
{
$validated = $request->validate([
'content' => ['required', 'max:200']
]);
$shout = new Shout();
$shout->poster_id = Auth::id();
$shout->poster_type = 'user';
$shout->content = $validated['content'];
$shout->save();
return response(['success' => true]);
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\ValidationHelper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Universe;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
class GamesController extends Controller
{
protected static function getAssets()
{
// TODO: XlXi: sort also based on how many people are in open servers
return Universe::where('public', true)
->whereRelation('starterPlace', 'moderated', false)
->join('assets', 'assets.id', '=', 'universes.startPlaceId')
->orderBy('assets.created_at', 'desc')
->orderBy('assets.visits', 'desc');
}
protected function listJson(Request $request)
{
$assets = self::getAssets()->paginate(30);
$data = [];
foreach($assets as $asset) {
$asset = $asset->starterPlace;
$creator = $asset->user;
array_push($data, [
'Name' => $asset->universe->name,
'Creator' => [
'Name' => $creator->username,
'Url' => $creator->getProfileUrl()
],
'Playing' => 0,
'Ratio' => 0,
'Url' => route('games.asset', ['asset' => $asset->id, 'assetName' => Str::slug($asset->name, '-')])
]);
}
return response([
'pages' => ($assets->hasPages() ? $assets->lastPage() : 1),
'data' => $data
]);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Api;
use Carbon\Carbon;
use Symfony\Component\HttpFoundation\Cookie;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Models\DynamicWebConfiguration;
use App\Helpers\ValidationHelper;
use App\Http\Controllers\Controller;
class MaintenanceController extends Controller
{
public function bypass(Request $request)
{
$validator = Validator::make($request->all(), [
'password' => ['required'],
'buttons' => ['required']
]);
if($validator->fails())
return ValidationHelper::generateValidatorError($validator);
$valid = $validator->valid();
$password = $valid['password'];
$buttons = $valid['buttons'];
$mtconf = json_decode(DynamicWebConfiguration::whereName('MaintenancePassword')->first()->value);
if(file_exists(storage_path('framework/down')) && $password == $mtconf->password)
{
$btns = array_slice($buttons, -count($mtconf->combination));
$data = json_decode(file_get_contents(storage_path('framework/down')), true);
if(isset($data['secret']) && $btns === $mtconf->combination)
{
$trustedHosts = explode(',', env('TRUSTED_HOSTS'));
$origin = join('.', array_slice(explode('.', explode('//', $request->headers->get('origin'))[1]), -2));
$passCheck = false;
foreach($trustedHosts as &$host)
{
if(str_ends_with($origin, $host))
$passCheck = true;
}
$expiresAt = Carbon::now()->addHours(24);
$bypassCookie = new Cookie('vb_constraint', base64_encode(json_encode([
'expires_at' => $expiresAt->getTimestamp(),
'mac' => hash_hmac('SHA256', $expiresAt->getTimestamp(), $data['secret']),
])), $expiresAt);
$bypassCookie = $bypassCookie->withSameSite('none');
if($passCheck)
$bypassCookie = $bypassCookie->withDomain('.' . $origin);
return response('')
->withCookie($bypassCookie);
}
}
$validator->errors()->add('password', 'Bad Request.');
return ValidationHelper::generateValidatorError($validator);
}
}

View File

@ -0,0 +1,151 @@
<?php
namespace App\Http\Controllers\Api;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use App\Helpers\ValidationHelper;
use App\Http\Controllers\Controller;
use App\Models\Transaction;
use App\Models\TransactionType;
use App\Models\User;
class MoneyController extends Controller
{
public function userSummary(Request $request)
{
$result = [
'columns' => [],
'total' => 0
];
$dataPoints = [
[
'Name' => 'Item Purchases',
'Points' => ['Purchases']
],
[
'Name' => 'Sale of Goods',
'Points' => ['Sales', 'Commissions']
],
[
'Name' => 'Group Payouts',
'Points' => ['Group Payouts']
]
];
foreach($dataPoints as $dataPoint)
{
$newColumn = ['name' => $dataPoint['Name'], 'total' => 0];
foreach($dataPoint['Points'] as $transactionType)
{
$column = $transactionType == 'Sales' ? 'seller_id' : 'user_id';
$newColumn['total'] += Transaction::where($column, Auth::user()->id)
->where('transaction_type_id', TransactionType::IDFromType($transactionType))
->where(function($query) use($request) {
if(!$request->has('filter'))
return $query;
$now = Carbon::now();
switch($request->get('filter'))
{
case 'pastday':
return $query->where('created_at', '>', $now->subDay());
case 'pastweek':
return $query->where('created_at', '>', $now->subWeek());
case 'pastmonth':
return $query->where('created_at', '>', $now->subMonth());
case 'pastyear':
return $query->where('created_at', '>', $now->subYear());
default:
return $query;
}
})
->sum('delta');
}
array_push($result['columns'], $newColumn);
$result['total'] += $newColumn['total'];
}
return response($result);
}
public function userTransactions(Request $request)
{
$validator = Validator::make($request->all(), [
'filter' => ['required', 'in:purchases,sales,commissions,grouppayouts']
]);
if($validator->fails())
return ValidationHelper::generateValidatorError($validator);
$valid = $validator->valid();
$resultData = [];
$transactionType = 0;
switch($valid['filter'])
{
case 'purchases':
$transactionType = TransactionType::where('name', 'Purchases')->first();
break;
case 'sales':
$transactionType = TransactionType::where('name', 'Sales')->first();
break;
case 'commissions':
$transactionType = TransactionType::where('name', 'Commissions')->first();
break;
case 'grouppayouts':
$transactionType = TransactionType::where('name', 'Group Payouts')->first();
break;
}
$transactions = Transaction::where(function($query) use($valid) {
if($valid['filter'] == 'sales')
return $query->where('seller_id', Auth::user()->id);
return $query->where('user_id', Auth::user()->id);
})
->where('transaction_type_id', $transactionType->id)
->with('asset')
->orderByDesc('id')
->cursorPaginate(30);
$prevCursor = $transactions->previousCursor();
$nextCursor = $transactions->nextCursor();
foreach($transactions as $transaction)
{
$user = null;
if($valid['filter'] != 'sales')
$user = $transaction->seller;
else
$user = $transaction->user;
$asset = null;
if($transactionType->format != '')
$asset = [
'url' => $transaction->asset->getShopUrl(),
'name' => $transaction->asset->name
];
array_push($resultData, [
'date' => $transaction->created_at->isoFormat('lll'),
'member' => $user->userToJson(),
'description' => $transactionType->format,
'amount' => $transaction->delta,
'item' => $asset
]);
}
return response([
'data' => $resultData,
'prev_cursor' => ($prevCursor ? $prevCursor->encode() : null),
'next_cursor' => ($nextCursor ? $nextCursor->encode() : null)
]);
}
}

View File

@ -0,0 +1,150 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use App\Helpers\ValidationHelper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Transaction;
use App\Models\TransactionType;
use App\Models\UserAsset;
class ShopController extends Controller
{
protected $validAssetTypeIds = [
'2', // T-Shirts
'8', // Hats
'11', // Shirts
'12', // Pants
'17', // Heads
'18', // Faces
'19', // Gear
'32' // Packages
];
protected static function getAssets($assetTypeIds, $gearGenre=null)
{
// TODO: XlXi: Group owned assets
return Asset::where('approved', true)
->where('moderated', false)
->where('onSale', true)
->where(function($query) use($assetTypeIds, $gearGenre) {
$query->whereIn('assetTypeId', explode(',', $assetTypeIds));
if ($gearGenre != null)
$query->whereIn('assetAttributeId', explode(',', $gearGenre));
});
}
protected function listJson(Request $request)
{
$validator = Validator::make($request->all(), [
'assetTypeId' => ['required', 'regex:/^\\d(,?\\d)*$/i'],
'gearGenreId' => ['regex:/^\\d(,?\\d)*$/i']
]);
if($validator->fails()) {
return ValidationHelper::generateValidatorError($validator);
}
$valid = $validator->valid();
foreach(explode(',', $valid['assetTypeId']) as $assetTypeId) {
if(!in_array($assetTypeId, $this->validAssetTypeIds)) {
$validator->errors()->add('assetTypeId', 'Invalid assetTypeId supplied.');
return ValidationHelper::generateValidatorError($validator);
}
}
if($valid['assetTypeId'] != '19' && isset($valid['gearGenreId'])) {
$validator->errors()->add('gearGenreId', 'gearGenreId can only be used with assetTypeId 19.');
return ValidationHelper::generateValidatorError($validator);
}
/* */
$assets = self::getAssets($valid['assetTypeId'], (isset($valid['gearGenreId']) ? $valid['gearGenreId'] : null));
$assets = $assets->orderByDesc('created_at')
->paginate(35);
$data = [];
foreach($assets as $asset) {
$creator = $asset->user;
array_push($data, [
'Name' => $asset->name,
'Creator' => [
'Name' => $creator->username,
'Url' => $creator->getProfileUrl()
],
'Thumbnail' => $asset->getThumbnail(),
'OnSale' => $asset->onSale,
'Price' => $asset->priceInTokens,
'Url' => $asset->getShopUrl()
]);
}
return response([
'pages' => ($assets->hasPages() ? $assets->lastPage() : 1),
'data' => $data
]);
}
protected function purchase(Request $request, Asset $asset)
{
// TODO: XlXi: limiteds
$validator = Validator::make($request->all(), [
'expectedPrice' => ['int']
]);
if($validator->fails()) {
return ValidationHelper::generateValidatorError($validator);
}
$valid = $validator->valid();
$result = [
'success' => false,
'userFacingMessage' => null,
'priceInTokens' => $asset->priceInTokens
];
$price = $asset->priceInTokens;
$user = Auth::user();
if($asset->assetType->locked)
{
$result['userFacingMessage'] = 'This asset cannot be purchased.';
$result['priceInTokens'] = null;
return response($result);
}
if($user->hasAsset($asset->id))
{
$result['userFacingMessage'] = 'You already own this item.';
return response($result);
}
if($valid['expectedPrice'] != $price)
return response($result);
if($asset->priceInTokens > $user->tokens)
{
$result['userFacingMessage'] = 'You can\'t afford this item.';
return response($result);
}
$result['success'] = true;
Transaction::createAssetSale($user, $asset);
$user->removeFunds($price);
$asset->user->addFunds($price * (1-.3)); // XlXi: 30% tax
UserAsset::createSerialed($user->id, $asset->id);
return response($result);
}
}

View File

@ -0,0 +1,162 @@
<?php
namespace App\Http\Controllers\Api;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use App\Helpers\GridHelper;
use App\Helpers\ValidationHelper;
use App\Http\Controllers\Controller;
use App\Jobs\ArbiterRender;
use App\Models\Asset;
use App\Models\RenderTracker;
class ThumbnailController extends Controller
{
private function assetValidationRules()
{
return [
'id' => [
'required',
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
return $query->where('moderated', false);
})
],
'type' => 'regex:/(3D|2D)/i'
];
}
private function userValidationRules()
{
return [
'id' => [
'required',
Rule::exists('App\Models\User', 'id'),
],
'position' => ['sometimes', 'regex:/(Full|Bust)/i'],
'type' => 'regex:/(3D|2D)/i'
];
}
private function handleRender(Request $request, string $renderType, bool $assetId = null)
{
$validator = Validator::make($request->all(), $this->{strtolower($renderType) . 'ValidationRules'}());
if($validator->fails())
return ValidationHelper::generateValidatorError($validator);
$valid = $validator->valid();
$model = ('App\\Models\\' . $renderType)::where('id', $valid['id'])->first();
$valid['type'] = strtolower($valid['type']);
if($renderType == 'User') {
if($model->hasActivePunishment() && $model->getPunishment()->isDeletion())
{
$validator->errors()->add('id', 'User is moderated');
return ValidationHelper::generateValidatorError($validator);
}
if(!array_key_exists('position', $valid))
$valid['position'] = 'Full';
$valid['position'] = strtolower($valid['position']);
if($valid['position'] != 'full' && $valid['type'] == '3d')
{
$validator->errors()->add('type', 'Cannot render non-full avatar as 3D.');
return ValidationHelper::generateValidatorError($validator);
}
switch($valid['position'])
{
case 'full':
if($model->thumbnail2DHash && $valid['type'] == '2d')
return response(['status' => 'success', 'data' => route('content', $model->thumbnail2DHash)]);
break;
case 'bust':
if($model->thumbnailBustHash && $valid['type'] == '2d')
return response(['status' => 'success', 'data' => route('content', $model->thumbnailBustHash)]);
break;
}
} elseif($renderType == 'Asset') {
if($model->renderId)
$model = Asset::where('id', $model->renderId)->first();
if($model->moderated)
return response(['status' => 'success', 'data' => '/thumbs/DeletedThumbnail.png']);
if(!$model->approved)
return response(['status' => 'success', 'data' => '/thumbs/PendingThumbnail.png']);
if(!$model->assetType->renderable)
return response(['status' => 'success', 'data' => '/thumbs/UnavailableThumbnail.png']);
if(!$model->{$valid['type'] == '3d' ? 'canRender3D' : 'isRenderable'}()) {
$validator->errors()->add('id', 'This asset cannot be rendered.');
return ValidationHelper::generateValidatorError($validator);
}
if($model->thumbnail2DHash && $valid['type'] == '2d')
return response(['status' => 'success', 'data' => route('content', $model->thumbnail2DHash)]);
}
if($model->thumbnail3DHash && $valid['type'] == '3d')
return response(['status' => 'success', 'data' => route('content', $model->thumbnail3DHash)]);
$trackerType = sprintf('%s%s', strtolower($renderType), $valid['type']);
if($renderType == 'User' && $valid['position'] == 'bust')
$trackerType .= 'bust';
$tracker = RenderTracker::where('type', $trackerType)
->where('target', $model->id)
->where('created_at', '>', Carbon::now()->subMinute());
if(!$tracker->exists()) {
$tracker = RenderTracker::create([
'type' => $trackerType,
'target' => $model->id
]);
ArbiterRender::dispatch(
$tracker,
$valid['type'] == '3d',
($renderType == 'User' ? $valid['position'] == 'full' ? 'Avatar' : 'Bust' : $model->typeString()),
$model->id
);
}
return response(['status' => 'loading']);
}
public function renderAsset(Request $request)
{
return $this->handleRender($request, 'Asset');
}
public function renderUser(Request $request)
{
return $this->handleRender($request, 'User');
}
public function tryAsset(Request $request)
{
$validator = Validator::make($request->all(), [
'id' => [
'required',
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
return $query->where('moderated', false);
})
]
]);
if($validator->fails())
return ValidationHelper::generateValidatorError($validator);
$valid = $validator->valid();
return $this->handleRender($request, 'User', $valid['id']);
}
}

View File

@ -1,99 +0,0 @@
<?php
namespace App\Http\Controllers\Apis;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use App\Helpers\JSON;
use App\Helpers\GridHelper;
use App\Helpers\ErrorHelper;
use App\Models\FFlag;
use App\Models\Fbucket;
class AppSettings extends Controller
{
/**
* A list of flag prefixes
*
* @var array
*/
protected $prefixes = [
'Unscoped' => '',
'Fast' => 'F',
'Dynamic' => 'DF',
'Synchronised' => 'SF'
];
/**
* A list of flag types
*
* @var array
*/
protected $types = [
'Log' => 'Log',
'Int' => 'Int',
'String' => 'String',
'Boolean' => 'Flag'
];
/**
* Returns a JSON array of settings for the specified bucket.
*
* @param \Illuminate\Http\Request $request
* @param string $bucketName
* @return Response
*/
public function getBucket(Request $request, $bucketName)
{
$primaryBucket = Fbucket::where('name', $bucketName);
if($primaryBucket->exists()) {
$primaryBucket = $primaryBucket->first();
$bucketIds = [ $primaryBucket->id ];
$bucketIds = array_merge($bucketIds, json_decode($primaryBucket->inheritedGroupIds));
if($primaryBucket->protected == 1 && !GridHelper::hasAllAccess($request)) {
return ErrorHelper::error([
'code' => 2,
'message' => 'You do not have access to this bucket.'
], 401);
}
/* */
$flags = [];
foreach($bucketIds as $bucket) {
$fflags = FFlag::where('bucketId', $bucket)->get();
foreach($fflags as $flag) {
$prefix = $this->prefixes[$flag->type];
$dataType = $this->types[$flag->dataType];
$name = '';
if($flag->type != 'Unscoped') {
$name = ($prefix . $dataType);
}
$name .= $flag->name;
$flags[$name] = $flag->value;
}
}
ksort($flags);
return JSON::EncodeResponse($flags);
} else {
return ErrorHelper::error([
'code' => 1,
'message' => 'The requested bucket does not exist.'
]);
}
}
}

View File

@ -1,43 +0,0 @@
<?php
namespace App\Http\Controllers\Apis;
use Illuminate\Http\Request;
use App\Models\WebsiteConfiguration;
use App\Helpers\GridHelper;
class VersionCompatibility extends Controller
{
function getVersions(Request $request)
{
if(!GridHelper::hasAllAccess($request)) {
return ErrorHelper::error([
'code' => 1,
'message' => 'You do not have access to this resource.'
], 401);
}
return Response()->json([
'data' => [
explode(';', WebsiteConfiguration::where('name', 'VersionCompatibilityVersions')->first()->value)
]
]);
}
function getMD5Hashes(Request $request)
{
if(!GridHelper::hasAllAccess($request)) {
return ErrorHelper::error([
'code' => 1,
'message' => 'You do not have access to this resource.'
], 401);
}
return Response()->json([
'data' => [
explode(';', WebsiteConfiguration::where('name', 'VersionCompatibilityHashes')->first()->value)
]
]);
}
}

View File

@ -1,126 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use App\Helpers\AuthHelper;
use Auth;
use App\Models\User;
class AuthController extends Controller
{
/**
* Creates an account for the user.
*
* @return Response
*/
public function Register(Request $request) {
if(AuthHelper::Guard($request))
return Response(null, 400);
/* */
$data = $request->all();
if ($request->input('password') != $request->input('confirmation'))
return Response()->json(['message'=>'The passwords you supplied don\'t match!', 'badInputs'=>['password','confirmation']]);
$valid = Validator::make(
$data,
[
'username' => ['required', 'string', 'regex:/[a-zA-Z0-9._]+/', 'max:20'],
'email' => ['required', 'string', 'email', 'max:255'],
'password' => ['required', 'string', 'min:8'],
]
);
if ($valid->stopOnFirstFailure()->fails()) {
$error = $valid->errors()->first();
$messages = $valid->messages()->get('*');
return Response()->json(['message'=>$error, 'badInputs'=>[array_keys($messages)]]);
}
if(User::where('username', $data['username'])->first())
return Response()->json(['message'=>'This user already exists.', 'badInputs'=>['username']]);
if(User::where('email', $data['email'])->first())
return Response()->json(['message'=>'This email is already in use!', 'badInputs'=>['email']]);
$user = new User();
$user->username = $data['username'];
$user->email = $data['email'];
$user->password = Hash::make($data['password']);
$user->about = 'I\'m new to Graphictoria!';
$user->save();
$request->session()->regenerate();
$newSession = AuthHelper::GrantSession($request, $user->id);
$request->session()->put('authentication', $newSession->token);
return Response()->json(['message'=>'Success!', 'badInputs'=>[]]);
}
/**
* Logs the user in.
*
* @return Response
*/
public function Login(Request $request) {
if(AuthHelper::Guard($request))
return Response(null, 400);
/* */
$data = $request->all();
$valid = Validator::make(
$data,
[
'username' => ['required', 'string'],
'password' => ['required', 'string'],
]
);
if ($valid->stopOnFirstFailure()->fails()) {
$error = $valid->errors()->first();
$messages = $valid->messages()->get('*');
return Response()->json(['message'=>$error, 'badInputs'=>[array_keys($messages)]]);
}
/* */
$user = User::where('username', $request->input('username'))->first();
if (!$user)
return Response()->json(['message'=>'That user doesn\'t exist.', 'badInputs'=>['username']]);
if (!Hash::check($request->input('password'), $user->password))
return Response()->json(['message'=>'The password you tried is incorrect.', 'badInputs'=>['password']]);
$request->session()->regenerate();
$newSession = AuthHelper::GrantSession($request, $user->id);
$request->session()->put('authentication', $newSession->token);
return Response()->json(['message'=>'Success!', 'badInputs'=>[]]);
}
/**
* Logs the user out and kills the session.
*
* @return Response
*/
public function Logout(Request $request) {
if(!AuthHelper::Guard($request))
return Response(null, 400);
AuthHelper::RemoveSession($request);
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}

View File

@ -1,43 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use App\Models\Banner;
class BannerController extends Controller
{
/**
* Returns a JSON array of on-site banners.
*
* @return Response
*/
public function getBanners()
{
$redis = Cache::store('redis');
$content = '[{}]'; // fallback
if($bannerSettings = $redis->get('bannerSetting'))
{
$content = $bannerSettings;
}
else
{
$banners = Banner::select('type', 'message as text', 'dismissable')
->get();
$response = $banners->toJson();
$redis->put('bannerSetting', $response, now()->addMinutes(5));
$content = $response;
}
return response($content)
->header('Content-Type', 'application/json');
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Http\Controllers\Blog;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class BlogController extends Controller
{
public function home()
{
return view('blog.home');
}
}

View File

@ -1,289 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use App\Models\User\UserSession;
use App\Models\Post;
use App\Models\Reply;
use App\Models\Category;
use App\Models\Friend;
use App\Models\Feed;
use App\Models\Item;
use App\Models\Selling;
use App\Models\Inventory;
use App\Models\Staff;
use App\Models\Prices;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use App\Helpers\AuthHelper;
use Illuminate\Http\Request;
use Auth;
class CatalogController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
/*public function __construct()
{
$this->middleware('auth');
}*/
/**
* Show the application dashboard.
*
* @return \Illuminate\Contracts\Support\Renderable
*/
public function index()
{
return view('home');
}
public function buy(Request $request, $id) {
$user = AuthHelper::GetCurrentUser($request);
if (!$user) return Response()->json(['message'=>'System Error', 'badInputs'=>['title']]);
$item = Item::whereId($id)->first();
if (!$item) return Response()->json(['message'=>'No Item.', 'badInputs'=>['title']]);
if (!isset($_POST['decision'])) return Response()->json(['message'=>'System Error', 'badInputs'=>['title']]);
$decision = $_POST['decision'];
if ($item->current_price > $user->bank) return Response()->json(['message'=>"Sorry, you don't have enough currency!", 'badInputs'=>['title']]);
switch($decision) {
case 'nonLimited':
$newInventory = new Inventory;
$newInventory->item_id = $item->id;
$newInventory->owner_id = $user->id;
$newInventory->owner_type = 'App\Models\User';
$newInventory->status = 1;
$user->inventory()->save($newInventory);
$newInventory->uid = 'nonLimited';
$newInventory->save();
$user->decrement('bank', $item->current_price);
$user->save();
$replies = $item->sellingPrices()->orderBy('price', 'asc')->paginate(10);
$itemA = $item->toArray();
$itemA['creator'] = User::where('id', $item->creator_id)->first();
foreach ($replies as &$reply) {
$creator = User::where('id', $reply['seller_id'])->first();
if ($creator->id == $user->id) {$reply['isMeta'] = true;}else{$reply['isMeta'] = false;}
$reply['created_at'] = explode('T', $reply['created_at'])[0];
$reply['seller_name'] = $creator->username;
}
return Response()->json(['message'=>"Success!", 'badInputs'=>[], "item"=>$itemA, "sellingPrices"=>$replies]);
break;
case 'limited':
if ($item->stock <= 0) return Response()->json(['message'=>"Sorry, there's no more in stock for this item!", 'badInputs'=>['title']]);
$newInventory = new Inventory;
$newInventory->item_id = $item->id;
$newInventory->owner_id = $user->id;
$newInventory->owner_type = 'App\Models\User';
$newInventory->status = 1;
$user->inventory()->save($newInventory);
$newInventory->uid = $newInventory->id;
$newInventory->save();
$user->decrement('bank', $item->current_price);
$user->save();
$item->decrement('stock');
$item->save();
$replies = $item->sellingPrices()->orderBy('price', 'asc')->paginate(10);
$itemA = $item->toArray();
$itemA['creator'] = User::where('id', $item->creator_id)->first();
foreach ($replies as &$reply) {
$creator = User::where('id', $reply['seller_id'])->first();
if ($creator->id == $user->id) {$reply['isMeta'] = true;}else{$reply['isMeta'] = false;}
$reply['created_at'] = explode('T', $reply['created_at'])[0];
$reply['seller_name'] = $creator->username;
}
return Response()->json(['message'=>"Success!", 'badInputs'=>[], "item"=>$itemA, "sellingPrices"=>$replies]);
break;
case 'selling':
if (!isset($_POST['sellingId'])) return Response()->json(['message'=>'No Selling ID.', 'badInputs'=>['title']]);
$sellingId = $_POST['sellingId'];
$sellingItem = Selling::whereId($sellingId)->first();
if (!$sellingItem) return Response()->json(['message'=>"That selling item doesn't exist!", 'badInputs'=>['title']]);
if ($sellingItem->seller_id == $user->id) return Response()->json(['message'=>"Thats you!", 'badInputs'=>['title']]);
$seller = User::where('id', $sellingItem->seller_id)->first();
$ownedItem = Inventory::where('owner_id', $sellingItem->seller_id)->where('item_id', $item->id)->first();
if ($sellingItem->price > $user->bank) return Response()->json(['message'=>"Sorry, you don't have enough currency!", 'badInputs'=>['title']]);
$newInventory = new Inventory;
$newInventory->item_id = $item->id;
$newInventory->owner_id = $user->id;
$newInventory->owner_type = 'App\Models\User';
$newInventory->status = 1;
$user->inventory()->save($newInventory);
$newInventory->uid = $sellingItem->uid;
$newInventory->save();
$user->decrement('bank', $sellingItem->price);
$user->save();
$seller->increment('bank', $sellingItem->price);
$seller->save();
/*$priceNew = new Prices;
$priceNew->price = $sellingItem->price;
$item->prices()->save($priceNew);*/
$sellingItem->delete();
$ownedItem->delete();
$sellingItemNew = Selling::whereId($sellingId)->first();
if (count($item->sellingPrices) <= 0) {$item->current_price = null;$item->save();}else{$item->current_price = $sellingItemNew->price;$item->save();}
$replies = $item->sellingPrices()->orderBy('price', 'asc')->paginate(10);
$itemA = $item->toArray();
$itemA['creator'] = User::where('id', $item->creator_id)->first();
foreach ($replies as &$reply) {
$creator = User::where('id', $reply['seller_id'])->first();
if ($creator->id == $user->id) {$reply['isMeta'] = true;}else{$reply['isMeta'] = false;}
$reply['created_at'] = explode('T', $reply['created_at'])[0];
$reply['seller_name'] = $creator->username;
}
return Response()->json(['message'=>"Success!", 'badInputs'=>[], "item"=>$itemA, "sellingPrices"=>$replies]);
break;
default:
break;
}
}
public function sell(Request $request, $id) {
$user = AuthHelper::GetCurrentUser($request);
if (!$user) return Response()->json(['message'=>'System Error', 'badInputs'=>['title']]);
$item = Item::whereId($id)->first();
if (!$item) return Response()->json(['message'=>'System Error', 'badInputs'=>['title']]);
if (!$user->ownsItem($id)) return Response()->json(['message'=>"You don't own this item!", 'badInputs'=>['title']]);
$inventory = Inventory::where('item_id', $id)->where('owner_id', $user->id)->first();
$data = $request->all();
$valid = Validator::make($data, [
'price' => ['required', 'integer', 'min:1'],
]);
if ($valid->stopOnFirstFailure()->fails()) {
$error = $valid->errors()->first();
$messages = $valid->messages()->get('*');
return Response()->json(['message'=>$error, 'badInputs'=>[array_keys($messages)]]);
}
$selling = new Selling;
$selling->seller_id = $user->id;
$selling->uid = $inventory->uid;
$selling->price = $request->input('price');
$item->sellingPrices()->save($selling);
$sellingItemNew = Selling::where('item_id', $id)->first();
if ($item->sellingPrices()->count() <= 0) {$item->current_price = null;$item->save();}else{$item->current_price = $sellingItemNew->price;$item->save();}
$replies = $item->sellingPrices()->orderBy('price', 'asc')->paginate(10);
$itemA = $item->toArray();
$itemA['creator'] = User::where('id', $item->creator_id)->first();
foreach ($replies as &$reply) {
$creator = User::where('id', $reply['seller_id'])->first();
if ($creator->id == $user->id) {$reply['isMeta'] = true;}else{$reply['isMeta'] = false;}
$reply['created_at'] = explode('T', $reply['created_at'])[0];
$reply['seller_name'] = $creator->username;
}
return Response()->json(['message'=>"Success!", 'badInputs'=>[], "item"=>$itemA, "sellingPrices"=>$replies]);
}
public function removeSale(Request $request, $id) {
$user = AuthHelper::GetCurrentUser($request);
if (!$user) return Response()->json(['message'=>'System Error', 'badInputs'=>['title']]);
$sellingId = $id;
$sellingItem = Selling::whereId($sellingId)->first();
if (!$sellingItem) return Response()->json(['message'=>"That selling item doesn't exist!", 'badInputs'=>['title']]);
if ($sellingItem->seller_id != $user->id) return Response()->json(['message'=>"Thats not you!", 'badInputs'=>['title']]);
$item = Item::whereId($sellingItem->item_id)->first();
if (!$sellingItem) return Response()->json(['message'=>"That item doesn't exist!", 'badInputs'=>['title']]);
$sellingItem->delete();
if (count($item->sellingPrices) <= 0) {$item->current_price = null;$item->save();}else{$item->current_price = $sellingItemNew->price;$item->save();}
$replies = $item->sellingPrices()->orderBy('price', 'asc')->paginate(10);
$itemA = $item->toArray();
$itemA['creator'] = User::where('id', $item->creator_id)->first();
foreach ($replies as &$reply) {
$creator = User::where('id', $reply['seller_id'])->first();
if ($creator->id == $user->id) {$reply['isMeta'] = true;}else{$reply['isMeta'] = false;}
$reply['created_at'] = explode('T', $reply['created_at'])[0];
$reply['seller_name'] = $creator->username;
}
return Response()->json(['message'=>"Success!", 'badInputs'=>[], "item"=>$itemA, "sellingPrices"=>$replies]);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers\Cdn;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\Request;
use App\Models\CdnHash;
use App\Helpers\CdnHelper;
use App\Helpers\ValidationHelper;
use App\Http\Controllers\Controller;
class CdnController extends Controller
{
public function getContent(Request $request, $hash)
{
$disk = CdnHelper::GetDisk();
if(preg_match('/^[a-f0-9]{64}$/i', $hash) && $disk->exists($hash)) {
$content = CdnHash::where('hash', $hash)->first();
if(!$content || $content->deleted)
return response('This item is currently unavailable.')
->header('content-type', 'text/plain');
return response($disk->get($hash))
->header('content-type', $content->mime_type);
} else {
return response('Invalid hash.')
->header('content-type', 'text/plain');
}
}
}

View File

@ -5,202 +5,9 @@ namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use App\Models\User\UserSession;
use App\Models\Category;
use App\Models\Post;
use App\Models\Reply;
use App\Models\Staff;
use App\Models\CatalogCategory;
use App\Models\Friend;
use App\Models\Feed;
use App\Models\Item;
use App\Models\Inventory;
use App\Models\Selling;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use App\Helpers\AuthHelper;
use Illuminate\Routing\Controller as BaseController;
use Carbon;
use Auth;
use Illuminate\Http\Request;
use DateTime;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
public function fetchCategoriesFP(Request $request) {
$user = AuthHelper::GetCurrentUser($request);
if (!$user) {return Response()->json(["error"=>"No user."]);}
$staff = Staff::where('user_id', $user->id)->first();
if ($staff) {$categories = Category::get();}else{$categories = Category::where('staffOnly', '0')->get();}
return Response()->json(["categories"=>$categories]);
}
public function fetchCategories() {
$categories = Category::orderBy('staffOnly', 'desc')->get();
return Response()->json(["categories"=>$categories]);
}
public function fetchCategoriesCatalog(Request $request) {
$categories = CatalogCategory::get();
return Response()->json(["categories"=>$categories]);
}
public function fetchFeed(Request $request) {
$user = AuthHelper::GetCurrentUser($request);
if (!$user) {return Response()->json(["error"=>"No user."]);}
$friends = Friend::where('status', 1)->where('recieved_id', $user->id)->orWhere('sent_id', $user->id)->get()->toArray();
$actualFriends = [];
foreach ($friends as $friend) {
if ($friend['recieved_id'] == $user->id) {
array_push($actualFriends, $friend['sent_id']);
}else{
array_push($actualFriends, $friend['recieved_id']);
}
}
$feed = Feed::whereIn('user_id', $actualFriends)->orWhere('user_id', $user->id)->orderBy('created_at', 'desc')->paginate(15);
foreach ($feed as &$singleFeed) {
$creator = User::where('id', $singleFeed['user_id'])->first();
$singleFeed['creatorName'] = $creator->username;
}
return Response()->json(["data"=>$feed]);
}
public function fetchCategoryCatalog(Request $request, $id) {
$category = CatalogCategory::where('id', $id)->first();
if (!$category) {return Response()->json(false);}
$items = $category->items()->orderBy('updated_at', 'desc')->paginate(25);
foreach ($items as &$item) {
$item['creator'] = User::where('id', $item['creator_id'])->first();
}
return Response()->json(["data"=>$category, "items"=>$items]);
}
public function fetchCategory(Request $request, $id) {
$category = Category::where('id', $id)->first();
if (!$category) {return Response()->json(false);}
$posts = $category->posts()->orderBy('pinned', 'desc')->orderBy('updated_at', 'desc')->paginate(15);
foreach ($posts as &$post) {
$post['creator'] = User::where('id', $post['creator_id'])->first();
}
return Response()->json(["data"=>$category, "posts"=>$posts]);
}
public function fetchUser(Request $request, $id) {
$meta = AuthHelper::GetCurrentUser($request);
$user = User::where('id', $id)->first();
if (!$user) {return Response()->json('Error');}
$array = $user->toArray();
if ($meta && $meta->id == $array['id']) $array['isMeta'] = true; else $array['isMeta'] = false;
if ($meta && $meta->getFriends('pending', 'checkSent', $array['id'])) $array['isFriend'] = 'needToAccept'; elseif ($meta && array_intersect($meta->getFriends('pending', 'id', null), [$array['id']])) $array['isFriend'] = 'pending'; elseif ($meta && array_intersect($meta->getFriends('id', null, null), [$array['id']])) $array['isFriend'] = true; else $array['isFriend'] = false;
return Response()->json(["data"=>$array]);
}
public function fetchPost(Request $request, $id) {
$post = Post::where('id', $id)->first();
if (!$post) {return Response()->json(false);}
$postA = $post->toArray();
$realDate = explode('T', $postA['created_at'])[0];
$postA['created_at'] = $realDate;
$postA['creator'] = User::where('id', $postA['creator_id'])->first();
$replies = $post->replies()->orderBy('pinned', 'desc')->orderBy('created_at', 'asc')->paginate(10);
foreach ($replies as &$reply) {
$creator = User::where('id', $reply['creator_id'])->first();
$reply['created_at'] = explode('T', $reply['created_at'])[0];
$reply['creator_name'] = $creator->username;
}
return Response()->json(["post"=>$postA,"replies"=>$replies]);
}
public function fetchItem(Request $request, $id) {
$user = AuthHelper::GetCurrentUser($request);
$item = Item::where('id', $id)->first();
if (!$item) return Response()->json(false);
$itemA = $item->toArray();
$realDate = explode('T', $itemA['created_at'])[0];
$itemA['created_at'] = $realDate;
$itemA['creator'] = User::where('id', $item->creator_id)->first();
if ($user) {
$sellingItem = Selling::where('seller_id', $user->id)->first();
if ($sellingItem) {
$itemA['isSelling'] = true;
} else {
$itemA['isSelling'] = false;
}
} else {
$itemA['isSelling'] = false;
}
if ($user && $user->ownsItem($id)) {$itemA['ownsItem'] = true;}else{$itemA['ownsItem'] = false;}
$replies = $item->sellingPrices()->orderBy('price', 'asc')->paginate(10);
foreach ($replies as &$reply) {
$creator = User::where('id', $reply['seller_id'])->first();
if ($creator->id == $user->id) {$reply['isMeta'] = true;}else{$reply['isMeta'] = false;}
$reply['created_at'] = explode('T', $reply['created_at'])[0];
$reply['seller_name'] = $creator->username;
}
return Response()->json(["item"=>$itemA,"sellingPrices"=>$replies]);
}
}

View File

@ -1,37 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Games;
use App\Models\WebStatus;
class GamesController extends Controller
{
/**
* Returns if the games arbiter is operational or not.
*
* @return Response
*/
public function isAvailable()
{
$status = WebStatus::where('name', 'GamesArbiter')
->first();
if (!$status) return response()->json(['error' => false])
->header('Content-Type', 'application/json');
return response()->json(['available' => $status->operational])
->header('Content-Type', 'application/json');
}
public function validatePlaceJoin()
{
// todo: move to backend and make this actually return if the player is validated
// this is only here for testing
return response('true', null)
->header('Content-Type', 'text/plain');
}
}

View File

@ -1,32 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Grid\SoapService;
use Illuminate\Http\Request;
class GridTest extends Controller
{
/**
* @return Response
*/
public function generateThumbnail()
{
$testScript = <<<TestScript
settings()["Task Scheduler"].ThreadPoolConfig = Enum.ThreadPoolConfig.PerCore4;
game:GetService("ContentProvider"):SetThreadPool(16)
game:GetService("Stats"):SetReportUrl("http://api.gtoria.net/teststat")
game:GetService("ContentProvider"):SetBaseUrl("http://www.roblox.com/")
game:LoadWorld(23173663)
return game:GetService("ThumbnailGenerator"):Click("PNG", 1920, 1080, false, false)
TestScript;
$test = new SoapService('http://127.0.0.1:64989');
$result = $test->OpenJob(SoapService::MakeJobJSON('test', 10, 0, 0, 'test render', $testScript));
return response(base64_decode($result->OpenJobExResult->LuaValue[0]->value))
->header('Content-Type', 'image/png');
}
}

View File

@ -1,226 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use App\Models\User\UserSession;
use App\Models\Post;
use App\Models\Reply;
use App\Models\Category;
use App\Models\Friend;
use App\Models\Feed;
use App\Models\Staff;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use App\Helpers\AuthHelper;
use Illuminate\Http\Request;
use Auth;
class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
/*public function __construct()
{
$this->middleware('auth');
}*/
/**
* Show the application dashboard.
*
* @return \Illuminate\Contracts\Support\Renderable
*/
public function index()
{
return view('home');
}
public function createPost(Request $request) {
$data = $request->all();
$valid = Validator::make($data, [
'title' => ['required', 'string', 'min:3', 'max:38'],
'body' => ['required', 'string', 'min:3', 'max:380'],
'category' => ['required']
]);
if ($valid->stopOnFirstFailure()->fails()) {
$error = $valid->errors()->first();
$messages = $valid->messages()->get('*');
return Response()->json(['message'=>$error, 'badInputs'=>[array_keys($messages)]]);
}
$meta = AuthHelper::GetCurrentUser($request);
if (!$meta) {return Response()->json(['message'=>'System error', 'badInputs'=>['title']]);}
if (!isset($_POST['creator_id'])) {return Response()->json(['message'=>'System error', 'badInputs'=>['title']]);}
$user = User::where('id', $_POST['creator_id'])->first();
if (!$user) {return Response()->json(['message'=>'User not found!', 'badInputs'=>['title']]);}
if (!isset($_POST['category'])) {return Response()->json(['message'=>'Category not found!', 'badInputs'=>['category']]);}
$categoryId = $_POST['category'];
$category = Category::where('id', $categoryId)->first();
$staff = Staff::where('user_id', $user->id)->first();
if ($category->staffOnly == '1' && !$staff) {return Response()->json(['message'=>'You cant use that category.', 'badInputs'=>['category']]);}
$post = new Post;
$post->title = $_POST['title'];
$post->body = $_POST['body'];
$post->creator_id = $_POST['creator_id'];
$category->posts()->save($post);
return Response()->json(['message'=>'Success!', 'badInputs'=>[], 'post_id'=>$post->id]);
}
public function createFeed(Request $request) {
$data = $request->all();
$valid = Validator::make($data, [
'body' => ['required', 'string', 'min:3', 'max:245'],
]);
if ($valid->stopOnFirstFailure()->fails()) {
$error = $valid->errors()->first();
$messages = $valid->messages()->get('*');
return Response()->json(['message'=>$error, 'badInputs'=>[array_keys($messages)]]);
}
$user = AuthHelper::GetCurrentUser($request);
if (!$user) {return Response()->json(['message'=>'System error', 'badInputs'=>['title']]);}
$feed = new Feed;
$feed->user_id = $user->id;
$feed->body = $request->input('body');
$feed->save();
$friends = Friend::where('status', 1)->where('recieved_id', $user->id)->orWhere('sent_id', $user->id)->get()->toArray();
$actualFriends = [];
foreach ($friends as $friend) {
if ($friend['recieved_id'] == $user->id) {
array_push($actualFriends, $friend['sent_id']);
}else{
array_push($actualFriends, $friend['recieved_id']);
}
}
$newFeed = Feed::whereIn('user_id', $actualFriends)->orWhere('user_id', $user->id)->orderBy('created_at', 'desc')->paginate(15);
foreach ($newFeed as &$singleFeed) {
$creator = User::where('id', $singleFeed['user_id'])->first();
$singleFeed['creatorName'] = $creator->username;
}
return Response()->json(['message'=>'Success!', 'badInputs'=>[], "data"=>$newFeed]);
}
public function addFriend(Request $request, $id) {
$user = User::where('id', $id)->first();
if (!$user) {return Response()->json(['message'=>'No user.', 'badInputs'=>['title']]);}
$meta = AuthHelper::GetCurrentUser($request);
if (!$meta) {return Response()->json(['message'=>'System error.', 'badInputs'=>['title']]);}
if (!isset($_POST['decision'])) {return Response()->json(['message'=>'System error.', 'badInputs'=>['title']]);}
switch($_POST['decision']) {
case 'remove':
if ($meta && !array_intersect($meta->getFriends('id', null, null), [$user->id]))
return Response()->json(['message'=>'Not Friends.', 'badInputs'=>['title']]);
elseif ($meta && array_intersect($meta->getFriends('pending', 'id', null), [$user->id]))
return Response()->json(['message'=>'Already Pending.', 'badInputs'=>['title']]);
$friend = $meta->getFriends('remove', null, $user->id);
return Response()->json(['message'=>'Success!', 'badInputs'=>[], "data"=>false]);
break;
case 'accept':
if ($meta && array_intersect($meta->getFriends('id', null, null), [$user->id]))
return Response()->json(['message'=>'Already Friends.', 'badInputs'=>['title']]);
$friend = $meta->getFriends('accept', null, $user->id);
return Response()->json(['message'=>'Success!', 'badInputs'=>[], "data"=>true]);
break;
case 'add':
if ($meta && array_intersect($meta->getFriends('id', null, null), [$user->id]))
return Response()->json(['message'=>'Already Friends.', 'badInputs'=>['title']]);
elseif ($meta && array_intersect($meta->getFriends('pending', 'id', null), [$user->id]))
return Response()->json(['message'=>'Already Pending.', 'badInputs'=>['title']]);
$friend = new Friend;
$friend->sent_id = $meta->id;
$friend->recieved_id = $user->id;
$friend->status = 0;
$friend->save();
return Response()->json(['message'=>'Success!', 'badInputs'=>[], "data"=>'pending']);
break;
default:
break;
}
}
public function createReply(Request $request, $id) {
$data = $request->all();
$valid = Validator::make($data, [
'body' => ['required', 'string', 'min:3', 'max:380'],
]);
if ($valid->stopOnFirstFailure()->fails()) {
$error = $valid->errors()->first();
$messages = $valid->messages()->get('*');
return Response()->json(['message'=>$error, 'badInputs'=>[array_keys($messages)]]);
}
$meta = AuthHelper::GetCurrentUser($request);
if (!$meta) {return Response()->json(['message'=>'System error', 'badInputs'=>['title']]);}
if (!isset($_POST['creator_id'])) {return Response()->json(['message'=>'System error', 'badInputs'=>['title']]);}
$user = User::where('id', $_POST['creator_id'])->first();
if (!$user) {return Response()->json(['message'=>'User not found!', 'badInputs'=>['title']]);}
$post = Post::where('id', $id)->first();
if (!$post) {return Response()->json(['message'=>'Post not found!', 'badInputs'=>['body']]);}
if ($post->locked && $user->id != $meta->id) {return Response()->json(['message'=>'This post is locked!', 'badInputs'=>['body']]);}
$reply = new Reply;
$reply->body = $_POST['body'];
$reply->creator_id = $user->id;
$post->replies()->save($reply);
$post->touch();
return Response()->json(['message'=>'Success!', 'badInputs'=>[], 'post_id'=>$post->id]);
}
}

View File

@ -1,69 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Symfony\Component\HttpFoundation\Cookie;
use App\Models\WebsiteConfiguration;
class MaintenanceController extends Controller
{
/**
* Handles the maintenance bypass request.
*
* @return Response
*/
public function bypass(Request $request)
{
$password = $request->input('password');
$buttons = $request->input('buttons');
if($password && $buttons)
{
$mtconf = json_decode(WebsiteConfiguration::whereName('MaintenancePassword')->first()->value);
if($password == $mtconf->password)
{
$btns = array_slice($buttons, -count($mtconf->combination));
$data = json_decode(file_get_contents(storage_path('framework/down')), true);
if(isset($data['secret']) && $btns === $mtconf->combination)
{
$trustedHosts = explode(',', env('TRUSTED_HOSTS'));
$origin = parse_url($request->headers->get('origin'), PHP_URL_HOST);
$passCheck = false;
foreach($trustedHosts as &$host)
{
if(str_ends_with($origin, $host))
$passCheck = true;
}
$expiresAt = Carbon::now()->addHours(24);
$bypassCookie = new Cookie('gt_constraint', base64_encode(json_encode([
'expires_at' => $expiresAt->getTimestamp(),
'mac' => hash_hmac('SHA256', $expiresAt->getTimestamp(), $data['secret']),
])), $expiresAt);
if($passCheck)
$bypassCookie = $bypassCookie->withDomain('.' . $origin);
return response('')
->withCookie($bypassCookie);
}
}
return response('')
->setStatusCode(403);
}
else
{
return response('{"errors":[{"code":400,"message":"BadRequest"}]}')
->setStatusCode(400)
->header('Cache-Control', 'private')
->header('Content-Type', 'application/json; charset=utf-8');
}
}
}

View File

@ -1,130 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use App\Models\User\UserSession;
use App\Models\Post;
use App\Models\Reply;
use App\Models\Category;
use App\Models\Friend;
use App\Models\Feed;
use App\Models\Staff;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use App\Helpers\AuthHelper;
use Illuminate\Http\Request;
use Auth;
class SettingsController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
/*public function __construct()
{
$this->middleware('auth');
}*/
/**
* Show the application dashboard.
*
* @return \Illuminate\Contracts\Support\Renderable
*/
public function index()
{
return view('home');
}
public function settingsAbout(Request $request) {
$data = $request->all();
$valid = Validator::make($data, [
'body' => ['required', 'string', 'min:2', 'max:180'],
]);
if ($valid->stopOnFirstFailure()->fails()) {
$error = $valid->errors()->first();
$messages = $valid->messages()->get('*');
return Response()->json(['message'=>$error, 'badInputs'=>[array_keys($messages)]]);
}
$user = AuthHelper::GetCurrentUser($request);
$user->about = $_POST['body'];
$user->save();
return Response()->json(['message'=>'Success!', 'badInputs'=>[]]);
}
public function settingsPassword(Request $request) {
$data = $request->all();
$valid = Validator::make($data, [
'currentPassword' => ['required', 'string'],
'newPassword' => ['required', 'string', 'min:8'],
'checkNewPassword' => ['required', 'string'],
]);
if ($valid->stopOnFirstFailure()->fails()) {
$error = $valid->errors()->first();
$messages = $valid->messages()->get('*');
return Response()->json(['message'=>$error, 'badInputs'=>[array_keys($messages)]]);
}
$user = AuthHelper::GetCurrentUser($request);
if (!$user) return Response()->json(['message'=>'User not found!', 'badInputs'=>['title']]);
if (!Hash::check($request->input('currentPassword'), $user->password))
return Response()->json(['message'=>'Thats not the right password!', 'badInputs'=>['currentPassword']]);
if ($request->input('newPassword') != $request->input('checkNewPassword'))
return Response()->json(['message'=>'Those dont match!', 'badInputs'=>['checkNewPassword', 'newPassword']]);
$user->password = Hash::make($request->input('newPassword'));
$user->save();
return Response()->json(['message'=>'Success!', 'badInputs'=>[]]);
}
public function settingsEmail(Request $request) {
$data = $request->all();
$valid = Validator::make($data, [
'currentPassword' => ['required', 'string'],
'newEmail' => ['required', 'string', 'email', 'max:255'],
]);
if ($valid->stopOnFirstFailure()->fails()) {
$error = $valid->errors()->first();
$messages = $valid->messages()->get('*');
return Response()->json(['message'=>$error, 'badInputs'=>[array_keys($messages)]]);
}
$user = AuthHelper::GetCurrentUser($request);
if (!$user) return Response()->json(['message'=>'User not found!', 'badInputs'=>['title']]);
if (!Hash::check($request->input('currentPassword'), $user->password))
return Response()->json(['message'=>'Thats not the right password!', 'badInputs'=>['currentPassword']]);
$user->email = $request->input('newEmail');
$user->email_verified_at = null;
$user->save();
return Response()->json(['message'=>'Success!', 'badInputs'=>[]]);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\Setup;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\Controller;
use App\Models\DynamicWebConfiguration;
class SetupController extends Controller
{
public function getFile(Request $request, $file)
{
$file = basename($file);
$filePath = Storage::path('setup/' . $file);
if(!file_exists($filePath) || strtolower($file) == '.gitignore' || str_ends_with(strtolower($file), 'pdb.zip'))
return response('404 not found.', 404)
->header('Content-Type', 'text/plain');
return response()->file($filePath);
}
public function getClientVersion()
{
return response(DynamicWebConfiguration::where('name', 'ClientUploadVersion')->first()->value)
->header('Content-Type', 'text/plain');
}
public function getStudioVersion()
{
return response(DynamicWebConfiguration::where('name', 'StudioUploadVersion')->first()->value)
->header('Content-Type', 'text/plain');
}
}

View File

@ -1,33 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Helpers\AuthHelper;
class UserController extends Controller
{
/**
* Gets the current user's settings.
*
* @return Response
*/
public function GetSettings(Request $request) {
$currentUser = AuthHelper::GetCurrentUser($request);
if($currentUser) {
return Response()->json([
'data' => $currentUser
]);
} else {
return Response()->json([
'error' => 'Unauthorized',
'userFacingMessage' => 'You are not authorized to perform this request.'
]);
}
// Not sure how we'd get here, but just in case
return Response(null, 400);
}
}

View File

@ -0,0 +1,325 @@
<?php
namespace App\Http\Controllers\Web;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use App\Http\Controllers\Controller;
use App\Models\AdminUpload;
use App\Models\DynamicWebConfiguration;
use App\Models\PunishmentType;
use App\Models\Username;
use App\Models\User;
use App\Models\UserIp;
class AdminController extends Controller
{
function getJs(Request $request, string $jsFile)
{
$filePath = public_path('js/adm/' . basename($jsFile));
if(!file_exists($filePath))
abort(404);
return response()->file($filePath);
}
// Moderator+
// GET admin.dashboard
function dashboard()
{
return view('web.admin.dashboard');
}
// GET admin.useradmin
function userAdmin(Request $request)
{
$user = User::where('id', $request->get('ID'));
if(!$user->exists())
abort(400);
return view('web.admin.useradmin')->with('user', $user->first());
}
// GET admin.manualmoderateuser
function manualModerateUser(Request $request)
{
$user = User::where('id', $request->get('ID'));
if(!$user->exists())
abort(400);
return view('web.admin.manualmoderateuser')->with('user', $user->first());
}
// POST admin.manualmoderateusersubmit
function manualModerateUserSubmit(Request $request)
{
$validator = Validator::make($request->all(), [
'ID' => [
'required',
Rule::exists('App\Models\User', 'id')
],
'moderate-action' => [
'required',
Rule::exists('App\Models\PunishmentType', 'id')
],
'internal-note' => 'required'
], [
'moderate-action.required' => 'Please provide an account state.',
'internal-note.required' => 'An internal note must be provided on why this user\'s state was changed.'
]);
if($validator->fails())
return $this->manualModerateUserError($validator);
$user = User::where('id', $request->get('ID'))->first();
if(Auth::user()->id == $user->id)
{
$validator->errors()->add('ID', 'Cannot apply account state to current user.');
return $this->manualModerateUserError($validator);
}
if(
($user->hasRoleset('ProtectedUser') && !Auth::user()->hasRoleset('Owner'))
// XlXi: Prevent lower-ranks from banning higher ranks.
|| (
($user->hasRoleset('Owner') && !Auth::user()->hasRoleset('Owner'))
&& ($user->hasRoleset('Administrator') && !Auth::user()->hasRoleset('Administrator'))
)
)
{
$validator->errors()->add('ID', 'User is protected. Contact an owner.');
return $this->manualModerateUserError($validator);
}
// XlXi: Moderation action type 1 is None.
if($request->get('moderate-action') == 1 && !$user->hasActivePunishment())
return $this->manualModerateUserSuccess(sprintf('%s already has an account state of None. No changes applied.', $user->username));
if($request->get('moderate-action') != 1 && $user->hasActivePunishment())
{
$validator->errors()->add('ID', 'User already has an active punishment.');
return $this->manualModerateUserError($validator);
}
if(Auth::user()->hasRoleset('Administrator'))
{
if($request->has('scrub-username'))
{
$newUsername = sprintf('[ Content Deleted %d ]', $user->id);
Username::where('user_id', $user->id)
->update([
'scrubbed' => true,
'scrubbed_by' => Auth::user()->id
]);
Username::create([
'username' => $newUsername,
'user_id' => $user->id
]);
$user->username = $newUsername;
$user->save();
}
}
PunishmentType::where('id', $request->get('moderate-action'))
->first()
->applyToUser([
'user_id' => $user->id,
'user_note' => $request->get('user-note') ?: '',
'internal_note' => $request->get('internal-note') ?: '',
'moderator_id' => Auth::user()->id
]);
return $this->manualModerateUserSuccess(sprintf('Successfully applied account state to %s.', $user->username));
}
function manualModerateUserError($validator)
{
$user = User::where('id', request()->get('ID'))->first();
return view('web.admin.manualmoderateuser')
->with('user', $user)
->withErrors($validator);
}
function manualModerateUserSuccess($message)
{
$user = User::where('id', request()->get('ID'))->first();
return view('web.admin.manualmoderateuser')
->with('user', $user)
->with('success', $message);
}
// GET admin.usersearch
function userSearch(Request $request)
{
$types = [
'userid' => 'UserId',
'username' => 'UserName',
'emailaddress' => 'EmailAddress',
'ipaddress' => 'IpAddress'
];
foreach($types as $type => &$func)
{
if($type == $request->has($type))
return $this->{'userSearchQuery' . $func}($request);
}
return view('web.admin.usersearch');
}
// POST admin.usersearchquery
function userSearchQueryUserId(Request $request)
{
$users = User::where('id', $request->get('userid'))
->paginate(25)
->appends($request->all());
return view('web.admin.usersearch')->with('users', $users);
}
function userSearchQueryUserName(Request $request)
{
$users = User::where('username', 'like', '%' . $request->get('username') . '%')
->paginate(25)
->appends($request->all());
return view('web.admin.usersearch')->with('users', $users);
}
function userSearchQueryEmailAddress(Request $request)
{
if(!Auth::user()->hasRoleset('Owner'))
abort(403);
$users = User::where('email', $request->get('emailaddress'))
->paginate(25)
->appends($request->all());
return view('web.admin.usersearch')->with('users', $users);
}
function userSearchQueryIpAddress(Request $request)
{
if(!Auth::user()->hasRoleset('Owner'))
abort(403);
$users = UserIp::where('ipAddress', $request->get('ipaddress'))
->join('users', 'users.id', '=', 'user_ips.userId')
->orderBy('users.id', 'desc')
->paginate(25)
->appends($request->all());
return view('web.admin.usersearch')->with('users', $users)->with('isIpSearch', true);
}
// GET admin.userlookup
function userLookup()
{
return view('web.admin.userlookup');
}
// POST admin.userlookupquery
function userLookupQuery(Request $request)
{
$users = [];
foreach(preg_split('/\r\n|\r|\n/', $request->get('lookup')) as $username)
{
$user = User::where('username', $username);
if($user->exists())
{
$user = $user->first();
array_push(
$users,
[
'found' => true,
'user' => $user
]
);
}
else
{
array_push(
$users,
[
'found' => false,
'username' => $username
]
);
}
}
return view('web.admin.userlookup')->with('users', $users)->with('input', $request->get('lookup'));
}
// Admin+
// GET admin.autoupload
function autoUpload()
{
return view('web.admin.catalog.autoupload');
}
// GET admin.assetupload
function assetUpload()
{
return view('web.admin.catalog.assetupload');
}
// GET admin.adminuploads
function getAdminUploads(Request $request)
{
$uploads = AdminUpload::query()
->orderByDesc('id')
->paginate(25);
return view('web.admin.catalog.adminuploads')->with('uploads', $uploads);
}
function metricsVisualization()
{
return view('web.admin.metricsvisualization');
}
function arbiterDiag(Request $request, string $arbiterType = null)
{
return view('web.admin.arbiter.diag')->with([
'title' => sprintf('%s Arbiter Diag', $arbiterType),
'arbiter' => $arbiterType
]);
}
// Owner+
function arbiterManagement(Request $request, string $arbiterType = null, string $jobId = null)
{
return view('web.admin.arbiter.management')->with([
'title' => sprintf('%s Arbiter Management', $arbiterType),
'arbiter' => $arbiterType
]);
}
function configuration(Request $request)
{
return view('web.admin.configuration')->with([
'values' => DynamicWebConfiguration::get()
]);
}
function deployer()
{
return view('web.admin.deployer');
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Http\Requests\Auth\LoginRequest;
use App\Providers\RouteServiceProvider;
use App\Models\Session;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
class AuthenticatedSessionController extends Controller
{
/**
* Display the login view.
*
* @return \Illuminate\View\View
*/
public function index()
{
return view('web.auth.login');
}
/**
* Handle an incoming authentication request.
*
* @param \App\Http\Requests\Auth\LoginRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(LoginRequest $request)
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(RouteServiceProvider::HOME);
}
/**
* Destroy an authenticated session.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Request $request)
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use App\Http\Controllers\Controller;
class ConfirmablePasswordController extends Controller
{
/**
* Show the confirm password view.
*
* @return \Illuminate\View\View
*/
public function show()
{
return view('web.auth.confirm-password');
}
/**
* Confirm the user's password.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function store(Request $request)
{
if (! Auth::guard('web')->validate([
'email' => $request->user()->email,
'password' => $request->password,
])) {
throw ValidationException::withMessages([
'password' => __('auth.password'),
]);
}
$request->session()->put('auth.password_confirmed_at', time());
return redirect()->intended(RouteServiceProvider::HOME);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Models\Session;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class DoubleSessionBlockController extends Controller
{
public function index()
{
return response()
->view('web.auth.ddos_blocked', [], 403);
}
public function store()
{
request()->validate([
'g-recaptcha-response' => [new \App\Rules\GoogleRecaptcha]
]);
request()->session()->put('bypass-block-screen', true);
$returnUrl = request()->input('ReturnUrl');
if(!$returnUrl)
$returnUrl = '/';
return redirect(urldecode($returnUrl), 302);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class EmailVerificationNotificationController extends Controller
{
/**
* Send a new email verification notification.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(RouteServiceProvider::HOME);
}
$request->user()->sendEmailVerificationNotification();
return back()->with('status', 'verification-link-sent');
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class EmailVerificationPromptController extends Controller
{
/**
* Display the email verification prompt.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function __invoke(Request $request)
{
return $request->user()->hasVerifiedEmail()
? redirect()->intended(RouteServiceProvider::HOME)
: view('web.auth.verify-email');
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use App\Http\Controllers\Controller;
class NewPasswordController extends Controller
{
/**
* Display the password reset view.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
return view('web.auth.reset-password', ['request' => $request]);
}
/**
* Handle an incoming new password request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$request->validate([
'token' => ['required'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$status = Password::reset(
$request->only('password', 'password_confirmation', 'token'),
function ($user) use ($request) {
$user->forceFill([
'password' => Hash::make($request->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $status == Password::PASSWORD_RESET
? redirect()->route('auth.login.index')->with('status', __($status))
: back()->withErrors(['status' => __($status)]);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use App\Http\Controllers\Controller;
class PasswordResetLinkController extends Controller
{
/**
* Display the password reset link request view.
*
* @return \Illuminate\View\View
*/
public function index()
{
return view('web.auth.forgot-password');
}
/**
* Handle an incoming password reset link request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
]);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$status = Password::sendResetLink(
$request->only('email')
);
return $status == Password::RESET_LINK_SENT
? back()->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules;
use App\Http\Controllers\Controller;
use App\Models\AvatarAsset;
use App\Models\DefaultUserAsset;
use App\Models\UserAsset;
use App\Models\Username;
use App\Models\User;
use App\Providers\RouteServiceProvider;
class RegisteredUserController extends Controller
{
/**
* Display the registration view.
*
* @return \Illuminate\View\View
*/
public function index()
{
return view('web.auth.register');
}
/**
* Handle an incoming registration request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'username' => ['required', 'string', 'min:3', 'max:20', 'regex:/^[a-zA-Z0-9]+[ _.-]?[a-zA-Z0-9]+$/i', 'unique:usernames'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
], [
'username.min' => 'Username can only be 3 to 20 characters long.',
'username.max' => 'Username can only be 3 to 20 characters long.',
'username.regex' => 'Username must be alphanumeric and cannot begin or end with a special character. (a-z, 0-9, dots, hyphens, spaces, and underscores are allowed)'
]);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
$user = User::create([
'username' => $request->username,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
Username::create([
'username' => $user->username,
'user_id' => $user->id
]);
foreach(DefaultUserAsset::all() as $defaultAsset)
{
UserAsset::createSerialed($user->id, $defaultAsset->asset_id);
if($defaultAsset->wearing)
{
AvatarAsset::create([
'owner_id' => $user->id,
'asset_id' => $defaultAsset->asset_id
]);
}
}
$user->redraw();
event(new Registered($user));
Auth::login($user);
return redirect(RouteServiceProvider::HOME);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Providers\RouteServiceProvider;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use App\Http\Controllers\Controller;
class VerifyEmailController extends Controller
{
/**
* Mark the authenticated user's email address as verified.
*
* @param \Illuminate\Foundation\Auth\EmailVerificationRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function __invoke(EmailVerificationRequest $request)
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(RouteServiceProvider::HOME);
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect()->intended(RouteServiceProvider::HOME);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Http\Controllers\Web;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class AvatarController extends Controller
{
public function index()
{
return view('web.avatar.editor');
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers\Web;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use App\Helpers\GridHelper;
use App\Helpers\ValidationHelper;
use App\Http\Controllers\Controller;
use App\Models\AvatarAsset;
use App\Models\User;
class ClientAvatarController extends Controller
{
public function bodyColors(Request $request)
{
$validator = Validator::make($request->all(), [
'userId' => [
'required',
Rule::exists('App\Models\User', 'id')
]
]);
if($validator->fails()) {
return ValidationHelper::generateValidatorError($validator);
}
$valid = $validator->valid();
$user = User::where('id', $valid['userId'])->first();
if($user->hasActivePunishment() && $user->getPunishment()->isDeletion()) {
$validator->errors()->add('id', 'User is moderated.');
return ValidationHelper::generateValidatorError($validator);
}
$document = simplexml_load_string(GridHelper::getBodyColorsXML());
$bodyColors = $user->getBodyColors();
$document->xpath('//int[@name="HeadColor"]')[0][0] = $bodyColors->head;
$document->xpath('//int[@name="TorsoColor"]')[0][0] = $bodyColors->torso;
$document->xpath('//int[@name="LeftArmColor"]')[0][0] = $bodyColors->leftArm;
$document->xpath('//int[@name="LeftLegColor"]')[0][0] = $bodyColors->leftLeg;
$document->xpath('//int[@name="RightArmColor"]')[0][0] = $bodyColors->rightArm;
$document->xpath('//int[@name="RightLegColor"]')[0][0] = $bodyColors->rightLeg;
return response($document->asXML())
->header('Content-Type', 'application/xml');
}
public function characterFetch(Request $request)
{
$validator = Validator::make($request->all(), [
'userId' => [
'required',
Rule::exists('App\Models\User', 'id')
]
]);
if($validator->fails()) {
return ValidationHelper::generateValidatorError($validator);
}
$valid = $validator->valid();
$user = User::where('id', $valid['userId'])->first();
if($user->hasActivePunishment() && $user->getPunishment()->isDeletion()) {
$validator->errors()->add('id', 'User is moderated.');
return ValidationHelper::generateValidatorError($validator);
}
$charApp = '';
$charApp .= route('client.bodyColors', ['userId' => $user->id]);
foreach($user->getWearing()->get() as $avatarAsset)
{
$charApp .= ';' . route('client.asset', ['id' => $avatarAsset->asset->id]);
if($avatarAsset->asset->assetTypeId == 19) // Gear
$charApp .= '&equipped=1';
}
return response($charApp)
->header('Content-Type', 'text/plain');
}
}

View File

@ -0,0 +1,122 @@
<?php
namespace App\Http\Controllers\Web;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use App\Helpers\GridHelper;
use App\Helpers\ValidationHelper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\AssetVersion;
use App\Models\RobloxAsset;
use App\Models\UserAsset;
class ClientController extends Controller
{
function assetRegularValidator()
{
return [
'id' => [
'required',
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
return $query->where('moderated', false)
->where('approved', true);
})
],
'version' => [
'sometimes',
'numeric'
]
];
}
function assetVersionValidator()
{
return [
'assetversionid' => [
'required',
Rule::exists('App\Models\AssetVersion', 'id')
]
];
}
function userAssetValidator()
{
return [
'userassetid' => [
'required',
Rule::exists('App\Models\UserAsset', 'id')
]
];
}
function asset(Request $request)
{
$reqData = array_change_key_case($request->all());
$isVersionIdRequest = array_key_exists('assetversionid', $reqData);
$isUserAssetIdRequest = array_key_exists('userassetid', $reqData);
$validatorRuleSet = 'assetRegularValidator';
if($isVersionIdRequest)
$validatorRuleSet = 'assetVersionValidator';
elseif($isUserAssetIdRequest)
$validatorRuleSet = 'userAssetValidator';
$validator = Validator::make($reqData, $this->{$validatorRuleSet}());
if($validator->fails())
{
$rbxAsset = RobloxAsset::where('robloxAssetId', $request->get('id'))->first();
if($rbxAsset)
return redirect()->route('client.asset', ['id' => $rbxAsset->localAssetId]);
return redirect('https://assetdelivery.roblox.com/v1/asset?id=' . ($request->get('id') ?: 0));//return ValidationHelper::generateValidatorError($validator);
}
$valid = $validator->valid();
$asset = null;
if($isVersionIdRequest) {
$assetVersion = AssetVersion::where('id', $valid['assetversionid'])->first();
$asset = $assetVersion->asset;
$valid['version'] = $assetVersion->localVersion;
} elseif($isUserAssetIdRequest) {
$userAsset = UserAsset::where('id', $valid['userassetid'])->first();
$asset = $userAsset->asset;
} else {
$asset = Asset::where('id', $valid['id'])->first();
}
if(!$isVersionIdRequest && !array_key_exists('version', $valid))
$valid['version'] = 0;
if($asset == null) {
$validator->errors()->add('version', 'Unknown asset' . ($isVersionIdRequest ? ' version' : null) . '.');
return ValidationHelper::generateValidatorError($validator);
}
if(
!($asset->onSale || (Auth::check() && Auth::user()->id == $asset->creatorId)) // not on sale and not the creator
&&
!($asset->copyable()) // asset isn't defaulted to open source
&&
!GridHelper::hasAllAccess() // not grid
) {
$validator->errors()->add('id', 'You do not have access to this asset.');
return ValidationHelper::generateValidatorError($validator);
}
$contentHash = $asset->getContent($valid['version']);
if(!$contentHash) {
$validator->errors()->add('version', 'Unknown asset version.');
return ValidationHelper::generateValidatorError($validator);
}
return redirect(route('content', $contentHash));
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Http\Controllers\Web;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
// for future reference http://web.archive.org/web/20080531065005id_/roblox.com/Game/PlaceLauncher.asmx?WSDL
/*
PlaceLauncher Status Key
0: "" (Retry for client, no string for MadStatus)
1: "A server is loading the game..." (Retry for client)
2: "The server is ready. Joining the game..."
3: "Joining games is temporarily disabled while we upgrade. Please try again soon." (Displayed by MadStatus but results in an error for the client)
4: "An error occurred. Please try again later." (Displayed by MadStatus but results in an error for the client)
5: "The game you requested has ended." (Displayed by MadStatus but results in an error for the client)
6: "The game you requested is currently full. Waiting for an opening..."
7: "Roblox is updating. Please wait..." (Used by MadStatus)
8: "Requesting a server" (Displayed before a request is sent to PlaceLauncher.ashx)
Place join status results:
Waiting = 0
Loading = 1
Joining = 2
Disabled = 3
Error = 4
GameEnded = 5
GameFull = 6
UserLeft = 10
Restricted = 11
*/
class ClientGameController extends Controller
{
public function placeLauncher(Request $request)
{
return response([
'status' => 0,
'authenticationUrl' => '',
'authenticationTicket' => '',
'joinScriptUrl' => ''
]);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers\Web;
use App\Models\Asset;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class GamesController extends Controller
{
public function index()
{
return view('web.games.index');
}
public function showGame(Request $request, Asset $asset, string $assetName = null)
{
$assetSlug = Str::slug($asset->name, '-');
if($asset->moderated)
abort(404);
if($asset->assetTypeId != 9) // Place
return redirect()->route('shop.asset', ['asset' => $asset->id, 'assetName' => $assetSlug]);
if ($assetName != $assetSlug)
return redirect()->route('games.asset', ['asset' => $asset->id, 'assetName' => $assetSlug]);
return view('web.games.asset')->with([
'title' => sprintf('%s by %s', $asset->universe->name, $asset->user->username),
'asset' => $asset
]);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Web;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class HomeController extends Controller
{
public function landing()
{
return view('web.home.landing');
}
public function dashboard()
{
return view('web.home.dashboard');
}
}

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