8Land is an open pixel-art RPG game where players design their own lands and launch their own public games. The objective is to have the gameplay of Decentraland and the feel and nostalgia of Pokemon Gold and Silver
This Github repository does not include any license and is therefore subject to default copyright laws. The code contained within this repository is copyrighted and the user is not allowed to distribute, modify, or share the code without the permission of the copyright holder.
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.
Let me remind you that this codebase is old.
It favours owning and customizing abstractions even for the build pipeline, so I could share Typescript code and guarantee type-safety between backend and frontend back then. It's also very bloated and made during my free hours, with the objective of just having an MVP.
This is no longer the methodology I follow , since it takes a lot of time, and brings a lot of non-product related code into your workload. With the advance of Large Language Models, I also try to stick with the defaults of frameworks and use 3rd party providers the most, since it makes it easier to use these models to generate new code.
This repository provides a
devcontainer
in case you want to run the project in a container isolated from your host setup
npm run install:all
- Setup and start the Firebase Emulator Suite
- Create a new project in the Firebase Console
- Install the Firebase CLI:
npm install -g firebase-tools
- Login with your Google Account:
firebase login
- Setup the emulators:
firebase init emulators
- You will only need the
Authentication Emulator
- Leave everything else in its default values
- You will only need the
- Setup the project's secrets
- Create an API secrets file called
.env.secrets.development
in the root of the project and fill it with the necessary secrets- required environment variables are listed in
src/internals/environment/environment-variables.schema.ts
- required environment variables are listed in
- Create a Web App secrets file called
.env.secrets.development
inclient-side/web-app
and fill it with the necessary secrets- required environment variables are listed in
client-side/web-app/src/logic/app-internals/runtime/environment-variables.ts
- required environment variables are listed in
- Create an API secrets file called
- Start Firebase
npm run start:dev:firebase
- Start your project's infrastructure (example: databases, Redis, etc.)
./start-dev-infrastructure.sh
- Seed the development database with sample data by running
NODE_ENV=development npm run seed
- Set up an Alchemy account at https://www.alchemy.com/
- Create a Metamask Wallet
- Build the smart-contracts by running
NODE_ENV=development npm run build:smart-contracts
- Deploy smart-contracts into a testnet
- run
NODE_ENV=development npm run ts-node ./dist/scripts/deploy.ts
- take note of the contracts addresses and place them in
.env.secrets.development
to be used environment variables
- run
- Start Firebase
npm run start:dev:firebase
- Start your project's infrastructure (example: databases, Redis, etc.)
./start-dev-infrastructure.sh
- Start the server with
npm run start:dev
, ornpm run start:debug
if you want to debug the API in the Chrome Developer Tools - To run the web app:
cd client-side/web-app
npm run develop
- Open
http://localhost:8000
and start developing
end-user@8land.com
password123
admin@8land.com
password123
Before running any of these commands, you need to set an environment first by prefixing the command with
NODE_ENV=<development | test>
.
This is in order to avoid running some of these commands in a production environment, even if it has no NODE_ENV
set.
- Seed data:
npm run seed
- Tear down databases:
npm run tear-down-databases
Typeorm entities should always be placed in directories named typeorm
and have their file name ending in .entity.ts
in order to be detected by the Typeorm CLI.
Newly created migrations should be imported and placed in all-migrations.ts
files in order to run.
- Generate migration:
npm run typeorm migration:generate -- -- --pretty -n MigrationName
- Create empty migration:
npm run typeorm migration:create -- -- -n MigrationName
- Run migrations:
npm run typeorm migration:run
- Revert last migration:
npm run typeorm migration:revert
- Any other TypeORM command:
npm run typeorm {command} -- -- --{argument}
- Deploy
- Development
- Build the smart contracts
NODE_ENV=development npm run ts-node ./scripts/deploy.ts
- Take note of the deployed contracts addresses
- Set those addresses as environment variables in
.env.secrets.development
- Production
- Set the necessary environment variables in your terminal
- Build the smart contracts:
npm run build:smart-contracts
- Build the api:
npm run build:api
NODE_ENV=production node ./dist/scripts/deploy.js
- Take note of the deployed contracts addresses
- Set those addresses as environment variables in your production server
- Do not replace environment variables, but instead, try to version them, in order to avoid downtime or errors
- Example:
SOMETHING_CONTRACT_ADDRESS
andSOMETHING_CONTRACT_ADDRESS_V2
- Example:
- Development
- Place your DTO classes inside files ending in
.dto.ts
. NestJS compiler plugins will take it from here.
In order for a background job to run in a separate process and be compiled in a separate file from the server code, just have its file name end in .job.ts
. The job function should also be wrapped around app__runJob()
which is a global variable made available by importing src/internals/jobs/run-job
. Make sure you import this file on top of other imports. It's a module with side-effects that does the usual setup like loading environment variables, logging and error handling.
- Shut down all your services
- Set up Ngrok
- Set up your services in
~/.ngrok2/ngrok.yml
by adding:
tunnels:
service_1:
addr: 3000
proto: http
bind-tls: true # Forces HTTPS only tunneling
service_2:
addr: 8000
proto: http
bind-tls: true # Forces HTTPS only tunneling
- Run
<place where ngrok is installed>/ngrok start --all
- Remember to update the URLs in your services configuration files in order to match the URLs exposed by Ngrok
- Start your services
- Share the Ngrok URLs with whoever asked to try your app
-
Read about validations here: https://github.com/Bartmr/not-me
-
Avoid configurations that are tied to the build type (example: values and configurations that use
NODE_ENV
). -
Do not access environment variables directly (the linter will problably stop you from doing that). Use the
EnvironmentVariablesService
to access these variables. This service is responsible for parsing and validating all environment variables that are used. -
Always use custom repositories In order to enforce the use of the custom logic implemented in each repository (like auditing rows changes when extending
AuditedEntityRepository
) and to make sure that entities have all their required fields filled, always use custom repositories, by calling(connection or manager).getCustomRepository(CustomRepositoryClass)
.- As a rule of thumb, if you're using the entity class as a value, or using it's constructor, you're problably doing it wrong and you should use or augment your entity's custom repository.
-
DTOs should NOT have methods. There is currently a problem with reflection and class instances: imagine you want to self-document an endpoint by using Swagger: you annotate a DTO class properties with Swagger decorators, and then set the body argument type in the controller method with said DTO class. The real type that is going to come from that argument is not going to be an instance of the DTO class but an Object instance / object literal with the same properties (but not the methods) as the DTO class. This is because we use a validation mechanism that only runs on runtime and outputs object literals with the validated values. Setting something like
class-transformer
would bring new problems regarding the type safety between the class properties and the decorators annotating them. So this seems to be the more flexible choice. This body argument is then going to circulate deeper in other parts of the app, like services and other functions. That is why you should avoid usinginstanceof
on values that problably came from outside the API, as it will always return false: because the value is not really an instance of the DTO class, but an instance of Object. That also means that the body argument, altought typed as the DTO class, will not have its methods. Typescript will allow you to call a method as if it were a real DTO, but it will throw an exception since that method doesn't really exist. That's why you should not declare methods on DTO classes.class SomeDTO { public value: string; doSomething() { } } @Controller('some-path') export class SomeController { @Post() async create( @Body() someDTO: SomeDTO /* <-- Is not a real instance of SomeDTO, but is an object literal with the same properties and none of the methods */ ): Promise<SomeDTO> { console.log(someDTO.value) // <-- will print a value console.log(someDTO instanceof SomeDTO) /* <-- Will always return false, since someDTO is an instance of an object literal. DO NOT USE instanceof TO DISTINGUISH VALUE TYPES */ someDTO.doSomething() // <-- Will crash, since the object literal has the properties of SomeDTO, but not the methods return 'will-never-get-here'; }
- Not-Me: https://github.com/Bartmr/not-me
- Files to be changed
- .nvmrc
- BREAKING-CHANGES.md
- add warning for inherited projects to delete all
node_modules
directories andpackage-lock.json
files, and then runnpm run install:all
- add warning for inherited projects to delete all
- Dockerfile
- Dockerfile.dev
- client-side/web-app/.nvmrc
- client-side/web-app/Dockerfile
- client-side/web-app/load-build-environment.js
- client-side/web-app/package.json
engine
field@types/node
version
- package.json
engine
field@types/node
version
- tsconfig.base.json
- delete all
node_modules
directories andpackage-lock.json
files - run
npm run install:all
- Files to be changed
- .gitignore
- could instead just add a
.gitignore
file inside the new library
- could instead just add a
- .prettierignore
- .dockerignore
- Dockerfile
- .eslintignore
- .eslintrc.js
- All
tsconfig
files - package.json
- add the necessary steps to cover the new library code in the integrity checks
- Web App (and other nested projects)
- .dockerignore
- Dockerfile
- .eslintignore
- .eslintrc.js
- All
tsconfig
files- also add an alias/path for the new library, so it doesn't get directly imported with a relative path
- package.json
- copy the new library source files to the
dist
directory when running the release build
- copy the new library source files to the
- Webpack resolvers and aliases
- add a new alias for the new library, pointing to
libs
during local builds and todist
during release builds
- add a new alias for the new library, pointing to
- .gitignore