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