Added support for login and providing the godot client webapp
							parent
							
								
									35101b5cec
								
							
						
					
					
						commit
						0bc36b69b9
					
				| @ -0,0 +1,151 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | declare(strict_types=1); | ||||||
|  | 
 | ||||||
|  | namespace DoctrineMigrations; | ||||||
|  | 
 | ||||||
|  | use Doctrine\DBAL\Schema\Schema; | ||||||
|  | use Doctrine\Migrations\AbstractMigration; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Auto-generated Migration: Please modify to your needs! | ||||||
|  |  */ | ||||||
|  | final class Version20240508185559 extends AbstractMigration | ||||||
|  | { | ||||||
|  |     public function getDescription(): string | ||||||
|  |     { | ||||||
|  |         return ''; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function up(Schema $schema): void | ||||||
|  |     { | ||||||
|  |         // this up() migration is auto-generated, please modify it to your needs | ||||||
|  |         $this->addSql('CREATE TABLE "character" (id UUID NOT NULL, dojo_id UUID DEFAULT NULL, name VARCHAR(64) NOT NULL, strength INT NOT NULL, constitution INT NOT NULL, agility INT NOT NULL, chi INT NOT NULL, PRIMARY KEY(id))'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_937AB03432F09E9C ON "character" (dojo_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN "character".id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN "character".dojo_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE character_technique (character_id UUID NOT NULL, technique_id UUID NOT NULL, PRIMARY KEY(character_id, technique_id))'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_506B3A7A1136BE75 ON character_technique (character_id)'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_506B3A7A1F8ACB26 ON character_technique (technique_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN character_technique.character_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN character_technique.technique_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE city (id UUID NOT NULL, dungeon_id UUID NOT NULL, PRIMARY KEY(id))'); | ||||||
|  |         $this->addSql('CREATE UNIQUE INDEX UNIQ_2D5B0234B606863 ON city (dungeon_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN city.id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN city.dungeon_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE country (id UUID NOT NULL, capital_id UUID NOT NULL, PRIMARY KEY(id))'); | ||||||
|  |         $this->addSql('CREATE UNIQUE INDEX UNIQ_5373C966FC2D9FF7 ON country (capital_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN country.id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN country.capital_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE dojo (id UUID NOT NULL, village_id UUID DEFAULT NULL, owner_id UUID NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); | ||||||
|  |         $this->addSql('CREATE UNIQUE INDEX UNIQ_9494CCB15E237E06 ON dojo (name)'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_9494CCB15E0D5582 ON dojo (village_id)'); | ||||||
|  |         $this->addSql('CREATE UNIQUE INDEX UNIQ_9494CCB17E3C61F9 ON dojo (owner_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN dojo.id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN dojo.village_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN dojo.owner_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE dungeon (id UUID NOT NULL, city_id UUID NOT NULL, PRIMARY KEY(id))'); | ||||||
|  |         $this->addSql('CREATE UNIQUE INDEX UNIQ_3FFA1F908BAC62AF ON dungeon (city_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN dungeon.id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN dungeon.city_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE fight (id UUID NOT NULL, winner_id UUID DEFAULT NULL, tournament_id UUID NOT NULL, start_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, events JSON NOT NULL, PRIMARY KEY(id))'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_21AA44565DFCD4B8 ON fight (winner_id)'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_21AA445633D1A3E7 ON fight (tournament_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN fight.id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN fight.winner_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN fight.tournament_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE fight_character (fight_id UUID NOT NULL, character_id UUID NOT NULL, PRIMARY KEY(fight_id, character_id))'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_BBD57C41AC6657E4 ON fight_character (fight_id)'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_BBD57C411136BE75 ON fight_character (character_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN fight_character.fight_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN fight_character.character_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE prefecture (id UUID NOT NULL, capital_id UUID NOT NULL, country_id UUID NOT NULL, PRIMARY KEY(id))'); | ||||||
|  |         $this->addSql('CREATE UNIQUE INDEX UNIQ_ABE6511AFC2D9FF7 ON prefecture (capital_id)'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_ABE6511AF92F3E70 ON prefecture (country_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN prefecture.id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN prefecture.capital_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN prefecture.country_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE technique (id UUID NOT NULL, prerequisite_id UUID DEFAULT NULL, name VARCHAR(255) NOT NULL, costs INT NOT NULL, damage VARCHAR(255) NOT NULL, energy VARCHAR(255) NOT NULL, accuracy VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); | ||||||
|  |         $this->addSql('CREATE UNIQUE INDEX UNIQ_D73B98415E237E06 ON technique (name)'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_D73B9841276AF86B ON technique (prerequisite_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN technique.id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN technique.prerequisite_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE tournament (id UUID NOT NULL, winner_id UUID DEFAULT NULL, name VARCHAR(255) NOT NULL, start_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_BD5FB8D95DFCD4B8 ON tournament (winner_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN tournament.id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN tournament.winner_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE tournament_character (tournament_id UUID NOT NULL, character_id UUID NOT NULL, PRIMARY KEY(tournament_id, character_id))'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_43FA182533D1A3E7 ON tournament_character (tournament_id)'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_43FA18251136BE75 ON tournament_character (character_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN tournament_character.tournament_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN tournament_character.character_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE "user" (id UUID NOT NULL, dojo_id UUID DEFAULT NULL, auth_name VARCHAR(32) NOT NULL, properties JSON NOT NULL, PRIMARY KEY(id))'); | ||||||
|  |         $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D64932F09E9C ON "user" (dojo_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN "user".id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN "user".dojo_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('CREATE TABLE village (id UUID NOT NULL, prefecture_id UUID NOT NULL, PRIMARY KEY(id))'); | ||||||
|  |         $this->addSql('CREATE INDEX IDX_4E6C7FAA9D39C865 ON village (prefecture_id)'); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN village.id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('COMMENT ON COLUMN village.prefecture_id IS \'(DC2Type:ulid)\''); | ||||||
|  |         $this->addSql('ALTER TABLE "character" ADD CONSTRAINT FK_937AB03432F09E9C FOREIGN KEY (dojo_id) REFERENCES dojo (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE character_technique ADD CONSTRAINT FK_506B3A7A1136BE75 FOREIGN KEY (character_id) REFERENCES "character" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE character_technique ADD CONSTRAINT FK_506B3A7A1F8ACB26 FOREIGN KEY (technique_id) REFERENCES technique (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE city ADD CONSTRAINT FK_2D5B0234B606863 FOREIGN KEY (dungeon_id) REFERENCES dungeon (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE country ADD CONSTRAINT FK_5373C966FC2D9FF7 FOREIGN KEY (capital_id) REFERENCES city (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE dojo ADD CONSTRAINT FK_9494CCB15E0D5582 FOREIGN KEY (village_id) REFERENCES village (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE dojo ADD CONSTRAINT FK_9494CCB17E3C61F9 FOREIGN KEY (owner_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE dungeon ADD CONSTRAINT FK_3FFA1F908BAC62AF FOREIGN KEY (city_id) REFERENCES city (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE fight ADD CONSTRAINT FK_21AA44565DFCD4B8 FOREIGN KEY (winner_id) REFERENCES "character" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE fight ADD CONSTRAINT FK_21AA445633D1A3E7 FOREIGN KEY (tournament_id) REFERENCES tournament (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE fight_character ADD CONSTRAINT FK_BBD57C41AC6657E4 FOREIGN KEY (fight_id) REFERENCES fight (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE fight_character ADD CONSTRAINT FK_BBD57C411136BE75 FOREIGN KEY (character_id) REFERENCES "character" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE prefecture ADD CONSTRAINT FK_ABE6511AFC2D9FF7 FOREIGN KEY (capital_id) REFERENCES city (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE prefecture ADD CONSTRAINT FK_ABE6511AF92F3E70 FOREIGN KEY (country_id) REFERENCES country (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE technique ADD CONSTRAINT FK_D73B9841276AF86B FOREIGN KEY (prerequisite_id) REFERENCES technique (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE tournament ADD CONSTRAINT FK_BD5FB8D95DFCD4B8 FOREIGN KEY (winner_id) REFERENCES "character" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE tournament_character ADD CONSTRAINT FK_43FA182533D1A3E7 FOREIGN KEY (tournament_id) REFERENCES tournament (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE tournament_character ADD CONSTRAINT FK_43FA18251136BE75 FOREIGN KEY (character_id) REFERENCES "character" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE "user" ADD CONSTRAINT FK_8D93D64932F09E9C FOREIGN KEY (dojo_id) REFERENCES dojo (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |         $this->addSql('ALTER TABLE village ADD CONSTRAINT FK_4E6C7FAA9D39C865 FOREIGN KEY (prefecture_id) REFERENCES prefecture (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function down(Schema $schema): void | ||||||
|  |     { | ||||||
|  |         // this down() migration is auto-generated, please modify it to your needs | ||||||
|  |         $this->addSql('CREATE SCHEMA public'); | ||||||
|  |         $this->addSql('ALTER TABLE "character" DROP CONSTRAINT FK_937AB03432F09E9C'); | ||||||
|  |         $this->addSql('ALTER TABLE character_technique DROP CONSTRAINT FK_506B3A7A1136BE75'); | ||||||
|  |         $this->addSql('ALTER TABLE character_technique DROP CONSTRAINT FK_506B3A7A1F8ACB26'); | ||||||
|  |         $this->addSql('ALTER TABLE city DROP CONSTRAINT FK_2D5B0234B606863'); | ||||||
|  |         $this->addSql('ALTER TABLE country DROP CONSTRAINT FK_5373C966FC2D9FF7'); | ||||||
|  |         $this->addSql('ALTER TABLE dojo DROP CONSTRAINT FK_9494CCB15E0D5582'); | ||||||
|  |         $this->addSql('ALTER TABLE dojo DROP CONSTRAINT FK_9494CCB17E3C61F9'); | ||||||
|  |         $this->addSql('ALTER TABLE dungeon DROP CONSTRAINT FK_3FFA1F908BAC62AF'); | ||||||
|  |         $this->addSql('ALTER TABLE fight DROP CONSTRAINT FK_21AA44565DFCD4B8'); | ||||||
|  |         $this->addSql('ALTER TABLE fight DROP CONSTRAINT FK_21AA445633D1A3E7'); | ||||||
|  |         $this->addSql('ALTER TABLE fight_character DROP CONSTRAINT FK_BBD57C41AC6657E4'); | ||||||
|  |         $this->addSql('ALTER TABLE fight_character DROP CONSTRAINT FK_BBD57C411136BE75'); | ||||||
|  |         $this->addSql('ALTER TABLE prefecture DROP CONSTRAINT FK_ABE6511AFC2D9FF7'); | ||||||
|  |         $this->addSql('ALTER TABLE prefecture DROP CONSTRAINT FK_ABE6511AF92F3E70'); | ||||||
|  |         $this->addSql('ALTER TABLE technique DROP CONSTRAINT FK_D73B9841276AF86B'); | ||||||
|  |         $this->addSql('ALTER TABLE tournament DROP CONSTRAINT FK_BD5FB8D95DFCD4B8'); | ||||||
|  |         $this->addSql('ALTER TABLE tournament_character DROP CONSTRAINT FK_43FA182533D1A3E7'); | ||||||
|  |         $this->addSql('ALTER TABLE tournament_character DROP CONSTRAINT FK_43FA18251136BE75'); | ||||||
|  |         $this->addSql('ALTER TABLE "user" DROP CONSTRAINT FK_8D93D64932F09E9C'); | ||||||
|  |         $this->addSql('ALTER TABLE village DROP CONSTRAINT FK_4E6C7FAA9D39C865'); | ||||||
|  |         $this->addSql('DROP TABLE "character"'); | ||||||
|  |         $this->addSql('DROP TABLE character_technique'); | ||||||
|  |         $this->addSql('DROP TABLE city'); | ||||||
|  |         $this->addSql('DROP TABLE country'); | ||||||
|  |         $this->addSql('DROP TABLE dojo'); | ||||||
|  |         $this->addSql('DROP TABLE dungeon'); | ||||||
|  |         $this->addSql('DROP TABLE fight'); | ||||||
|  |         $this->addSql('DROP TABLE fight_character'); | ||||||
|  |         $this->addSql('DROP TABLE prefecture'); | ||||||
|  |         $this->addSql('DROP TABLE technique'); | ||||||
|  |         $this->addSql('DROP TABLE tournament'); | ||||||
|  |         $this->addSql('DROP TABLE tournament_character'); | ||||||
|  |         $this->addSql('DROP TABLE "user"'); | ||||||
|  |         $this->addSql('DROP TABLE village'); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,75 @@ | |||||||
|  | # Use the front controller as index file. It serves as a fallback solution when | ||||||
|  | # every other rewrite/redirect fails (e.g. in an aliased environment without | ||||||
|  | # mod_rewrite). Additionally, this reduces the matching process for the | ||||||
|  | # start page (path "/") because otherwise Apache will apply the rewriting rules | ||||||
|  | # to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl). | ||||||
|  | DirectoryIndex index.php | ||||||
|  | 
 | ||||||
|  | # By default, Apache does not evaluate symbolic links if you did not enable this | ||||||
|  | # feature in your server configuration. Uncomment the following line if you | ||||||
|  | # install assets as symlinks or if you experience problems related to symlinks | ||||||
|  | # when compiling LESS/Sass/CoffeScript assets. | ||||||
|  | # Options +SymLinksIfOwnerMatch | ||||||
|  | 
 | ||||||
|  | # Disabling MultiViews prevents unwanted negotiation, e.g. "/index" should not resolve | ||||||
|  | # to the front controller "/index.php" but be rewritten to "/index.php/index". | ||||||
|  | <IfModule mod_negotiation.c> | ||||||
|  |     Options -MultiViews | ||||||
|  | </IfModule> | ||||||
|  | 
 | ||||||
|  | <IfModule mod_rewrite.c> | ||||||
|  |     # This Option needs to be enabled for RewriteRule, otherwise it will show an error like | ||||||
|  |     # 'Options FollowSymLinks or SymLinksIfOwnerMatch is off which implies that RewriteRule directive is forbidden' | ||||||
|  |     Options +SymLinksIfOwnerMatch | ||||||
|  | 
 | ||||||
|  |     RewriteEngine On | ||||||
|  | 
 | ||||||
|  |     # Determine the RewriteBase automatically and set it as environment variable. | ||||||
|  |     # If you are using Apache aliases to do mass virtual hosting or installed the | ||||||
|  |     # project in a subdirectory, the base path will be prepended to allow proper | ||||||
|  |     # resolution of the index.php file and to redirect to the correct URI. It will | ||||||
|  |     # work in environments without path prefix as well, providing a safe, one-size | ||||||
|  |     # fits all solution. But as you do not need it in this case, you can comment | ||||||
|  |     # the following 2 lines to eliminate the overhead. | ||||||
|  |     RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$ | ||||||
|  |     RewriteRule .* - [E=BASE:%1] | ||||||
|  | 
 | ||||||
|  |     # Sets the HTTP_AUTHORIZATION header removed by Apache | ||||||
|  |     RewriteCond %{HTTP:Authorization} .+ | ||||||
|  |     RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] | ||||||
|  | 
 | ||||||
|  |     # Redirect to URI without front controller to prevent duplicate content | ||||||
|  |     # (with and without `/index.php`). Only do this redirect on the initial | ||||||
|  |     # rewrite by Apache and not on subsequent cycles. Otherwise we would get an | ||||||
|  |     # endless redirect loop (request -> rewrite to front controller -> | ||||||
|  |     # redirect -> request -> ...). | ||||||
|  |     # So in case you get a "too many redirects" error or you always get redirected | ||||||
|  |     # to the start page because your Apache does not expose the REDIRECT_STATUS | ||||||
|  |     # environment variable, you have 2 choices: | ||||||
|  |     # - disable this feature by commenting the following 2 lines or | ||||||
|  |     # - use Apache >= 2.3.9 and replace all L flags by END flags and remove the | ||||||
|  |     #   following RewriteCond (best solution) | ||||||
|  |     RewriteCond %{ENV:REDIRECT_STATUS} ="" | ||||||
|  |     RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L] | ||||||
|  | 
 | ||||||
|  |     # If the requested filename exists, simply serve it. | ||||||
|  |     # We only want to let Apache serve files and not directories. | ||||||
|  |     # Rewrite all other queries to the front controller. | ||||||
|  |     RewriteCond %{REQUEST_FILENAME} !-f | ||||||
|  |     RewriteRule ^ %{ENV:BASE}/index.php [L] | ||||||
|  | </IfModule> | ||||||
|  | 
 | ||||||
|  | <IfModule !mod_rewrite.c> | ||||||
|  |     <IfModule mod_alias.c> | ||||||
|  |         # When mod_rewrite is not available, we instruct a temporary redirect of | ||||||
|  |         # the start page to the front controller explicitly so that the website | ||||||
|  |         # and the generated links can still be used. | ||||||
|  |         RedirectMatch 307 ^/$ /index.php/ | ||||||
|  |         # RedirectTemp cannot be used instead | ||||||
|  |     </IfModule> | ||||||
|  | </IfModule> | ||||||
|  | 
 | ||||||
|  | Header set Cross-Origin-Embedder-Policy "require-corp" | ||||||
|  | Header set Cross-Origin-Opener-Policy "same-origin" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
											
												Binary file not shown.
											
										
									
								| After Width: | Height: | Size: 12 KiB | 
| @ -0,0 +1,213 @@ | |||||||
|  | /**************************************************************************/ | ||||||
|  | /*  audio.worklet.js                                                      */ | ||||||
|  | /**************************************************************************/ | ||||||
|  | /*                         This file is part of:                          */ | ||||||
|  | /*                             GODOT ENGINE                               */ | ||||||
|  | /*                        https://godotengine.org                         */ | ||||||
|  | /**************************************************************************/ | ||||||
|  | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ | ||||||
|  | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */ | ||||||
|  | /*                                                                        */ | ||||||
|  | /* Permission is hereby granted, free of charge, to any person obtaining  */ | ||||||
|  | /* a copy of this software and associated documentation files (the        */ | ||||||
|  | /* "Software"), to deal in the Software without restriction, including    */ | ||||||
|  | /* without limitation the rights to use, copy, modify, merge, publish,    */ | ||||||
|  | /* distribute, sublicense, and/or sell copies of the Software, and to     */ | ||||||
|  | /* permit persons to whom the Software is furnished to do so, subject to  */ | ||||||
|  | /* the following conditions:                                              */ | ||||||
|  | /*                                                                        */ | ||||||
|  | /* The above copyright notice and this permission notice shall be         */ | ||||||
|  | /* included in all copies or substantial portions of the Software.        */ | ||||||
|  | /*                                                                        */ | ||||||
|  | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */ | ||||||
|  | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */ | ||||||
|  | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ | ||||||
|  | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */ | ||||||
|  | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */ | ||||||
|  | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */ | ||||||
|  | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */ | ||||||
|  | /**************************************************************************/ | ||||||
|  | 
 | ||||||
|  | class RingBuffer { | ||||||
|  | 	constructor(p_buffer, p_state, p_threads) { | ||||||
|  | 		this.buffer = p_buffer; | ||||||
|  | 		this.avail = p_state; | ||||||
|  | 		this.threads = p_threads; | ||||||
|  | 		this.rpos = 0; | ||||||
|  | 		this.wpos = 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	data_left() { | ||||||
|  | 		return this.threads ? Atomics.load(this.avail, 0) : this.avail; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	space_left() { | ||||||
|  | 		return this.buffer.length - this.data_left(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	read(output) { | ||||||
|  | 		const size = this.buffer.length; | ||||||
|  | 		let from = 0; | ||||||
|  | 		let to_write = output.length; | ||||||
|  | 		if (this.rpos + to_write > size) { | ||||||
|  | 			const high = size - this.rpos; | ||||||
|  | 			output.set(this.buffer.subarray(this.rpos, size)); | ||||||
|  | 			from = high; | ||||||
|  | 			to_write -= high; | ||||||
|  | 			this.rpos = 0; | ||||||
|  | 		} | ||||||
|  | 		if (to_write) { | ||||||
|  | 			output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from); | ||||||
|  | 		} | ||||||
|  | 		this.rpos += to_write; | ||||||
|  | 		if (this.threads) { | ||||||
|  | 			Atomics.add(this.avail, 0, -output.length); | ||||||
|  | 			Atomics.notify(this.avail, 0); | ||||||
|  | 		} else { | ||||||
|  | 			this.avail -= output.length; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	write(p_buffer) { | ||||||
|  | 		const to_write = p_buffer.length; | ||||||
|  | 		const mw = this.buffer.length - this.wpos; | ||||||
|  | 		if (mw >= to_write) { | ||||||
|  | 			this.buffer.set(p_buffer, this.wpos); | ||||||
|  | 			this.wpos += to_write; | ||||||
|  | 			if (mw === to_write) { | ||||||
|  | 				this.wpos = 0; | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			const high = p_buffer.subarray(0, mw); | ||||||
|  | 			const low = p_buffer.subarray(mw); | ||||||
|  | 			this.buffer.set(high, this.wpos); | ||||||
|  | 			this.buffer.set(low); | ||||||
|  | 			this.wpos = low.length; | ||||||
|  | 		} | ||||||
|  | 		if (this.threads) { | ||||||
|  | 			Atomics.add(this.avail, 0, to_write); | ||||||
|  | 			Atomics.notify(this.avail, 0); | ||||||
|  | 		} else { | ||||||
|  | 			this.avail += to_write; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class GodotProcessor extends AudioWorkletProcessor { | ||||||
|  | 	constructor() { | ||||||
|  | 		super(); | ||||||
|  | 		this.threads = false; | ||||||
|  | 		this.running = true; | ||||||
|  | 		this.lock = null; | ||||||
|  | 		this.notifier = null; | ||||||
|  | 		this.output = null; | ||||||
|  | 		this.output_buffer = new Float32Array(); | ||||||
|  | 		this.input = null; | ||||||
|  | 		this.input_buffer = new Float32Array(); | ||||||
|  | 		this.port.onmessage = (event) => { | ||||||
|  | 			const cmd = event.data['cmd']; | ||||||
|  | 			const data = event.data['data']; | ||||||
|  | 			this.parse_message(cmd, data); | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	process_notify() { | ||||||
|  | 		if (this.notifier) { | ||||||
|  | 			Atomics.add(this.notifier, 0, 1); | ||||||
|  | 			Atomics.notify(this.notifier, 0); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	parse_message(p_cmd, p_data) { | ||||||
|  | 		if (p_cmd === 'start' && p_data) { | ||||||
|  | 			const state = p_data[0]; | ||||||
|  | 			let idx = 0; | ||||||
|  | 			this.threads = true; | ||||||
|  | 			this.lock = state.subarray(idx, ++idx); | ||||||
|  | 			this.notifier = state.subarray(idx, ++idx); | ||||||
|  | 			const avail_in = state.subarray(idx, ++idx); | ||||||
|  | 			const avail_out = state.subarray(idx, ++idx); | ||||||
|  | 			this.input = new RingBuffer(p_data[1], avail_in, true); | ||||||
|  | 			this.output = new RingBuffer(p_data[2], avail_out, true); | ||||||
|  | 		} else if (p_cmd === 'stop') { | ||||||
|  | 			this.running = false; | ||||||
|  | 			this.output = null; | ||||||
|  | 			this.input = null; | ||||||
|  | 			this.lock = null; | ||||||
|  | 			this.notifier = null; | ||||||
|  | 		} else if (p_cmd === 'start_nothreads') { | ||||||
|  | 			this.output = new RingBuffer(p_data[0], p_data[0].length, false); | ||||||
|  | 		} else if (p_cmd === 'chunk') { | ||||||
|  | 			this.output.write(p_data); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	static array_has_data(arr) { | ||||||
|  | 		return arr.length && arr[0].length && arr[0][0].length; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	process(inputs, outputs, parameters) { | ||||||
|  | 		if (!this.running) { | ||||||
|  | 			return false; // Stop processing.
 | ||||||
|  | 		} | ||||||
|  | 		if (this.output === null) { | ||||||
|  | 			return true; // Not ready yet, keep processing.
 | ||||||
|  | 		} | ||||||
|  | 		const process_input = GodotProcessor.array_has_data(inputs); | ||||||
|  | 		if (process_input) { | ||||||
|  | 			const input = inputs[0]; | ||||||
|  | 			const chunk = input[0].length * input.length; | ||||||
|  | 			if (this.input_buffer.length !== chunk) { | ||||||
|  | 				this.input_buffer = new Float32Array(chunk); | ||||||
|  | 			} | ||||||
|  | 			if (!this.threads) { | ||||||
|  | 				GodotProcessor.write_input(this.input_buffer, input); | ||||||
|  | 				this.port.postMessage({ 'cmd': 'input', 'data': this.input_buffer }); | ||||||
|  | 			} else if (this.input.space_left() >= chunk) { | ||||||
|  | 				GodotProcessor.write_input(this.input_buffer, input); | ||||||
|  | 				this.input.write(this.input_buffer); | ||||||
|  | 			} else { | ||||||
|  | 				this.port.postMessage('Input buffer is full! Skipping input frame.'); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		const process_output = GodotProcessor.array_has_data(outputs); | ||||||
|  | 		if (process_output) { | ||||||
|  | 			const output = outputs[0]; | ||||||
|  | 			const chunk = output[0].length * output.length; | ||||||
|  | 			if (this.output_buffer.length !== chunk) { | ||||||
|  | 				this.output_buffer = new Float32Array(chunk); | ||||||
|  | 			} | ||||||
|  | 			if (this.output.data_left() >= chunk) { | ||||||
|  | 				this.output.read(this.output_buffer); | ||||||
|  | 				GodotProcessor.write_output(output, this.output_buffer); | ||||||
|  | 				if (!this.threads) { | ||||||
|  | 					this.port.postMessage({ 'cmd': 'read', 'data': chunk }); | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		this.process_notify(); | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	static write_output(dest, source) { | ||||||
|  | 		const channels = dest.length; | ||||||
|  | 		for (let ch = 0; ch < channels; ch++) { | ||||||
|  | 			for (let sample = 0; sample < dest[ch].length; sample++) { | ||||||
|  | 				dest[ch][sample] = source[sample * channels + ch]; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	static write_input(dest, source) { | ||||||
|  | 		const channels = source.length; | ||||||
|  | 		for (let ch = 0; ch < channels; ch++) { | ||||||
|  | 			for (let sample = 0; sample < source[ch].length; sample++) { | ||||||
|  | 				dest[sample * channels + ch] = source[ch][sample]; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | registerProcessor('godot-processor', GodotProcessor); | ||||||
											
												Binary file not shown.
											
										
									
								| After Width: | Height: | Size: 5.5 KiB | 
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								
											
												Binary file not shown.
											
										
									
								| After Width: | Height: | Size: 316 KiB | 
| @ -0,0 +1,161 @@ | |||||||
|  | /** | ||||||
|  |  * @license | ||||||
|  |  * Copyright 2015 The Emscripten Authors | ||||||
|  |  * SPDX-License-Identifier: MIT | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | // Pthread Web Worker startup routine:
 | ||||||
|  | // This is the entry point file that is loaded first by each Web Worker
 | ||||||
|  | // that executes pthreads on the Emscripten application.
 | ||||||
|  | 
 | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var Module = {}; | ||||||
|  | 
 | ||||||
|  | // Thread-local guard variable for one-time init of the JS state
 | ||||||
|  | var initializedJS = false; | ||||||
|  | 
 | ||||||
|  | function assert(condition, text) { | ||||||
|  |   if (!condition) abort('Assertion failed: ' + text); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function threadPrintErr() { | ||||||
|  |   var text = Array.prototype.slice.call(arguments).join(' '); | ||||||
|  |   console.error(text); | ||||||
|  | } | ||||||
|  | function threadAlert() { | ||||||
|  |   var text = Array.prototype.slice.call(arguments).join(' '); | ||||||
|  |   postMessage({cmd: 'alert', text: text, threadId: Module['_pthread_self']()}); | ||||||
|  | } | ||||||
|  | // We don't need out() for now, but may need to add it if we want to use it
 | ||||||
|  | // here. Or, if this code all moves into the main JS, that problem will go
 | ||||||
|  | // away. (For now, adding it here increases code size for no benefit.)
 | ||||||
|  | var out = () => { throw 'out() is not defined in worker.js.'; } | ||||||
|  | var err = threadPrintErr; | ||||||
|  | self.alert = threadAlert; | ||||||
|  | 
 | ||||||
|  | Module['instantiateWasm'] = (info, receiveInstance) => { | ||||||
|  |   // Instantiate from the module posted from the main thread.
 | ||||||
|  |   // We can just use sync instantiation in the worker.
 | ||||||
|  |   var module = Module['wasmModule']; | ||||||
|  |   // We don't need the module anymore; new threads will be spawned from the main thread.
 | ||||||
|  |   Module['wasmModule'] = null; | ||||||
|  |   var instance = new WebAssembly.Instance(module, info); | ||||||
|  |   // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193,
 | ||||||
|  |   // the above line no longer optimizes out down to the following line.
 | ||||||
|  |   // When the regression is fixed, we can remove this if/else.
 | ||||||
|  |   return receiveInstance(instance); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Turn unhandled rejected promises into errors so that the main thread will be
 | ||||||
|  | // notified about them.
 | ||||||
|  | self.onunhandledrejection = (e) => { | ||||||
|  |   throw e.reason ?? e; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function handleMessage(e) { | ||||||
|  |   try { | ||||||
|  |     if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code.
 | ||||||
|  | 
 | ||||||
|  |     // Until we initialize the runtime, queue up any further incoming messages.
 | ||||||
|  |     let messageQueue = []; | ||||||
|  |     self.onmessage = (e) => messageQueue.push(e); | ||||||
|  | 
 | ||||||
|  |     // And add a callback for when the runtime is initialized.
 | ||||||
|  |     self.startWorker = (instance) => { | ||||||
|  |       Module = instance; | ||||||
|  |       // Notify the main thread that this thread has loaded.
 | ||||||
|  |       postMessage({ 'cmd': 'loaded' }); | ||||||
|  |       // Process any messages that were queued before the thread was ready.
 | ||||||
|  |       for (let msg of messageQueue) { | ||||||
|  |         handleMessage(msg); | ||||||
|  |       } | ||||||
|  |       // Restore the real message handler.
 | ||||||
|  |       self.onmessage = handleMessage; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |       // Module and memory were sent from main thread
 | ||||||
|  |       Module['wasmModule'] = e.data.wasmModule; | ||||||
|  | 
 | ||||||
|  |       // Use `const` here to ensure that the variable is scoped only to
 | ||||||
|  |       // that iteration, allowing safe reference from a closure.
 | ||||||
|  |       for (const handler of e.data.handlers) { | ||||||
|  |         Module[handler] = function() { | ||||||
|  |           postMessage({ cmd: 'callHandler', handler, args: [...arguments] }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       Module['wasmMemory'] = e.data.wasmMemory; | ||||||
|  | 
 | ||||||
|  |       Module['buffer'] = Module['wasmMemory'].buffer; | ||||||
|  | 
 | ||||||
|  |       Module['workerID'] = e.data.workerID; | ||||||
|  | 
 | ||||||
|  |       Module['ENVIRONMENT_IS_PTHREAD'] = true; | ||||||
|  | 
 | ||||||
|  |       if (typeof e.data.urlOrBlob == 'string') { | ||||||
|  |         importScripts(e.data.urlOrBlob); | ||||||
|  |       } else { | ||||||
|  |         var objectUrl = URL.createObjectURL(e.data.urlOrBlob); | ||||||
|  |         importScripts(objectUrl); | ||||||
|  |         URL.revokeObjectURL(objectUrl); | ||||||
|  |       } | ||||||
|  |       Godot(Module); | ||||||
|  |     } else if (e.data.cmd === 'run') { | ||||||
|  |       // Pass the thread address to wasm to store it for fast access.
 | ||||||
|  |       Module['__emscripten_thread_init'](e.data.pthread_ptr, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0, /*canBlock=*/1); | ||||||
|  | 
 | ||||||
|  |       // Await mailbox notifications with `Atomics.waitAsync` so we can start
 | ||||||
|  |       // using the fast `Atomics.notify` notification path.
 | ||||||
|  |       Module['__emscripten_thread_mailbox_await'](e.data.pthread_ptr); | ||||||
|  | 
 | ||||||
|  |       assert(e.data.pthread_ptr); | ||||||
|  |       // Also call inside JS module to set up the stack frame for this pthread in JS module scope
 | ||||||
|  |       Module['establishStackSpace'](); | ||||||
|  |       Module['PThread'].receiveObjectTransfer(e.data); | ||||||
|  |       Module['PThread'].threadInitTLS(); | ||||||
|  | 
 | ||||||
|  |       if (!initializedJS) { | ||||||
|  |         initializedJS = true; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       try { | ||||||
|  |         Module['invokeEntryPoint'](e.data.start_routine, e.data.arg); | ||||||
|  |       } catch(ex) { | ||||||
|  |         if (ex != 'unwind') { | ||||||
|  |           // The pthread "crashed".  Do not call `_emscripten_thread_exit` (which
 | ||||||
|  |           // would make this thread joinable).  Instead, re-throw the exception
 | ||||||
|  |           // and let the top level handler propagate it back to the main thread.
 | ||||||
|  |           throw ex; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } else if (e.data.cmd === 'cancel') { // Main thread is asking for a pthread_cancel() on this thread.
 | ||||||
|  |       if (Module['_pthread_self']()) { | ||||||
|  |         Module['__emscripten_thread_exit'](-1); | ||||||
|  |       } | ||||||
|  |     } else if (e.data.target === 'setimmediate') { | ||||||
|  |       // no-op
 | ||||||
|  |     } else if (e.data.cmd === 'checkMailbox') { | ||||||
|  |       if (initializedJS) { | ||||||
|  |         Module['checkMailbox'](); | ||||||
|  |       } | ||||||
|  |     } else if (e.data.cmd) { | ||||||
|  |       // The received message looks like something that should be handled by this message
 | ||||||
|  |       // handler, (since there is a e.data.cmd field present), but is not one of the
 | ||||||
|  |       // recognized commands:
 | ||||||
|  |       err('worker.js received unknown command ' + e.data.cmd); | ||||||
|  |       err(e.data); | ||||||
|  |     } | ||||||
|  |   } catch(ex) { | ||||||
|  |     err('worker.js onmessage() captured an uncaught exception: ' + ex); | ||||||
|  |     if (ex && ex.stack) err(ex.stack); | ||||||
|  |     if (Module['__emscripten_thread_crashed']) { | ||||||
|  |       Module['__emscripten_thread_crashed'](); | ||||||
|  |     } | ||||||
|  |     throw ex; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | self.onmessage = handleMessage; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @ -0,0 +1,12 @@ | |||||||
|  | 
 | ||||||
|  | from php:8.2-apache | ||||||
|  | 
 | ||||||
|  | RUN apt update && \ | ||||||
|  |     apt install -y postgresql-server-dev-15 && \ | ||||||
|  |     docker-php-ext-install pdo_pgsql && \ | ||||||
|  |     a2enmod rewrite headers | ||||||
|  | 
 | ||||||
|  | RUN rm -rf /var/www/html && \ | ||||||
|  |     ln -sf /app/public /var/www/html && \ | ||||||
|  |     sed -i 's/80/8000/' /etc/apache2/ports.conf && \ | ||||||
|  |     sed -i 's/80/8000/' /etc/apache2/sites-enabled/000-default.conf | ||||||
| @ -0,0 +1,39 @@ | |||||||
|  | <?php | ||||||
|  | namespace App\Command; | ||||||
|  | 
 | ||||||
|  | use App\Security\AccessTokenHandler; | ||||||
|  | use Symfony\Component\Console\Attribute\AsCommand; | ||||||
|  | use Symfony\Component\Console\Command\Command; | ||||||
|  | use Symfony\Component\Console\Input\InputArgument; | ||||||
|  | use Symfony\Component\Console\Input\InputInterface; | ||||||
|  | use Symfony\Component\Console\Output\OutputInterface; | ||||||
|  | use Symfony\Component\Console\Style\SymfonyStyle; | ||||||
|  | 
 | ||||||
|  | #[AsCommand(name: 'app:login-url', description: 'Add a short description for your command')] | ||||||
|  | class LoginUrlCommand extends Command | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     public function __construct(private AccessTokenHandler $handler) | ||||||
|  |     { | ||||||
|  |         parent::__construct(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function configure(): void | ||||||
|  |     { | ||||||
|  |         $this->addArgument('auth_name', InputArgument::REQUIRED, 'Authentication name to use.'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function execute(InputInterface $input, OutputInterface $output): int | ||||||
|  |     { | ||||||
|  |         $io = new SymfonyStyle($input, $output); | ||||||
|  |         $auth_name = $input->getArgument('auth_name'); | ||||||
|  | 
 | ||||||
|  |         $msg = $this->handler->encodeMessage($auth_name, "AUTH_LOGIN_SEED"); | ||||||
|  | 
 | ||||||
|  |         $io->writeln('URL: <href=http://localhost:8000/login/' . $msg . '>/login</>'); | ||||||
|  | 
 | ||||||
|  |         $io->success(""); | ||||||
|  | 
 | ||||||
|  |         return Command::SUCCESS; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,31 @@ | |||||||
|  | <?php | ||||||
|  | namespace App\Controller; | ||||||
|  | 
 | ||||||
|  | use App\Security\AccessTokenHandler; | ||||||
|  | use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | ||||||
|  | use Symfony\Component\HttpFoundation\Response; | ||||||
|  | use Symfony\Component\Routing\Annotation\Route; | ||||||
|  | 
 | ||||||
|  | class ClientLoaderController extends AbstractController | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     public function __construct(private AccessTokenHandler $handler) | ||||||
|  |     {} | ||||||
|  | 
 | ||||||
|  |     #[Route('/login/{slug}', name: 'app_client_loader')] | ||||||
|  |     public function index(string $slug): Response | ||||||
|  |     { | ||||||
|  |         $authName = $this->handler->parseMessage($slug, "AUTH_LOGIN_SEED", "5 min"); | ||||||
|  | 
 | ||||||
|  |         $size_pck = filesize("godot/index.pck"); | ||||||
|  |         $size_wasm = filesize("godot/index.wasm"); | ||||||
|  | 
 | ||||||
|  |         return $this->render('client_loader/index.html.twig', | ||||||
|  |             [ | ||||||
|  |                 'controller_name' => $authName, | ||||||
|  |                 'token' => $this->handler->encodeMessage($authName, 'AUTH_SEED'), | ||||||
|  |                 'size_wasm' => $size_wasm, | ||||||
|  |                 'size_pck' => $size_pck | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,7 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
|  | if ! podman inspect ag-server:latest >/dev/null 2>&1; then | ||||||
|  |     podman build -f server.Dockerfile -t ag-server:latest | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | podman run --net=host -v $(pwd):/app -it --rm ag-server:latest | ||||||
| @ -0,0 +1,248 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | 	<head> | ||||||
|  | 		<meta charset="utf-8"> | ||||||
|  | 		<meta name="viewport" content="width=device-width, user-scalable=no"> | ||||||
|  | 		<title>Animegame-Client</title> | ||||||
|  | 		<style> | ||||||
|  | body { | ||||||
|  | 	touch-action: none; | ||||||
|  | 	margin: 0; | ||||||
|  | 	border: 0 none; | ||||||
|  | 	padding: 0; | ||||||
|  | 	text-align: center; | ||||||
|  | 	background-color: black; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #canvas { | ||||||
|  | 	display: block; | ||||||
|  | 	margin: 0; | ||||||
|  | 	color: white; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #canvas:focus { | ||||||
|  | 	outline: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .godot { | ||||||
|  | 	font-family: 'Noto Sans', 'Droid Sans', Arial, sans-serif; | ||||||
|  | 	color: #e0e0e0; | ||||||
|  | 	background-color: #3b3943; | ||||||
|  | 	background-image: linear-gradient(to bottom, #403e48, #35333c); | ||||||
|  | 	border: 1px solid #45434e; | ||||||
|  | 	box-shadow: 0 0 1px 1px #2f2d35; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Status display */ | ||||||
|  | 
 | ||||||
|  | #status { | ||||||
|  | 	position: absolute; | ||||||
|  | 	left: 0; | ||||||
|  | 	top: 0; | ||||||
|  | 	right: 0; | ||||||
|  | 	bottom: 0; | ||||||
|  | 	display: flex; | ||||||
|  | 	justify-content: center; | ||||||
|  | 	align-items: center; | ||||||
|  | 	/* don't consume click events - make children visible explicitly */ | ||||||
|  | 	visibility: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #status-progress { | ||||||
|  | 	width: 366px; | ||||||
|  | 	height: 7px; | ||||||
|  | 	background-color: #38363A; | ||||||
|  | 	border: 1px solid #444246; | ||||||
|  | 	padding: 1px; | ||||||
|  | 	box-shadow: 0 0 2px 1px #1B1C22; | ||||||
|  | 	border-radius: 2px; | ||||||
|  | 	visibility: visible; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media only screen and (orientation:portrait) { | ||||||
|  | 	#status-progress { | ||||||
|  | 		width: 61.8%; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #status-progress-inner { | ||||||
|  | 	height: 100%; | ||||||
|  | 	width: 0; | ||||||
|  | 	box-sizing: border-box; | ||||||
|  | 	transition: width 0.5s linear; | ||||||
|  | 	background-color: #202020; | ||||||
|  | 	border: 1px solid #222223; | ||||||
|  | 	box-shadow: 0 0 1px 1px #27282E; | ||||||
|  | 	border-radius: 3px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #status-indeterminate { | ||||||
|  | 	height: 42px; | ||||||
|  | 	visibility: visible; | ||||||
|  | 	position: relative; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #status-indeterminate > div { | ||||||
|  | 	width: 4.5px; | ||||||
|  | 	height: 0; | ||||||
|  | 	border-style: solid; | ||||||
|  | 	border-width: 9px 3px 0 3px; | ||||||
|  | 	border-color: #2b2b2b transparent transparent transparent; | ||||||
|  | 	transform-origin: center 21px; | ||||||
|  | 	position: absolute; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); } | ||||||
|  | 
 | ||||||
|  | #status-notice { | ||||||
|  | 	margin: 0 100px; | ||||||
|  | 	line-height: 1.3; | ||||||
|  | 	visibility: visible; | ||||||
|  | 	padding: 4px 6px; | ||||||
|  | 	visibility: visible; | ||||||
|  | } | ||||||
|  | 		</style> | ||||||
|  | 		<link id='-gd-engine-icon' rel='icon' type='image/png' href='/index.icon.png' /> | ||||||
|  | <link rel='apple-touch-icon' href='./index.apple-touch-icon.png'/> | ||||||
|  | 
 | ||||||
|  | 	</head> | ||||||
|  | 	<body> | ||||||
|  | 		<canvas id="canvas"> | ||||||
|  | 			HTML5 canvas appears to be unsupported in the current browser.<br > | ||||||
|  | 			Please try updating or use a different browser. | ||||||
|  | 		</canvas> | ||||||
|  | 		<div id="status"> | ||||||
|  | 			<div id="status-progress" style="display: none;" oncontextmenu="event.preventDefault();"> | ||||||
|  | 				<div id ="status-progress-inner"></div> | ||||||
|  | 			</div> | ||||||
|  | 			<div id="status-indeterminate" style="display: none;" oncontextmenu="event.preventDefault();"> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 			</div> | ||||||
|  | 			<div id="status-notice" class="godot" style="display: none;"></div> | ||||||
|  | 		</div> | ||||||
|  | 
 | ||||||
|  | 		<script src="/index.js"></script> | ||||||
|  | 		<script> | ||||||
|  | const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"executable":"index","experimentalVK":false,"fileSizes":{"/index.pck":{{ size_pck }},"/index.wasm":{{ size_wasm }}},"focusCanvas":true,"gdextensionLibs":[]}; | ||||||
|  | const engine = new Engine(GODOT_CONFIG); | ||||||
|  | 
 | ||||||
|  | (function () { | ||||||
|  | 	const INDETERMINATE_STATUS_STEP_MS = 100; | ||||||
|  | 	const statusProgress = document.getElementById('status-progress'); | ||||||
|  | 	const statusProgressInner = document.getElementById('status-progress-inner'); | ||||||
|  | 	const statusIndeterminate = document.getElementById('status-indeterminate'); | ||||||
|  | 	const statusNotice = document.getElementById('status-notice'); | ||||||
|  | 
 | ||||||
|  | 	let initializing = true; | ||||||
|  | 	let statusMode = 'hidden'; | ||||||
|  | 
 | ||||||
|  | 	let animationCallbacks = []; | ||||||
|  | 	function animate(time) { | ||||||
|  | 		animationCallbacks.forEach((callback) => callback(time)); | ||||||
|  | 		requestAnimationFrame(animate); | ||||||
|  | 	} | ||||||
|  | 	requestAnimationFrame(animate); | ||||||
|  | 
 | ||||||
|  | 	function animateStatusIndeterminate(ms) { | ||||||
|  | 		const i = Math.floor((ms / INDETERMINATE_STATUS_STEP_MS) % 8); | ||||||
|  | 		if (statusIndeterminate.children[i].style.borderTopColor === '') { | ||||||
|  | 			Array.prototype.slice.call(statusIndeterminate.children).forEach((child) => { | ||||||
|  | 				child.style.borderTopColor = ''; | ||||||
|  | 			}); | ||||||
|  | 			statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf'; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function setStatusMode(mode) { | ||||||
|  | 		if (statusMode === mode || !initializing) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		[statusProgress, statusIndeterminate, statusNotice].forEach((elem) => { | ||||||
|  | 			elem.style.display = 'none'; | ||||||
|  | 		}); | ||||||
|  | 		animationCallbacks = animationCallbacks.filter(function (value) { | ||||||
|  | 			return (value !== animateStatusIndeterminate); | ||||||
|  | 		}); | ||||||
|  | 		switch (mode) { | ||||||
|  | 		case 'progress': | ||||||
|  | 			statusProgress.style.display = 'block'; | ||||||
|  | 			break; | ||||||
|  | 		case 'indeterminate': | ||||||
|  | 			statusIndeterminate.style.display = 'block'; | ||||||
|  | 			animationCallbacks.push(animateStatusIndeterminate); | ||||||
|  | 			break; | ||||||
|  | 		case 'notice': | ||||||
|  | 			statusNotice.style.display = 'block'; | ||||||
|  | 			break; | ||||||
|  | 		case 'hidden': | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			throw new Error('Invalid status mode'); | ||||||
|  | 		} | ||||||
|  | 		statusMode = mode; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function setStatusNotice(text) { | ||||||
|  | 		while (statusNotice.lastChild) { | ||||||
|  | 			statusNotice.removeChild(statusNotice.lastChild); | ||||||
|  | 		} | ||||||
|  | 		const lines = text.split('\n'); | ||||||
|  | 		lines.forEach((line) => { | ||||||
|  | 			statusNotice.appendChild(document.createTextNode(line)); | ||||||
|  | 			statusNotice.appendChild(document.createElement('br')); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function displayFailureNotice(err) { | ||||||
|  | 		const msg = err.message || err; | ||||||
|  | 		console.error(msg); | ||||||
|  | 		setStatusNotice(msg); | ||||||
|  | 		setStatusMode('notice'); | ||||||
|  | 		initializing = false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const missing = Engine.getMissingFeatures(); | ||||||
|  | 	if (missing.length !== 0) { | ||||||
|  | 		const missingMsg = 'Error\nThe following features required to run Godot projects on the Web are missing:\n'; | ||||||
|  | 		displayFailureNotice(missingMsg + missing.join('\n')); | ||||||
|  | 	} else { | ||||||
|  | 		setStatusMode('indeterminate'); | ||||||
|  | 		engine.startGame({ | ||||||
|  | 			'onProgress': function (current, total) { | ||||||
|  | 				if (total > 0) { | ||||||
|  | 					statusProgressInner.style.width = `${(current / total) * 100}%`; | ||||||
|  | 					setStatusMode('progress'); | ||||||
|  | 					if (current === total) { | ||||||
|  | 						// wait for progress bar animation | ||||||
|  | 						setTimeout(() => { | ||||||
|  | 							setStatusMode('indeterminate'); | ||||||
|  | 						}, 500); | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					setStatusMode('indeterminate'); | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 		}).then(() => { | ||||||
|  | 			setStatusMode('hidden'); | ||||||
|  | 			initializing = false; | ||||||
|  | 		}, displayFailureNotice); | ||||||
|  | 	} | ||||||
|  | }()); | ||||||
|  | 		</script> | ||||||
|  | 	</body> | ||||||
|  | </html> | ||||||
|  | 
 | ||||||
| @ -0,0 +1,22 @@ | |||||||
|  | {% extends 'base.html.twig' %} | ||||||
|  | 
 | ||||||
|  | {% block title %}Hello ClientLoaderController!{% endblock %} | ||||||
|  | 
 | ||||||
|  | {% block body %} | ||||||
|  | <style> | ||||||
|  |     .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } | ||||||
|  |     .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } | ||||||
|  | </style> | ||||||
|  | 
 | ||||||
|  | <div class="example-wrapper"> | ||||||
|  |     <h1>Hello {{ controller_name }}! ✅</h1> | ||||||
|  |      | ||||||
|  |     <p style="white-space:nowrap">Token: {{ token }}</p> | ||||||
|  | 
 | ||||||
|  |     This friendly message is coming from: | ||||||
|  |     <ul> | ||||||
|  |         <li>Your controller at <code><a href="{{ '/home/pascal/git/animegame-dojo/src/Controller/ClientLoaderController.php'|file_link(0) }}">src/Controller/ClientLoaderController.php</a></code></li> | ||||||
|  |         <li>Your template at <code><a href="{{ '/home/pascal/git/animegame-dojo/templates/client_loader/index.html.twig'|file_link(0) }}">templates/client_loader/index.html.twig</a></code></li> | ||||||
|  |     </ul> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
| @ -0,0 +1,248 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | 	<head> | ||||||
|  | 		<meta charset="utf-8"> | ||||||
|  | 		<meta name="viewport" content="width=device-width, user-scalable=no"> | ||||||
|  | 		<title>Animegame-Client</title> | ||||||
|  | 		<style> | ||||||
|  | body { | ||||||
|  | 	touch-action: none; | ||||||
|  | 	margin: 0; | ||||||
|  | 	border: 0 none; | ||||||
|  | 	padding: 0; | ||||||
|  | 	text-align: center; | ||||||
|  | 	background-color: black; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #canvas { | ||||||
|  | 	display: block; | ||||||
|  | 	margin: 0; | ||||||
|  | 	color: white; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #canvas:focus { | ||||||
|  | 	outline: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .godot { | ||||||
|  | 	font-family: 'Noto Sans', 'Droid Sans', Arial, sans-serif; | ||||||
|  | 	color: #e0e0e0; | ||||||
|  | 	background-color: #3b3943; | ||||||
|  | 	background-image: linear-gradient(to bottom, #403e48, #35333c); | ||||||
|  | 	border: 1px solid #45434e; | ||||||
|  | 	box-shadow: 0 0 1px 1px #2f2d35; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Status display */ | ||||||
|  | 
 | ||||||
|  | #status { | ||||||
|  | 	position: absolute; | ||||||
|  | 	left: 0; | ||||||
|  | 	top: 0; | ||||||
|  | 	right: 0; | ||||||
|  | 	bottom: 0; | ||||||
|  | 	display: flex; | ||||||
|  | 	justify-content: center; | ||||||
|  | 	align-items: center; | ||||||
|  | 	/* don't consume click events - make children visible explicitly */ | ||||||
|  | 	visibility: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #status-progress { | ||||||
|  | 	width: 366px; | ||||||
|  | 	height: 7px; | ||||||
|  | 	background-color: #38363A; | ||||||
|  | 	border: 1px solid #444246; | ||||||
|  | 	padding: 1px; | ||||||
|  | 	box-shadow: 0 0 2px 1px #1B1C22; | ||||||
|  | 	border-radius: 2px; | ||||||
|  | 	visibility: visible; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media only screen and (orientation:portrait) { | ||||||
|  | 	#status-progress { | ||||||
|  | 		width: 61.8%; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #status-progress-inner { | ||||||
|  | 	height: 100%; | ||||||
|  | 	width: 0; | ||||||
|  | 	box-sizing: border-box; | ||||||
|  | 	transition: width 0.5s linear; | ||||||
|  | 	background-color: #202020; | ||||||
|  | 	border: 1px solid #222223; | ||||||
|  | 	box-shadow: 0 0 1px 1px #27282E; | ||||||
|  | 	border-radius: 3px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #status-indeterminate { | ||||||
|  | 	height: 42px; | ||||||
|  | 	visibility: visible; | ||||||
|  | 	position: relative; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #status-indeterminate > div { | ||||||
|  | 	width: 4.5px; | ||||||
|  | 	height: 0; | ||||||
|  | 	border-style: solid; | ||||||
|  | 	border-width: 9px 3px 0 3px; | ||||||
|  | 	border-color: #2b2b2b transparent transparent transparent; | ||||||
|  | 	transform-origin: center 21px; | ||||||
|  | 	position: absolute; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); } | ||||||
|  | #status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); } | ||||||
|  | 
 | ||||||
|  | #status-notice { | ||||||
|  | 	margin: 0 100px; | ||||||
|  | 	line-height: 1.3; | ||||||
|  | 	visibility: visible; | ||||||
|  | 	padding: 4px 6px; | ||||||
|  | 	visibility: visible; | ||||||
|  | } | ||||||
|  | 		</style> | ||||||
|  | 		<link id='-gd-engine-icon' rel='icon' type='image/png' href='{{ asset('godot/index.icon.png') }}' /> | ||||||
|  | <link rel='apple-touch-icon' href='{{ asset('godot/index.apple-touch-icon.png') }}'/> | ||||||
|  | 
 | ||||||
|  | 	</head> | ||||||
|  | 	<body> | ||||||
|  | 		<canvas id="canvas"> | ||||||
|  | 			HTML5 canvas appears to be unsupported in the current browser.<br > | ||||||
|  | 			Please try updating or use a different browser. | ||||||
|  | 		</canvas> | ||||||
|  | 		<div id="status"> | ||||||
|  | 			<div id="status-progress" style="display: none;" oncontextmenu="event.preventDefault();"> | ||||||
|  | 				<div id ="status-progress-inner"></div> | ||||||
|  | 			</div> | ||||||
|  | 			<div id="status-indeterminate" style="display: none;" oncontextmenu="event.preventDefault();"> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 				<div></div> | ||||||
|  | 			</div> | ||||||
|  | 			<div id="status-notice" class="godot" style="display: none;"></div> | ||||||
|  | 		</div> | ||||||
|  | 
 | ||||||
|  | 		<script src="{{ asset('godot/index.js') }}"></script> | ||||||
|  | 		<script> | ||||||
|  | const GODOT_CONFIG = {"args":["--", "--token={{ token }}"],"canvasResizePolicy":2,"executable":"{{ asset('godot/index') }}","experimentalVK":false,"fileSizes":{"{{ asset('godot/index.pck') }}":355472,"{{ asset('godot/index.wasm') }}":35708238},"focusCanvas":true,"gdextensionLibs":[]}; | ||||||
|  | const engine = new Engine(GODOT_CONFIG); | ||||||
|  | 
 | ||||||
|  | (function () { | ||||||
|  | 	const INDETERMINATE_STATUS_STEP_MS = 100; | ||||||
|  | 	const statusProgress = document.getElementById('status-progress'); | ||||||
|  | 	const statusProgressInner = document.getElementById('status-progress-inner'); | ||||||
|  | 	const statusIndeterminate = document.getElementById('status-indeterminate'); | ||||||
|  | 	const statusNotice = document.getElementById('status-notice'); | ||||||
|  | 
 | ||||||
|  | 	let initializing = true; | ||||||
|  | 	let statusMode = 'hidden'; | ||||||
|  | 
 | ||||||
|  | 	let animationCallbacks = []; | ||||||
|  | 	function animate(time) { | ||||||
|  | 		animationCallbacks.forEach((callback) => callback(time)); | ||||||
|  | 		requestAnimationFrame(animate); | ||||||
|  | 	} | ||||||
|  | 	requestAnimationFrame(animate); | ||||||
|  | 
 | ||||||
|  | 	function animateStatusIndeterminate(ms) { | ||||||
|  | 		const i = Math.floor((ms / INDETERMINATE_STATUS_STEP_MS) % 8); | ||||||
|  | 		if (statusIndeterminate.children[i].style.borderTopColor === '') { | ||||||
|  | 			Array.prototype.slice.call(statusIndeterminate.children).forEach((child) => { | ||||||
|  | 				child.style.borderTopColor = ''; | ||||||
|  | 			}); | ||||||
|  | 			statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf'; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function setStatusMode(mode) { | ||||||
|  | 		if (statusMode === mode || !initializing) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		[statusProgress, statusIndeterminate, statusNotice].forEach((elem) => { | ||||||
|  | 			elem.style.display = 'none'; | ||||||
|  | 		}); | ||||||
|  | 		animationCallbacks = animationCallbacks.filter(function (value) { | ||||||
|  | 			return (value !== animateStatusIndeterminate); | ||||||
|  | 		}); | ||||||
|  | 		switch (mode) { | ||||||
|  | 		case 'progress': | ||||||
|  | 			statusProgress.style.display = 'block'; | ||||||
|  | 			break; | ||||||
|  | 		case 'indeterminate': | ||||||
|  | 			statusIndeterminate.style.display = 'block'; | ||||||
|  | 			animationCallbacks.push(animateStatusIndeterminate); | ||||||
|  | 			break; | ||||||
|  | 		case 'notice': | ||||||
|  | 			statusNotice.style.display = 'block'; | ||||||
|  | 			break; | ||||||
|  | 		case 'hidden': | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			throw new Error('Invalid status mode'); | ||||||
|  | 		} | ||||||
|  | 		statusMode = mode; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function setStatusNotice(text) { | ||||||
|  | 		while (statusNotice.lastChild) { | ||||||
|  | 			statusNotice.removeChild(statusNotice.lastChild); | ||||||
|  | 		} | ||||||
|  | 		const lines = text.split('\n'); | ||||||
|  | 		lines.forEach((line) => { | ||||||
|  | 			statusNotice.appendChild(document.createTextNode(line)); | ||||||
|  | 			statusNotice.appendChild(document.createElement('br')); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function displayFailureNotice(err) { | ||||||
|  | 		const msg = err.message || err; | ||||||
|  | 		console.error(msg); | ||||||
|  | 		setStatusNotice(msg); | ||||||
|  | 		setStatusMode('notice'); | ||||||
|  | 		initializing = false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const missing = Engine.getMissingFeatures(); | ||||||
|  | 	if (missing.length !== 0) { | ||||||
|  | 		const missingMsg = 'Error\nThe following features required to run Godot projects on the Web are missing:\n'; | ||||||
|  | 		displayFailureNotice(missingMsg + missing.join('\n')); | ||||||
|  | 	} else { | ||||||
|  | 		setStatusMode('indeterminate'); | ||||||
|  | 		engine.startGame({ | ||||||
|  | 			'onProgress': function (current, total) { | ||||||
|  | 				if (total > 0) { | ||||||
|  | 					statusProgressInner.style.width = `${(current / total) * 100}%`; | ||||||
|  | 					setStatusMode('progress'); | ||||||
|  | 					if (current === total) { | ||||||
|  | 						// wait for progress bar animation | ||||||
|  | 						setTimeout(() => { | ||||||
|  | 							setStatusMode('indeterminate'); | ||||||
|  | 						}, 500); | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					setStatusMode('indeterminate'); | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 		}).then(() => { | ||||||
|  | 			setStatusMode('hidden'); | ||||||
|  | 			initializing = false; | ||||||
|  | 		}, displayFailureNotice); | ||||||
|  | 	} | ||||||
|  | }()); | ||||||
|  | 		</script> | ||||||
|  | 	</body> | ||||||
|  | </html> | ||||||
|  | 
 | ||||||
| @ -0,0 +1,62 @@ | |||||||
|  | <?php | ||||||
|  | namespace App\Tests; | ||||||
|  | 
 | ||||||
|  | use App\Factory\UserFactory; | ||||||
|  | 
 | ||||||
|  | class UserTest extends AbstractTest | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Requirement: A user should be able to update his properties | ||||||
|  |      */ | ||||||
|  |     public function testUpdateUserProperties(): void | ||||||
|  |     { | ||||||
|  |         $requestUser = UserFactory::createOne(); | ||||||
|  | 
 | ||||||
|  |         $response = static::createClientWithToken($requestUser->authName)->request('PATCH', $this->getIri($requestUser), | ||||||
|  |             [ | ||||||
|  |                 'headers' => [ | ||||||
|  |                     'content-type' => 'application/merge-patch+json' | ||||||
|  |                 ], | ||||||
|  |                 'json' => [ | ||||||
|  |                     'authName' => 'foo.bar', | ||||||
|  |                     'properties' => [ | ||||||
|  |                         'a' => 1, | ||||||
|  |                         'b' => true, | ||||||
|  |                         'c' => null, | ||||||
|  |                         'd' => 'text' | ||||||
|  |                     ] | ||||||
|  |                 ] | ||||||
|  |             ]); | ||||||
|  | 
 | ||||||
|  |         $this->assertResponseStatusCodeSame(200); | ||||||
|  | 
 | ||||||
|  |         $this->assertEquals(1, $response->toArray()['properties']['a']); | ||||||
|  |         $this->assertEquals(true, $response->toArray()['properties']['b']); | ||||||
|  |         $this->assertEquals(null, $response->toArray()['properties']['c']); | ||||||
|  |         $this->assertEquals('text', $response->toArray()['properties']['d']); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Requirement: A user should NOT be able to update his authName | ||||||
|  |      */ | ||||||
|  |     public function testUpdateUserAuthnameNotPossible(): void | ||||||
|  |     { | ||||||
|  |         $requestUser = UserFactory::createOne(); | ||||||
|  | 
 | ||||||
|  |         $response = static::createClientWithToken($requestUser->authName)->request('PATCH', $this->getIri($requestUser), | ||||||
|  |             [ | ||||||
|  |                 'headers' => [ | ||||||
|  |                     'content-type' => 'application/merge-patch+json' | ||||||
|  |                 ], | ||||||
|  |                 'json' => [ | ||||||
|  |                     'authName' => 'foo.bar' | ||||||
|  |                 ] | ||||||
|  |             ]); | ||||||
|  | 
 | ||||||
|  |         $this->assertResponseStatusCodeSame(200); | ||||||
|  | 
 | ||||||
|  |         $this->assertNotEquals("foo.bar", $response->toArray()['authName']); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
					Loading…
					
					
				
		Reference in New Issue