NOTICKET: Api Client Generation
All checks were successful
Quality Check / Linting (push) Successful in 6s
Build Application / generate (push) Successful in 22s
Build Application / build (tower_defence_web, build/*, build, build/index.html, Web) (push) Successful in 16s
Build Application / build (tower_defence.elf, tower_defence.elf, tower_defence.elf, Linux) (push) Successful in 19s
Build Application / build (tower_defence.exe, tower_defence.exe, tower_defence.exe, Windows) (push) Successful in 21s
Build Application / build-docker (push) Successful in 16s
Build Application / release (push) Successful in 9s
All checks were successful
Quality Check / Linting (push) Successful in 6s
Build Application / generate (push) Successful in 22s
Build Application / build (tower_defence_web, build/*, build, build/index.html, Web) (push) Successful in 16s
Build Application / build (tower_defence.elf, tower_defence.elf, tower_defence.elf, Linux) (push) Successful in 19s
Build Application / build (tower_defence.exe, tower_defence.exe, tower_defence.exe, Windows) (push) Successful in 21s
Build Application / build-docker (push) Successful in 16s
Build Application / release (push) Successful in 9s
This commit is contained in:
parent
9ab96afacf
commit
1ab94cd961
10 changed files with 277 additions and 45 deletions
169
.api-template/core/ApiConfig.handlebars
Normal file
169
.api-template/core/ApiConfig.handlebars
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
extends {{>partials/api_config_parent_class}}
|
||||||
|
class_name {{>partials/api_config_class_name}}
|
||||||
|
|
||||||
|
{{>partials/disclaimer_autogenerated}}
|
||||||
|
|
||||||
|
# Configuration options for Api endpoints
|
||||||
|
# =======================================
|
||||||
|
#
|
||||||
|
# Helps share configuration customizations across Apis:
|
||||||
|
# - host, port & scheme
|
||||||
|
# - extra headers (low priority, high priority)
|
||||||
|
# - transport layer security options (TLS certificates)
|
||||||
|
# - log level
|
||||||
|
#
|
||||||
|
# You probably want to make an instance of this class with your own values,
|
||||||
|
# and feed it to each Api's constructor, before calling the Api's methods.
|
||||||
|
#
|
||||||
|
# Since it is a Resource, you may use `ResourceSaver.save()` and `preload()`
|
||||||
|
# to save it and load it from file, for convenience.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
# These are constant, immutable default values. Best not edit them.
|
||||||
|
# To set different values at runtime, use the @export'ed properties below.
|
||||||
|
const BEE_DEFAULT_HOST := "{{#if host}}{{{host}}}{{else}}localhost{{/if}}"
|
||||||
|
const BEE_DEFAULT_PORT_HTTP := 80
|
||||||
|
const BEE_DEFAULT_PORT_HTTPS := 443
|
||||||
|
const BEE_DEFAULT_POLLING_INTERVAL_MS := 333 # milliseconds
|
||||||
|
|
||||||
|
|
||||||
|
# Configuration also handles logging because it's convenient.
|
||||||
|
enum LogLevel {
|
||||||
|
SILENT,
|
||||||
|
ERROR,
|
||||||
|
WARNING,
|
||||||
|
INFO,
|
||||||
|
DEBUG,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Log level to configure verbosity.
|
||||||
|
@export var log_level := LogLevel.WARNING
|
||||||
|
|
||||||
|
{{!--
|
||||||
|
# Not sure if this should hold the HTTPClient instance or not. Not for now.
|
||||||
|
# Godot recommends using a single client for all requests, so it perhaps should.
|
||||||
|
|
||||||
|
# Godot's HTTP Client we are using.
|
||||||
|
# If none was set (by you), we'll lazily make one.
|
||||||
|
var bee_client: HTTPClient:
|
||||||
|
set(value):
|
||||||
|
bee_client = value
|
||||||
|
get:
|
||||||
|
if not bee_client:
|
||||||
|
bee_client = HTTPClient.new()
|
||||||
|
return bee_client
|
||||||
|
--}}
|
||||||
|
|
||||||
|
## The host to connect to, with or without the protocol scheme.
|
||||||
|
## We toggle TLS accordingly to the provided scheme, if any.
|
||||||
|
@export var host := BEE_DEFAULT_HOST:
|
||||||
|
set(value):
|
||||||
|
if value.begins_with("https://"):
|
||||||
|
tls_enabled = true
|
||||||
|
value = value.substr(8) # "https://".length() == 8
|
||||||
|
elif value.begins_with("http://"):
|
||||||
|
tls_enabled = false
|
||||||
|
value = value.substr(7) # "http://".length() == 7
|
||||||
|
host = value
|
||||||
|
|
||||||
|
|
||||||
|
## Port through which the connection will be established.
|
||||||
|
## NOTE: changing the host may change the port as well if the scheme was provided, see above.
|
||||||
|
@export var port := BEE_DEFAULT_PORT_HTTP
|
||||||
|
|
||||||
|
|
||||||
|
## Headers used as base for all requests made by Api instances using this config.
|
||||||
|
## Those are the lowest priority headers, and are merged with custom headers provided in the bee_request() method call
|
||||||
|
## as well as the headers override below, to compute the final, actually sent headers.
|
||||||
|
@export var headers_base := {
|
||||||
|
# Stigmergy: everyone does what is left to do (like ants do)
|
||||||
|
"User-Agent": "Stigmergiac/1.0 (Godot)",
|
||||||
|
# For my mental health's sake, only JSON is supported for now
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## High-priority headers, they will always override other headers coming from the base above or the method call.
|
||||||
|
@export var headers_override := {}
|
||||||
|
|
||||||
|
|
||||||
|
## Duration of sleep between poll() calls.
|
||||||
|
@export var polling_interval_ms := BEE_DEFAULT_POLLING_INTERVAL_MS # milliseconds
|
||||||
|
|
||||||
|
|
||||||
|
## Enable the Transport Security Layer (packet encryption, HTTPS)
|
||||||
|
@export var tls_enabled := false:
|
||||||
|
set(value):
|
||||||
|
tls_enabled = value
|
||||||
|
# port = BEE_DEFAULT_PORT_HTTPS if tls_enabled else BEE_DEFAULT_PORT_HTTP
|
||||||
|
|
||||||
|
|
||||||
|
## You should preload your *.crt file (the whole chain) in here if you want TLS.
|
||||||
|
## I usually concatenate my /etc/ssl/certs/ca-certificates.crt and webserver certs here.
|
||||||
|
## Remember to add the *.crt file to the exports, if necessary.
|
||||||
|
@export var trusted_chain: X509Certificate # only used if tls is enabled
|
||||||
|
@export var common_name_override := "" # for TLSOptions
|
||||||
|
|
||||||
|
|
||||||
|
## Dynamic accessor using the TLS properties above, but you may inject your own
|
||||||
|
## for example if you need to use TLSOptions.client_unsafe. Best not @export this.
|
||||||
|
var tls_options: TLSOptions:
|
||||||
|
set(value):
|
||||||
|
tls_options = value
|
||||||
|
get:
|
||||||
|
if not tls_enabled:
|
||||||
|
return null
|
||||||
|
if not tls_options:
|
||||||
|
tls_options = TLSOptions.client(trusted_chain, common_name_override)
|
||||||
|
return tls_options
|
||||||
|
|
||||||
|
|
||||||
|
func log_error(message: String):
|
||||||
|
if self.log_level >= LogLevel.ERROR:
|
||||||
|
push_error(message)
|
||||||
|
|
||||||
|
func log_warning(message: String):
|
||||||
|
if self.log_level >= LogLevel.WARNING:
|
||||||
|
push_warning(message)
|
||||||
|
|
||||||
|
func log_info(message: String):
|
||||||
|
if self.log_level >= LogLevel.INFO:
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
func log_debug(message: String):
|
||||||
|
if self.log_level >= LogLevel.DEBUG:
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
{{#each authMethods}}
|
||||||
|
# Authentication method `{{name}}`.
|
||||||
|
{{#if isBasicBearer }}
|
||||||
|
# Basic Bearer Authentication `{{bearerFormat}}`
|
||||||
|
func set_security_{{name}}(value: String):
|
||||||
|
self.headers_base["Authorization"] = "Bearer %s" % value
|
||||||
|
|
||||||
|
|
||||||
|
{{else if isApiKey }}
|
||||||
|
# Api Key Authentication `{{keyParamName}}`
|
||||||
|
func set_security_{{name}}(value: String):
|
||||||
|
{{#if isKeyInHeader }}
|
||||||
|
self.headers_base["{{keyParamName}}"] = value
|
||||||
|
{{else if isKeyInQuery }}
|
||||||
|
# Implementing this should be straightforward
|
||||||
|
log_error("Api Key in Query is not supported at the moment. (contribs welcome)")
|
||||||
|
{{else if isKeyInCookie }}
|
||||||
|
log_error("Api Key in Cookie is not supported at the moment. (contribs welcome)")
|
||||||
|
{{else }}
|
||||||
|
log_error("Unrecognized Api Key format (contribs welcome).")
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
# → Skipped: not implemented in the gdscript templates. (contribs are welcome)
|
||||||
|
|
||||||
|
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
|
@ -7,8 +7,25 @@ on:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
generate:
|
||||||
|
runs-on: stable
|
||||||
|
container:
|
||||||
|
image: git.euph.dev/actions/runner-java-21:1300
|
||||||
|
steps:
|
||||||
|
- name: "Checkout"
|
||||||
|
uses: "https://git.euph.dev/actions/checkout@v3"
|
||||||
|
- name: "Generate"
|
||||||
|
run: just generate
|
||||||
|
- name: Upload API Client as Artifact
|
||||||
|
uses: "https://git.euph.dev/actions/upload-artifact@v3"
|
||||||
|
with:
|
||||||
|
name: api-client
|
||||||
|
path: scripts/api-client/*
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: stable
|
runs-on: stable
|
||||||
|
needs:
|
||||||
|
- generate
|
||||||
container:
|
container:
|
||||||
image: git.euph.dev/actions/runner-redot-4.3:latest
|
image: git.euph.dev/actions/runner-redot-4.3:latest
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -35,6 +52,11 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: "https://git.euph.dev/actions/checkout@v3"
|
uses: "https://git.euph.dev/actions/checkout@v3"
|
||||||
|
- name: Download artifact from previous job
|
||||||
|
uses: "https://git.euph.dev/actions/download-artifact@v3"
|
||||||
|
with:
|
||||||
|
name: api-client
|
||||||
|
path: scripts/api-client
|
||||||
- name: Build Binary
|
- name: Build Binary
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ matrix.build-dir }}" != "" ]; then
|
if [ "${{ matrix.build-dir }}" != "" ]; then
|
||||||
|
@ -104,9 +126,9 @@ jobs:
|
||||||
# Tower Defence - Client ${{ github.ref_name }}
|
# Tower Defence - Client ${{ github.ref_name }}
|
||||||
Run this release with docker like this:
|
Run this release with docker like this:
|
||||||
\`\`\`sh
|
\`\`\`sh
|
||||||
docker run --rm -p 8080:80 git.euph.dev/towerdefence/web-client:${{ github.ref_name }}
|
docker run --rm -p 8100:80 git.euph.dev/towerdefence/web-client:${{ github.ref_name }}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
It will be available under [\`localhost:8080\`](localhost:8080)
|
It will be available under [\`localhost:8100\`](localhost:8100)
|
||||||
<br><br>
|
<br><br>
|
||||||
For more information read the [Documentation](https://git.euph.dev/TowerDefence/Dokumentation/wiki/Client/Config)
|
For more information read the [Documentation](https://git.euph.dev/TowerDefence/Dokumentation/wiki/Client/Config)
|
||||||
|
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
||||||
# Redot 4+ specific ignores
|
# Redot 4+ specific ignores
|
||||||
.godot/
|
.godot/
|
||||||
/android/
|
/android/
|
||||||
|
openapitools.json
|
||||||
|
scripts/api-client/
|
||||||
|
|
20
Justfile
Normal file
20
Justfile
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
_choose:
|
||||||
|
just --choose
|
||||||
|
|
||||||
|
lint:
|
||||||
|
-gdlint .
|
||||||
|
-gdformat -d .
|
||||||
|
|
||||||
|
api_version := "v0.0.0-rc.4"
|
||||||
|
api_location := "scripts/api-client"
|
||||||
|
generate:
|
||||||
|
#!/bin/sh
|
||||||
|
API_URL="https://git.euph.dev/TowerDefence/Server/releases/download/{{ api_version }}/api.yml"
|
||||||
|
rm -rf {{ api_location }}
|
||||||
|
mkdir -p {{ api_location }}
|
||||||
|
npx --yes @openapitools/openapi-generator-cli \
|
||||||
|
generate \
|
||||||
|
-g gdscript \
|
||||||
|
-i "$API_URL" \
|
||||||
|
-o {{ api_location }} \
|
||||||
|
-t .api-template
|
18
config/api_config.tres
Normal file
18
config/api_config.tres
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[gd_resource type="Resource" script_class="ApiConfig" load_steps=2 format=3 uid="uid://cdixdbu3sqgjn"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/api-client/core/ApiConfig.gd" id="1_0p6qs"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_0p6qs")
|
||||||
|
log_level = 2
|
||||||
|
host = "localhost"
|
||||||
|
port = 8080
|
||||||
|
headers_base = {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "Stigmergiac/1.0 (Godot)"
|
||||||
|
}
|
||||||
|
headers_override = {}
|
||||||
|
polling_interval_ms = 333
|
||||||
|
tls_enabled = false
|
||||||
|
common_name_override = ""
|
|
@ -1,4 +1,5 @@
|
||||||
excluded_directories: !!set
|
excluded_directories: !!set
|
||||||
.git: null
|
.git: null
|
||||||
addons: null
|
addons: null
|
||||||
|
api-client: null
|
||||||
|
|
||||||
|
|
1
gdlintrc
1
gdlintrc
|
@ -25,6 +25,7 @@ enum-name: ([A-Z][a-z0-9]*)+
|
||||||
excluded_directories: !!set
|
excluded_directories: !!set
|
||||||
.git: null
|
.git: null
|
||||||
addons: null
|
addons: null
|
||||||
|
api-client: null
|
||||||
expression-not-assigned: null
|
expression-not-assigned: null
|
||||||
function-argument-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
function-argument-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
||||||
function-arguments-number: 10
|
function-arguments-number: 10
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[gd_scene load_steps=3 format=3 uid="uid://dtaaw31x3n22f"]
|
[gd_scene load_steps=4 format=3 uid="uid://dtaaw31x3n22f"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/login/login.gd" id="1_12w35"]
|
[ext_resource type="Script" path="res://scripts/ui/login.gd" id="1_12w35"]
|
||||||
|
[ext_resource type="Resource" uid="uid://cdixdbu3sqgjn" path="res://config/api_config.tres" id="2_60hb8"]
|
||||||
|
|
||||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_d0bbp"]
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_d0bbp"]
|
||||||
|
|
||||||
|
@ -74,12 +75,12 @@ scroll_active = false
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
secret = true
|
secret = true
|
||||||
|
|
||||||
[node name="Button" type="Button" parent="Panel/VBoxContainer" node_paths=PackedStringArray("username_field", "password_field", "http_request")]
|
[node name="Button" type="Button" parent="Panel/VBoxContainer" node_paths=PackedStringArray("username_field", "password_field")]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Login"
|
text = "Login"
|
||||||
script = ExtResource("1_12w35")
|
script = ExtResource("1_12w35")
|
||||||
username_field = NodePath("../InputContainer/UsernameContainer/UsernameInput")
|
username_field = NodePath("../InputContainer/UsernameContainer/UsernameInput")
|
||||||
password_field = NodePath("../InputContainer/PasswordContainer/PasswordInput")
|
password_field = NodePath("../InputContainer/PasswordContainer/PasswordInput")
|
||||||
http_request = NodePath("HTTPRequest")
|
api_config = ExtResource("2_60hb8")
|
||||||
|
|
||||||
[node name="HTTPRequest" type="HTTPRequest" parent="Panel/VBoxContainer/Button"]
|
[node name="HTTPRequest" type="HTTPRequest" parent="Panel/VBoxContainer/Button"]
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
extends Button
|
|
||||||
|
|
||||||
@export var username_field: LineEdit
|
|
||||||
@export var password_field: LineEdit
|
|
||||||
@export var http_request: HTTPRequest
|
|
||||||
var url: String = "http://localhost:8080/api/v1/player/login"
|
|
||||||
var headers = ["Content-Type: application/json"]
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
if not username_field:
|
|
||||||
push_error("No Username Field set")
|
|
||||||
return
|
|
||||||
if not password_field:
|
|
||||||
push_error("No Password Field set")
|
|
||||||
return
|
|
||||||
if not http_request:
|
|
||||||
push_error("No Http Request set")
|
|
||||||
return
|
|
||||||
connect("pressed", login)
|
|
||||||
|
|
||||||
|
|
||||||
func login() -> void:
|
|
||||||
var login_data = {
|
|
||||||
"username": username_field.text,
|
|
||||||
"password": password_field.text,
|
|
||||||
}
|
|
||||||
var json = JSON.stringify(login_data)
|
|
||||||
http_request.connect("request_completed", on_login_response)
|
|
||||||
http_request.request(url, headers, HTTPClient.METHOD_POST, json)
|
|
||||||
|
|
||||||
|
|
||||||
func on_login_response(
|
|
||||||
_result: int,
|
|
||||||
_response_code: int,
|
|
||||||
_headers: PackedStringArray,
|
|
||||||
body: PackedByteArray,
|
|
||||||
) -> void:
|
|
||||||
print(body.get_string_from_utf8())
|
|
37
scripts/ui/login.gd
Normal file
37
scripts/ui/login.gd
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
extends Button
|
||||||
|
|
||||||
|
@export var username_field: LineEdit
|
||||||
|
@export var password_field: LineEdit
|
||||||
|
@export var api_config: ApiConfig
|
||||||
|
var api: ServerApi
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if not username_field:
|
||||||
|
push_error("No Username Field set")
|
||||||
|
return
|
||||||
|
if not password_field:
|
||||||
|
push_error("No Password Field set")
|
||||||
|
return
|
||||||
|
if not api_config:
|
||||||
|
push_error("No API Configuration provided")
|
||||||
|
return
|
||||||
|
api = ServerApi.new(api_config)
|
||||||
|
connect("pressed", login)
|
||||||
|
username_field.connect("pressed", login)
|
||||||
|
password_field.connect("pressed", login)
|
||||||
|
|
||||||
|
|
||||||
|
func login() -> void:
|
||||||
|
var login_data = PlayerLoginData.new()
|
||||||
|
login_data.username = username_field.text
|
||||||
|
login_data.password = password_field.text
|
||||||
|
api.player_login(login_data, on_success, on_error)
|
||||||
|
|
||||||
|
|
||||||
|
func on_success(a: ApiResponse) -> void:
|
||||||
|
print(a.body)
|
||||||
|
|
||||||
|
|
||||||
|
func on_error(b: ApiError) -> void:
|
||||||
|
print(b.response_code)
|
Loading…
Add table
Reference in a new issue