This commit is contained in:
Torsten 2024-12-22 14:02:54 +01:00
parent 1e22d28754
commit 6b926d3afe
126 changed files with 128562 additions and 104297 deletions

View file

@ -0,0 +1,3 @@
#godot-editor
Right upper corner in Menu selector window
![[Pasted image 20240204164006.png]]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
Sprite2D node
F => focus view on object
To get type information in code auto-completion, go to _Editor -> Editor Settings_, and in the _Text Editor -> Completion_ section, turn on the checkbox named _Add Type Hints_. You can also search for "type hints" in the search bar at the top of the window to find it quickly.
`Vector2` values have a function named `angle()` that calculates and returns the vector's angle in radians. If we assign that to the ship's rotation, it will turn to face its move direction.
```gdscript
var velocity = Vector2(480, -480)
func _process(delta: float) -> void:
position += velocity * delta
rotation = velocity.angle()
````

View file

@ -0,0 +1,21 @@
https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/to_space_and_beyond/taking_control
```gdscript
extends Sprite2D
var direction := Vector2(0,0)
var max_speed := 600.0
var velocity = Vector2(0, 0)
func _process(delta: float) -> void:
direction.x = Input.get_axis("move_left", "move_right")
direction.y = Input.get_axis("move_up", "move_down")
if direction.length() > 1.0:
direction = direction.normalized()
velocity = direction * max_speed
position += velocity * delta
if direction.length() > 0.0:
rotation = velocity.angle()
```

View file

@ -0,0 +1,19 @@
```python
func _process(delta: float) -> void:
var direction := Vector2(0, 0)
direction.x = Input.get_axis("move_left", "move_right")
direction.y = Input.get_axis("move_up", "move_down")
if direction.length() > 1.0:
direction = direction.normalized()
if Input.is_action_just_pressed("boost"):
max_speed = boost_speed
get_node("Timer").start()
velocity = direction * max_speed
position += velocity * delta
if direction.length() > 0.0:
rotation = velocity.angle()
```

View file

@ -0,0 +1,35 @@
```python
extends Sprite2D
var direction := Vector2(0,0)
var max_speed := 300.0
var velocity = Vector2(0, 0)
var boost_speed := 1500.0
var normal_speed := 300.0
var desired_velocity := Vector2()
var steering_factor := 10
func _process(delta: float) -> void:
var direction := Vector2(0, 0)
direction.x = Input.get_axis("move_left", "move_right")
direction.y = Input.get_axis("move_up", "move_down")
if direction.length() > 1.0:
direction = direction.normalized()
if Input.is_action_just_pressed("boost"):
max_speed = boost_speed
get_node("Timer").start()
desired_velocity = max_speed * direction
var steering = desired_velocity - velocity
velocity += steering * steering_factor * delta
position += velocity * delta
if direction.length() > 0.0:
rotation = velocity.angle()
func _on_timer_timeout() -> void:
max_speed = normal_speed
```

View file

@ -0,0 +1,9 @@
```python
extends Area2D
func _ready() -> void:
area_entered.connect(_on_area_entered)
func _on_area_entered(area_that_entred: Area2D) -> void:
queue_free()
```

View file

@ -0,0 +1,43 @@
````python
extends Area2D
var max_speed := 1200.0
var velocity := Vector2(0, 0)
var steering_factor := 3.0
var health := 10
func _ready() -> void:
area_entered.connect(_on_area_entered)
# This call updates the health bar to match the health variable when the
# game starts.
set_health(health)
func _process(delta: float) -> void:
var direction := Vector2(0, 0)
direction.x = Input.get_axis("move_left", "move_right")
direction.y = Input.get_axis("move_up", "move_down")
if direction.length() > 1.0:
direction = direction.normalized()
var desired_velocity := max_speed * direction
var steering := desired_velocity - velocity
velocity += steering * steering_factor * delta
position += velocity * delta
if velocity.length() > 0.0:
get_node("Sprite2D").rotation = velocity.angle()
func set_health(new_health: int) -> void:
health = new_health
get_node("UI/HealthBar").value = health
func _on_area_entered(area_that_entered: Area2D) -> void:
set_health(health + 10)
```

View file

@ -0,0 +1 @@
https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/telling_a_story/overview

View file

@ -0,0 +1,61 @@
```python
var inventory: Array[Dictionary] = [
{
"name": "sword",
"amount": 1,
},
{
"name": "potion",
"amount": 5,
},
{
"name": "potion",
"amount": 3,
},
{
"name": "coins",
"amount": 65,
},
]
```
[]
```python
var expressions := {
"happy" : preload("res://assets/emotion_happy.png"),
"regular" : preload("res://assets/emotion_regular.png"),
"sad" : preload("res://assets/emotion_sad.png"),
}
var dialogue_items: Array[Dictionary]= [
{
"expression": expressions["regular"],
"text": "I'm learning about Arrays..."
},
{
"expression": expressions["sad"],
"text": "... and it is a little bit complicated."
},
{
"expression": expressions["happy"],
"text": "Let's see if I got it right: an array is a list of values!",
},
{
"expression": expressions["regular"],
"text": "Did I get it right? Did I?",
},
{
"expression": expressions["happy"],
"text": "Hehe! Bye bye~!",
},
]
func show_text() -> void:
var current_item := dialogue_items[current_item_index]
rich_text_label.text = current_item["text"]
expression.texture = current_item["expression"]
```

View file

@ -0,0 +1 @@
A UI nodes' opacity is modified via the modulate property which controls the tinting of the node.

View file

@ -0,0 +1,28 @@
Tweens are created using a nodes' create_tween() method
```python
func slide_in() -> void:
var tween := create_tween()
tween.set_trans(Tween.TRANS_QUART)
tween.set_ease(Tween.EASE_OUT)
````
Running tweens in parallel via parallel method.
Modulate controls tinting, subproperty a is for alpha, meaning a target visibilty of 100%
```python
tween.parallel().tween_property(body, "modulate:a", 1.0, 0.2)
```
Full tween method:
```python
func slide_in() -> void:
var tween = create_tween()
tween.set_trans(Tween.TRANS_QUART)
tween.set_ease(Tween.EASE_OUT)
body.position.x = 200.0
tween.tween_property(body, "position:x", 0.0, 1.0) # Move x from 200 to 0
body.modulate.a = 0.0 # UI node visibility = 0
tween.parallel().tween_property(body, "modulate:a", 1.0, 1.0) # Animate visibility towards 100% (a=alpha)
```

View file

@ -0,0 +1,127 @@
Nothing new here, just replacement of TextureRect images
```python
extends Control
var expressions := {
"happy": preload ("res://assets/emotion_happy.png"),
"regular": preload ("res://assets/emotion_regular.png"),
"sad": preload ("res://assets/emotion_sad.png"),
}
var bodies := {
"sophia": preload ("res://assets/sophia.png"),
"pink": preload ("res://assets/pink.png")
}
## Data to display in the dialogue. Each dictionary in this array has three properties:
##
## - expression: a [code]Texture[/code] containing an expression
## - text: a [code]String[/code] containing the text the character says
## - character: a [code]Texture[/code] representing the character
var dialogue_items: Array[Dictionary] = [
{
"expression": expressions["regular"],
"text": "I've been studying arrays and dictionaries lately.",
"character": bodies["sophia"],
},
{
"expression": expressions["regular"],
"text": "Oh, nice. How has it been going?",
"character": bodies["pink"],
},
{
"expression": expressions["sad"],
"text": "Well... it's a little complicated!",
"character": bodies["sophia"],
},
{
"expression": expressions["sad"],
"text": "Oh!",
"character": bodies["pink"],
},
{
"expression": expressions["regular"],
"text": "It sure takes time to click at first.",
"character": bodies["pink"],
},
{
"expression": expressions["happy"],
"text": "If you keep at it, eventually, you'll get the hang of it!",
"character": bodies["pink"],
},
{
"expression": expressions["regular"],
"text": "Mhhh... I see. I'll keep at it, then.",
"character": bodies["sophia"],
},
{
"expression": expressions["happy"],
"text": "Thanks for the encouragement. Time to LEARN!!!",
"character": bodies["sophia"],
},
]
var current_item_index := 0
@onready var rich_text_label: RichTextLabel = %RichTextLabel
@onready var next_button: Button = %NextButton
@onready var audio_stream_player: AudioStreamPlayer = %AudioStreamPlayer
@onready var body: TextureRect = %Body
@onready var expression_texture_rect: TextureRect = %Expression
func _ready() -> void:
show_text()
next_button.pressed.connect(advance)
func show_text() -> void:
# We first extract data for the current dialogue item and apply it to the UI
# elements.
var current_item := dialogue_items[current_item_index]
rich_text_label.text = current_item["text"]
expression_texture_rect.texture = current_item["expression"]
body.texture = current_item["character"]
# We then hide the text and animate it appearing.
rich_text_label.visible_ratio = 0.0
var tween := create_tween()
var text_appearing_duration: float = current_item["text"].length() / 30.0
tween.tween_property(rich_text_label, "visible_ratio", 1.0, text_appearing_duration)
# We randomize the audio playback's start time to make it sound different
# every time and play the sound until the text finishes displaying.
var sound_max_length := audio_stream_player.stream.get_length() - text_appearing_duration
var sound_start_position := randf() * sound_max_length
audio_stream_player.play(sound_start_position)
tween.finished.connect(audio_stream_player.stop)
# We animate the character sliding in.
slide_in()
## Advances the dialogue to the next item or quits the game if there are no more
## items.
func advance() -> void:
current_item_index += 1
if current_item_index == dialogue_items.size():
get_tree().quit()
else:
show_text()
## Animates the character sliding in.
func slide_in() -> void:
var slide_tween := create_tween()
slide_tween.set_ease(Tween.EASE_OUT)
body.position.x = get_viewport_rect().size.x / 7
slide_tween.tween_property(body, "position:x", 0, 0.3)
body.modulate.a = 0
slide_tween.parallel().tween_property(body, "modulate:a", 1, 0.2
```

View file

@ -0,0 +1,81 @@
ProjectProject Settings...Input Map
Define action + button there
![[Pasted image 20240713073756.png]]
Next, select a button, and open Shortcut section
![[Pasted image 20240713073933.png]]
Click the <empty> Shortcut field, then select New Shortcut
The `Shortcut` resource takes an array of inputs as its data because in case you'd want to trigger the same button with multiple different inputs or input actions.
sadfs
![[Pasted image 20240713074212.png]]
![[Pasted image 20240713074345.png]]
Click on the created InputEventAction to expand it.
![[Pasted image 20240713080012.png]]
Set action to advance_text ( The action we created in the very first screenshot of this page)
![[Pasted image 20240713080125.png]]
Now all input defined for advance_text action will trigger the button.
Disabling button until tween finished
```python
func show_text() -> void:
# We first extract data for the current dialogue item and apply it to the UI
# elements.
# ...
next_button.disabled = true
tween.finished.connect(func() -> void:
next_button.disabled = false
)
```
Note to self: It seems to be possible to connect an event to more than 1 function. Here the full function from the tutorial. Note the tween.connect re-use
```python
func show_text() -> void:
# We first extract data for the current dialogue item and apply it to the UI
# elements.
var current_item := dialogue_items[current_item_index]
rich_text_label.text = current_item["text"]
expression_texture_rect.texture = current_item["expression"]
body.texture = current_item["character"]
# We then hide the text and animate it appearing.
rich_text_label.visible_ratio = 0.0
var tween := create_tween()
var text_appearing_duration: float = current_item["text"].length() / 30.0
tween.tween_property(rich_text_label, "visible_ratio", 1.0, text_appearing_duration)
# We randomize the audio playback's start time to make it sound different
# every time and play the sound until the text finishes displaying.
var sound_max_length := audio_stream_player.stream.get_length() - text_appearing_duration
var sound_start_position := randf() * sound_max_length
audio_stream_player.play(sound_start_position)
tween.finished.connect(audio_stream_player.stop)
# We animate the character sliding in.
slide_in()
# Finally, we disable the next button until the text finishes displaying.
next_button.disabled = true
tween.finished.connect(func() -> void:
next_button.disabled = false
)
```

View file

@ -0,0 +1,25 @@
```bbcode
BBCode is a simple [b]markup language[/b] that is [i]supposed[/i] to be simpler than [b]HTML[/b].
```
Examples
```bbcode
[b]bold[/b]
[i]italics[/i]
[u]underline[/u]
[s]strikethrough[/s]
[url=https://godotengine.org]link[/url]
[shake]This text shakes[/shake] makes the text shake.
[wave]This text is wavy[/wave] makes the text animate in a wave pattern.
[tornado freq=1.0]This text is even more wavy[/tornado] makes the text rotate like leaves. The freq parameter controls the speed of the rotation.
[rainbow val=0.8]This text is colored[/rainbow] tints the text and animates it using rainbow colors. The val parameter controls the saturation, that is to say, how intense the colors are.
```
Works only with the `RichTextLabel` node, and needs to be activated for the node (performance reasons)
![[Pasted image 20240719083713.png]]

View file

@ -0,0 +1,10 @@
`HBoxContainer`
`VBoxContainer`
`GridContainer`
`Control`
Anchors define corner position of a control node
TextureRect => Like Sprite2D, but for UI
You can drag an asset file on top of the TextureRect
![[Pasted image 20240615074811.png]]

View file

@ -0,0 +1 @@
https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/telling_a_story/first_ui_exploration

View file

@ -0,0 +1,5 @@
RichtextLabel
TextureRect
Button
ColorRect
AudioStreamPlayer

View file

@ -0,0 +1,11 @@
![[Pasted image 20240623095140.png]]![[Pasted image 20240623095146.png]]
![[Pasted image 20240623095156.png]]
.tres is text-based, recommnended and git-friendly
Theme Editor Overview: https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/telling_a_story/all_theme_editor_areas
Beware of not working copy/paste in theme styles. Workaround is Make unique and saving / loding in new component
1.) Create Theme Resource
2.) Modifiy
3.) Assign to uppermost UI Control Node

View file

@ -0,0 +1,28 @@
https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/telling_a_story/your_first_words
Scene Unique Nodes
`To mark a node as unique, right-click it in the _Scene_ dock and select _Access As Unique Name_ in the dropdown menu.`
![[Pasted image 20240625081421.png]]
Restriction: They can only be addressed from the scene their uniqueness is defined in.
`There's also an important limitation to this feature: nodes with a scene-unique name only exist in the scene they're marked in. For example, if we mark our `RichTextLabel` in the scene dialogue.tscn as unique, we can't reference it from another scene.
You also can't mark nodes instantiated at runtime as unique. That's why we learned the more general way of getting nodes first with `get_node()`.`
Script shortcut:
1.) Mark unique nodes
2.) Drag and hold over script editor
3.) Press and hold Ctrl
4.) Release and see created initialization code
![[Pasted image 20240625081825.png]]
Quitting the game
![[Pasted image 20240625083616.png]]
`get_tree().quit()`

View file

@ -0,0 +1,19 @@
![[Pasted image 20240626083938.png]]
`Do you remember how to find the name of the property in code? You can hover over the property in the editor, and a tooltip will show you the code name.`
````
## Draws the current text to the rich text element
func show_text() -> void:
var current_item := dialogue_items[current_item_index]
rich_text_label.text = current_item
rich_text_label.visible_ratio = 0.0 # <--rich
````
````
func show_text() -> void:
# ...
var tween := create_tween()
var text_appearing_duration := 1.2
tween.tween_property(rich_text_label, "visible_ratio", 1.0, text_appearing_duration)
````

View file

@ -0,0 +1,16 @@
Generic:
AudioStream
Positional:
AudioStream2D
AudioStream3D
`
```
var text_appearing_duration = 1.2 # value is in seconds
var sound_max_length := audio_stream_player.stream.get_length() - text_appearing_duration
var sound_start_position := randf() * sound_max_length
audio_stream_player.play(sound_start_position)
tween.finished.connect(audio_stream_player.stop)
```

View file

@ -0,0 +1,50 @@
```
extends Control
## An array of strings. Each string contains the text we want the character to
## say.
var dialogue_items: Array[String] = [
"I'm learning about Arrays...",
"...and it is a little bit complicated.",
"Let's see if I got it right: an array is a list of values!",
"Did I get it right? Did I?",
"Hehe! Bye bye~!",
]
## Holds the index of the currently displayed text
var current_item_index := 0
@onready var rich_text_label: RichTextLabel = %RichTextLabel
@onready var next_button: Button = %NextButton
@onready var audio_stream_player: AudioStreamPlayer = %AudioStreamPlayer
func _ready() -> void:
show_text()
next_button.pressed.connect(advance)
## Draws the current text to the rich text element
func show_text() -> void:
var current_item := dialogue_items[current_item_index]
rich_text_label.text = current_item
# We animate the text appearing letter by letter.
rich_text_label.visible_ratio = 0.0
var tween := create_tween()
var text_appearing_duration := 1.2
tween.tween_property(rich_text_label, "visible_ratio", 1.0, text_appearing_duration)
# This is where we play the audio. We randomize the audio playback's start
# time to make it sound different every time.
var sound_max_length := audio_stream_player.stream.get_length() - text_appearing_duration
var sound_start_position := randf() * sound_max_length
audio_stream_player.play(sound_start_position)
# We stop the audio when the text finishes appearing.
tween.finished.connect(audio_stream_player.stop)
func advance() -> void:
current_item_index += 1
# If we reached the end of the dialogue, we quit the game. Otherwise, we
# show the next dialogue line.
if current_item_index == dialogue_items.size():
get_tree().quit()
else:
show_text()
```

View file

@ -0,0 +1,3 @@
1. Arrange UI nodes in rows and columns using `Container` nodes.
2. Change the texture of a `TextureRect` node programmatically.
3. Map different image textures to different strings representing the expressions using [dictionaries](https://school.gdquest.com/glossary/dictionary).

View file

@ -0,0 +1,6 @@
```python
button_sophia.pressed.connect(func() -> void:
body.texture = bodies["sophia"]
)
```

View file

@ -0,0 +1,37 @@
In this lesson, you will write the code to change the expressions of the characters when clicking the buttons.
Loading image at compile time (advantage of seeing error if file doesnt exist)
```python
var image := preload("res:://image.png")
```
Loading at runtime on demand:
```python
load("res:://image.png")
```
Preloadingloading into a dictionary:
```python
var bodies := {
"sophia": preload("res://assets/sophia.png"),
"pink": preload("res://assets/pink.png")
}
```
Dont forget to multiselect + drag&drop assets into code editor, then multi-cursor correct using
either Alt+Ctrl+Mouseclicks, or Alt+Shift+Cursor Up/Down
*Setting TextureRect texture*
```python
func _ready() -> void:
body.texture = bodies["pink"]
expression.texture = expressions["happy"]
```
It's not mandatory to use square brackets to access dictionary values. You can also use the `.` operator. For example, writing `bodies["sophia"]` is equivalent to `bodies.sophia`.

View file

@ -0,0 +1,17 @@
```python
func create_button_pink() -> void:
var button := Button.new()
row_bodies.add_child(button)
```
```python
func create_buttons() -> void:
for current_body: String in bodies:
var button := Button.new()
row_bodies.add_child(button)
button.text = current_body.capitalize()
button.pressed.connect(func()-> void:
body.texture = bodies[current_body]
)
```

View file

@ -0,0 +1,10 @@
**Topics**:
Control Nodes
Label Node
AudioStreamPlayer Node
Arrays
Dictionaries
Dictionaries inside Arrays
Theming
Iterative Design

View file

@ -0,0 +1 @@
![[Pasted image 20240720081419.png]]

View file

@ -0,0 +1,12 @@
The goal: Going from linear dialogue display to integrating player choice
https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/start_a_dialogue/refactoring_the_dialogue#preparing-to-update-the-dialogue-scene
Designing the interface to include choice buttons
![[Pasted image 20240720075840.png]]
![[Pasted image 20240720080105.png]]
Sizing examples:
![[Pasted image 20240720081104.png]]

View file

@ -0,0 +1,18 @@
```python
extends Control
@onready var http_request: HTTPRequest = $btn_login/HTTPRequest
@onready var te_result: TextEdit = $te_result
var url = "https://meowfacts.herokuapp.com/"
func _on_btn_login_button_up() -> void:
var headers = ["Content-Type: application/json"]
http_request.request(url, headers, HTTPClient.Method.METHOD_GET)
func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
var data = JSON.parse_string(body.get_string_from_utf8())
te_result.text = data.data[0]
```
![[Pasted image 20241222121529.png]]

View file

@ -0,0 +1,11 @@
```
# Find out printer URI
torsten@Demogorgon:~$ lpinfo --include-schemes dnssd -v
network dnssd://Brother%20HL-L8260CDW%20series._ipp._tcp.local/?uuid=e3248000-80ce-11db-8000-b42200d990c3
# Publicize
lpadmin -p BrotherManualEntry -v dnssd://Brother%20HL-L8260CDW%20series._ipp._tcp.local/?uuid=e3248000-80ce-11db-8000-b42200d990c3
```
Find cups using localhost address http://localhost:631/

View file

@ -0,0 +1,7 @@
```
vim ~/.vimrc
```
```
set nocompatible
```

View file

@ -0,0 +1,68 @@
Source:
[Setup CodeIgniter — Devilbox 1.0 documentation](https://devilbox.readthedocs.io/en/latest/examples/setup-codeigniter.html?highlight=CodeIgniter)
Prerequisites: Have correct docker compose installed:
```bash
sudo apt install docker-compose-v2
```
Download Devilbox
Remove unwanted hosts from docker-compose.yml file
copy env file to .env
Set TLD_SUFFIX=local
In /etc/hosts, add 127.0.01 with application URL:
127.0.0.1 verleihapp.local
Start docker container: sudo docker compose up -d
Enter Container: ./shell.sh
Install CodeIgniter4 and create htdocs link:
```bash
mkdir myproject
cd myproject
composer create-project codeigniter4/appstarter myprojectapp
ln -s myprojectapp/public htdocs
```
.env settings
```php
CI_ENVIRONMENT = development (instead of production)
app.baseURL = 'http://myproject.local'
database.default.hostname = myproject.local
database.default.database = myproject
database.default.username = root
database.default.password =
database.default.DBDriver = MySQLi
database.default.DBPrefix =
database.default.port = 3306
```
Access mysql from inside container
```bash
./shell.sh
mysql -h 127.0.0.1 -u root
CREATE DATABASE godotweb;
```
https://shield.codeigniter.com/getting_started/install/

View file

@ -0,0 +1,2 @@
docker-compose up -d
./shell.sh

View file

@ -0,0 +1,5 @@
```bash
$ grep -R CI_VERSION|grep const
framework/system/CodeIgniter.php: public const CI_VERSION = '4.5.5';
```

View file

@ -0,0 +1,62 @@
Inside Devilbox container:
```bash
devilbox@php-8.1.14 in /shared/httpd $ pwd
/shared/httpd
mkdir myproject
cd myproject
composer create-project codeigniter4/appstarter myprojectapp
ln -s myprojectapp/public htdocs
```
Setup db settings in .env:
```txt
CI_ENVIRONMENT = development
#--------------------------------------------------------------------
# APP
#--------------------------------------------------------------------
app.baseURL = 'http://godotweb.local'
#--------------------------------------------------------------------
# DATABASE
#--------------------------------------------------------------------
database.default.hostname = godotweb.local
database.default.database = godotweb
database.default.username = root
database.default.password =
database.default.DBDriver = MySQLi
database.default.DBPrefix =
database.default.port = 3306
```
Add shield:
```bash
composer require codeigniter4/shield
```
In case of issues, see installation.
```bash
composer config minimum-stability dev
composer config prefer-stable true
```
```bash
php spark shield:setup
```
####
SET DEFAULT LOCALE
Config/App.php
```php
public bool $negotiateLocale = true;
public string $defaultLocale = 'en';
public array $supportedLocales = ['en', 'de'];
```

View file

@ -0,0 +1,146 @@
```bash
php spark make:controller Api/Auth --suffix --restful
```
Routes.php
```php
//Get
$routes->get("logout", "AuthController::logout", ["filter" => "apiauth"]);
```
Api/AuthController.php
```php
<?php
namespace App\Controllers\Api;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\RESTful\ResourceController;
use CodeIgniter\Shield\Models\UserModel;
use CodeIgniter\Shield\Entities\User;
class AuthController extends ResourceController
{
//Post
public function register()
{
$rules = [
"username" => "required|is_unique[users.username]",
"email" => "required|valid_email|is_unique[auth_identities.secret]", // gets written to secret field in user table
"password" => "required" // gets written to secret 2 field in user table
];
if (!$this->validate($rules))
{
return $this->respondCreated([
"status" => false,
"message" => $this->validator->getErrors(),
"data" => []
]);
}
$um = new UserModel(); // coming from Shield
$userEntity = new User([
"username" => $this->request->getVar("username"),
"email" => $this->request->getVar("email"),
"password" => $this->request->getVar("password")
]);
$um->save($userEntity);
return $this->respondCreated( [
"status" => true,
"message" => "User saved successfully",
"data" => []
]);
}
//////////////////////////////////////
// Post
public function login()
{
if(auth()->loggedIn()) {
auth()->logout();
}
$rules = [
"email" => "required|valid_email",
"password" => "required"
];
if (!$this->validate($rules)) {
return $this->respondCreated([
"status" => false,
"message" => $this->validator->getErrors(),
"data" => []
]);
}
// success
$credentials = [
"email" => $this->request->getVar("email"),
"password" => $this->request->getVar("password")
];
$loginAttempt = auth()->attempt($credentials);
if (!$loginAttempt->isOK()) {
return $this->respondCreated([
"status" => false,
"message" => "Invalid login details",
"data" => []
]);
}
// We have a valid data set
$um = new UserModel();
$userData = $um->findById(auth()->id());
$token = $userData->generateAccessToken("thisismysecretkey");
$auth_token = $token->raw_token; // Only available after generation
return $this->respondCreated( [
"status" => true,
"message" => "User logged in successfully",
"data" => [
"token" => $auth_token
]
]);
}
////////////////////////
public function logout()
{
auth()->logout();
auth()->user()->revokeAllAccessTokens();
return $this->respondCreated([
"status" => true,
"message" => "User logged out successfully",
"data" => []
]);
}
//Get
public function profile()
{
$userId = auth()->id();
return $this->respondCreated([
"status" => true,
"message" => "Profile api called",
"data" => []
]);
}
public function accessDenied()
{
return $this->respondCreated([
"status" => false,
"message" => "Invalid access",
"data" => []
]);
}
}
```

View file

@ -0,0 +1,22 @@
https://www.youtube.com/watch?v=_ZRHt-fWJpc&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=29
app/Config/Routes.php
Remember: Shield added =>
```php
service('auth')->routes($routes)
```
```php
// API routes (put below service('auth'))
$routes->group("api", ["namespace" => "App\Controller\Api"], function($routes) {
$routes->post("register", "AuthController::register");
$routes->post("login", "AuthController::login");
$routes->get("profile", "AuthController::profile");
$routes->get("logout", "AuthController::logout", ["filter"=>"apiauth"]);
//$routes->post("add-project", "ProjectController::addProject");
//$routes->get("list-projects", "ProjectController::listProjects");
//$routes->delete("delete-project/(:num)", "ProjectController::deleteProject/$1");
});
```

View file

@ -0,0 +1,46 @@
https://www.youtube.com/watch?v=USqOc-GCA4s&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=32
Create AuthFilter.php
```bash
php spark make:filter Auth --suffix
```
Define before method in AuthFilter.php
```php
public function before(RequestInterface $request, $arguments = null)
{
helper("auth");
if (!auth("tokens")->loggedIn()) {
return redirect()->to(base_url("api/ invalid-access")); // need to create invalid-access route
}
}
```
Routes.php
```php
// API ROUTES
$routes->group("api",["namespace]=> "App\Controllers\Api"], function($routes) {
...
$routes->get("invalid-access", "AuthController::accessDenied");
§routes->get('demo-method", "AuthController::demoMethod",["filter" => "apiauth"]); // Filter Name is defined in aliases array in Filters.php below.
...
```
Add filter to app/Config/Filters.php
```php
use App\Filters\AuthFilter;
public array $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'apiauth' => AuthFilter::class
]
```
Authorize request by adding field "Authorization" with value "Bearer xxxxxx" in Request Header:
![[Pasted image 20241127140801.png]]

View file

@ -0,0 +1,5 @@
![[Pasted image 20241214094809.png]]
![[Pasted image 20241214094906.png]]
600933326ac42a7f1669bfcaceb4bf1f973c8f64f54dccff9d6c424a9c2c0e5d

View file

@ -0,0 +1,41 @@
```bash
php spark make:model Board --suffix
php spark make:controller Api/Board --suffix --restful
```
Add allowed fields to model:
(BoardModel.php)
```php
$allowedFields = ["name"];
```
```php
php spark make:migration create_boards
```
Migration file:
```php
//up:
$this->forge->addField([
"id" => [ "type"=>"INT", "constraint"=>5, "auto increment" => true, "unsigned" => true ],
"name" => [ "type"=>"VARCHAR", "constraint" => 120, "null" => false ]
]);
$this->forge->addPrimaryKey("id);
$this->forge->createTable("boards");
//down:
$this->forge->dropTable("boards");
```
```bash
php spark migrate:status
php spark migrate
```
Create seeder ?

View file

@ -0,0 +1,40 @@
Inside Devilbox container:
```bash
devilbox@php-8.1.14 in /shared/httpd $ pwd
/shared/httpd
mkdir myproject
cd myproject
composer create-project codeigniter4/appstarter myprojectapp
ln -s myprojectapp/public htdocs
```
Add shield:
```bash
composer require codeigniter4/shield
```
In case of issues, see installation.
```bash
composer config minimum-stability dev
composer config prefer-stable true
```
```bash
php spark shield:setup
```
####
SET DEFAULT LOCALE
Config/App.php
```php
public bool $negotiateLocale = true;
public string $defaultLocale = 'en';
public array $supportedLocales = ['en', 'de'];
```

View file

@ -0,0 +1,32 @@
```bash
php spark make:scaffold Product
```
Creates Controller, Model, Migration, Seeder
```php
//up:
$this->forge->addField((["id"=>
["type"=>"INT",
"constraint"=>5,
"auto increment" => true,
"unsigned" => true
],
"name" => ["type"=>"VARCHAR".
"constraint" => 120,
"null" => false
]
]
));
$this->forge->addPrimaryKey("id);
$this->forge->createTable("students");
//down:
$this->forge->dropTable("students");
```
```bash
php spark migrate:status
php spark migrate
```

View file

@ -0,0 +1,27 @@
https://www.youtube.com/watch?v=QaNGT-hj0so&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=8
![[Pasted image 20241112083749.png]]
![[Pasted image 20241112084116.png]]
Add route to Controller+method in Config/Routes.php:
![[Pasted image 20241112084252.png]]
Example for using where clause ( second parameter in update method ):
![[Pasted image 20241112084920.png]]
Delete:
![[Pasted image 20241112085347.png]]
Select all:
![[Pasted image 20241112090019.png]]
![[Pasted image 20241112090048.png]]
Select with where clause
![[Pasted image 20241112090400.png]]
For single row result, we directly load into Array ( instead of Array of Array)
![[Pasted image 20241112090526.png]]

View file

@ -0,0 +1,36 @@
https://www.youtube.com/watch?v=4_MdtVqfH1o&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=9
```bash
php spark make:model Student --suffix
```
Creates StudentModel.php in app/Models
Add allowedFields
![[Pasted image 20241113082554.png]]
Add Model to Controller
![[Pasted image 20241113082703.png]]
![[Pasted image 20241113083139.png]]
add to routes
```php
$routes->get("add-student", "StudentController::insertStudent");
```
update example (condition, data):
![[Pasted image 20241113083603.png]]
delete:
![[Pasted image 20241113083823.png]]
get all :
![[Pasted image 20241113084056.png]]
get single with generic condition:
![[Pasted image 20241113084645.png]]

View file

@ -0,0 +1,23 @@
For creating dummy data
Seeder files go into app/Database/Seeds
```bash
php spark make:seeder Student --suffix
```
fill run method
![[Pasted image 20241113085432.png]]
Run seeder file
```bash
php spark db:seed StudentSeeder
```
Mass dummy data using Factory / Faker
![[Pasted image 20241113085948.png]]
![[Pasted image 20241113090300.png]]

View file

@ -0,0 +1,33 @@
https://www.youtube.com/watch?v=WGtj1Ihlf10&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=11
get, post, put/patch, delete
![[Pasted image 20241114084652.png]]
![[Pasted image 20241114084843.png]]
![[Pasted image 20241114084916.png]]
![[Pasted image 20241114084945.png]]
Routes.php
![[Pasted image 20241114085118.png]]
Test using postman
![[Pasted image 20241114085726.png]]
![[Pasted image 20241114085814.png]]
![[Pasted image 20241114085826.png]]
Remove debugging noise in payload by switching to production environment in .env
Method spoofing ( changing POST into DELETE request )
![[Pasted image 20241114090303.png]]

View file

@ -0,0 +1,29 @@
https://www.youtube.com/watch?v=4neOZhzn-qI&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=12
![[Pasted image 20241115082725.png]]
app/Config/Routes.php
![[Pasted image 20241115082852.png]]
http://godotweb.local/product/add
http://godotweb.local/product/list
http://godotweb.local/category/add
http://godotweb.local/category/list
**ROUTE NAMESPACE**
Create subfolder in Controllers directory
```bash
php spark make:controller Admin/Admin --suffix
```
![[Pasted image 20241115084552.png]]
```bash
php spark make:controller Seller/Seller --suffix
```
![[Pasted image 20241115084636.png]]
Access Controller in subfolder using namespace parameter in routes group method:
![[Pasted image 20241115085338.png]]

View file

@ -0,0 +1,12 @@
https://www.youtube.com/watch?v=rlIKHZ5Nq2U&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=14
app/Filters
app/Config/Filters.php
![[Pasted image 20241116111944.png]]
```bash
php spark make:filter Auth --suffix
```
![[Pasted image 20241116113439.png]]
![[Pasted image 20241116113532.png]]

View file

@ -0,0 +1,11 @@
```bash
php spark make:controller Api/Api --suffix --restful
```
#17/38
== > Created controller ApiController (in Controlles subfolder Api) will have 9 methods.
( WHich the tutorial doesnt use, basically only difference to standard Controllers is usage of ResourceController)
![[Pasted image 20241117112106.png]]

View file

@ -0,0 +1,8 @@
Routes.php
![[Pasted image 20241117113037.png]]
Route parameter
![[Pasted image 20241117113342.png]]

View file

@ -0,0 +1,51 @@
![[Pasted image 20241117113802.png]]
```php
$studentObject = new StudentModel();
$imageFile = $this->request->getFile("profile_image");
// only go through image storing logic if image actually exists in payload
if (isset($imageFile) && !empty($imageFile)) { // with image
$imageName = $imageFile->getName();
$tempArray = explode(".", $imageName);
$newImageName = round(microtime(true)) . "" . end($tempArray);
if($imageFile->move("images/" . $newImageName)) { // move automatically creates images folder if not exists
$data = [
"name" => $this->request->getVar("name"),
"email" => $this->request->getVar("email"),
"profile_image" => "images/" . $newImageName
];
}
else {
$response = [
"status" => false,
"message" => "Image Upload failed",
"data" => []
];
}
}
else { // without image
$data = [
"name" => $this->request->getVar("name"),
"email" => $this->request->getVar("email"),
];
}
// store student
if ($studentObject->insert($data)) {
$response = [
"status" => true,
"message" => "Student saved successfully",
"data" => []
];
} else {
$response = [
"status" => false,
"message" => "Failed to save student",
"data" => []
];
}
```

View file

@ -0,0 +1,8 @@
![[Pasted image 20241118085649.png]]
**![[Pasted image 20241118090014.png]]
![[Pasted image 20241118090047.png]]

View file

@ -0,0 +1 @@
https://www.youtube.com/watch?v=0aBmb1aaj_Q&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=22

View file

@ -0,0 +1,2 @@
https://www.youtube.com/watch?v=hX612xp_Gtw&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=23

View file

@ -0,0 +1,35 @@
```bash
php spark make:controller Api/Auth --suffix --restful
php spark make:controller Api/Project --suffix --restful
```
![[Pasted image 20241123100630.png]]
![[Pasted image 20241123100706.png]]
![[Pasted image 20241123100734.png]]
![[Pasted image 20241123100756.png]]
--- PROJECT CONTROLLER
![[Pasted image 20241123100834.png]]
![[Pasted image 20241123100851.png]]
![[Pasted image 20241123100913.png]]
ADD SHIELD USERMODEL
![[Pasted image 20241123101019.png]]
```bash
php spark make:model Project --suffix
```
extend allowedFields, e.g.
```php
$allowedFields = ["user_id","name"]
```
Add Project Model to ProjectController
```php
use App\Models\ProjectModel;
```

View file

@ -0,0 +1,22 @@
https://www.youtube.com/watch?v=_ZRHt-fWJpc&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=29
app/Config/Routes.php
Remember: Shield added =>
```php
service('auth')->routes($routes)
```
```php
// API routes (put below service('auth'))
$routes->group("api", ["namespace" => "App\Controller\Api"], function($routes) {
$routes->post("register", "AuthController::register");
$routes->post("login", "AuthController::login");
$routes->get("profile", "AuthController::profile");
$routes->get("logout", "AuthController::logout");
$routes->post("add-project", "ProjectController::addProject");
$routes->get("list-projects", "ProjectController::listProjects");
$routes->delete("delete-project/(:num)", "ProjectController::deleteProject/$1");
});
```

View file

@ -0,0 +1,93 @@
AuthController:
```php
use CodeIgniter\Restful\ResourceController;
use CodeIgniter\Shield\Models\UserModel;
use CodeIgniter\Shield\Entities\User;
class AuthController extends ResourceController
{
//Post
public function register()
{
$rules = [
"username" => "required|is_unique[users.username]",
"email" => "required|valid_email|is_unique[auth_identities.secret]", // gets written to secret field in user table
"password" => "required" // gets written to secret 2 field in user table
];
if (!$this->validate($rules)) {
$response = [
"status" => false,
"message" => $this->validator->getErrors(),
"data" => []
];
} else {
$userObject = new UserModel(); // coming from Shield
$userEntityObject = new User([
"username" => $this->request->getVar("username"),
"email" => $this->request->getVar("email"),
"password" => $this->request->getVar("password")
]);
$userObject->save($userEntityObject);
$response = [
"status" => true,
"message" => "User saved successfully",
"data" => []
]
}
return $this->respondCreated($response);
}
}
```
Note: shield stores users in auth_identies. Email address should be stored in field secret.
https://onlinewebtutorblog.com/codeigniter-4-restful-api-using-shield-authentication/
My version:
```php
use CodeIgniter\Restful\ResourceController;
use CodeIgniter\Shield\Models\UserModel;
use CodeIgniter\Shield\Entities\User;
class AuthController extends ResourceController
{
//Post
public function register()
{
$rules = [
"username" => "required|is_unique[users.username]",
"email" => "required|valid_email|is_unique[auth_identities.secret]", // gets written to secret field in user table
"password" => "required" // gets written to secret 2 field in user table
];
if (!$this->validate($rules)) {
return $this->respondCreated([
"status" => false,
"message" => $this->validator->getErrors(),
"data" => []
]
);
$um = new UserModel(); // coming from Shield
$userEntity = new User([
"username" => $this->request->getVar("username"),
"email" => $this->request->getVar("email"),
"password" => $this->request->getVar("password")
]);
$um->save($userEntity);
return $this->respondCreated( [
"status" => true,
"message" => "User saved successfully",
"data" => []
]);
}
}
```

View file

@ -0,0 +1,123 @@
https://www.youtube.com/watch?v=XJHFtfQQmRs&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=31
https://onlinewebtutorblog.com/codeigniter-4-restful-api-using-shield-authentication/
In AuthController::login:
```php
// Post
public function login()
{
if(auth()->loggedIn()){
auth()->logout();
}
$rules = [
"email" => "required|valid_email",
"password" => "required"
];
if (!$this->validate($rules)) {
$response = [
"status" => false,
"message" => $this->validator->getErrors(),
"data" => []
];
} else {
// success
$credentials = [
"email" => $this->request->getVar("email"),
"password" => $this->request->getVar("password")
];
$loginAttempt = auth()->attempt($credentials);
if (!$loginAttempt->isOK()) {
$response = [
"status" => false,
"message" => "Invalid login details",
"data" => []
];
} else {
// We have a valid data set
$userObject = new UserModel();
$userData = $userObject->findById(auth()->id());
$token = $userData->generateAccessToken("thisismysecretkey");
$auth_token = $token->raw_token; // Only available after generation
$response = [
"status" => true,
"message" => "User logged in successfully",
"data" => [
"token" => $auth_token
]
];
}
}
return $this->respondCreated($response);
}
```
https://shield.codeigniter.com/references/authentication/tokens/
My version:
```php
// Post
public function login()
{
if(auth()->loggedIn()){
auth()->logout();
}
$rules = [
"email" => "required|valid_email",
"password" => "required"
];
if (!$this->validate($rules)) {
return $this->respondCreated([
"status" => false,
"message" => $this->validator->getErrors(),
"data" => []
]);
// success
$credentials = [
"email" => $this->request->getVar("email"),
"password" => $this->request->getVar("password")
];
$loginAttempt = auth()->attempt($credentials);
if (!$loginAttempt->isOK()) {
return $this->respondCreated([
"status" => false,
"message" => "Invalid login details",
"data" => []
]);
// We have a valid data set
$um = new UserModel();
$userData = $um->findById(auth()->id());
$token = $userData->generateAccessToken("thisismysecretkey");
$auth_token = $token->raw_token; // Only available after generation
return $this->respondCreated( [
"status" => true,
"message" => "User logged in successfully",
"data" => [
"token" => $auth_token
] );
}
```

View file

@ -0,0 +1,61 @@
https://www.youtube.com/watch?v=USqOc-GCA4s&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=32
Create AuthFilter.php
```bash
php spark make:filter Auth --suffix
```
Define before method in AuthFilter.php
```php
public function before(RequestInterface $request, $arguments = null)
{
helper("auth");
if (!auth("tokens")->loggedIn()) {
return redirect()->to(base_url("api/ invalid-access")); // need to create invalid-access route
}
}
```
Routes.php
```php
// API ROUTES
$routes->group("api",["namespace]=> "App\Controllers\Api"], function($routes) {
...
$routes->get("invalid-access", "AuthController::accessDenied");
§routes->get('demo-method", "AuthController::demoMethod",["filter" => "apiauth"]); // Filter Name is defined in aliases array in Filters.php below.
...
```
Define accessDenied method in app/Controllers/Api/AuthController.php
```php
...
public function accessDenied()
{
return $this->respondCreated([
"status" => false,
"message" => "Invalid access",
"data" => []
]);
}
...
```
Add filter to app/Config/Filters.php
```php
use App\Filters\AuthFilter;
public array $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'apiauth' => AuthFilter::class
]
```
Authorize request by adding field "Authorization" with value "Bearer xxxxxx" in Request Header:
![[Pasted image 20241127140801.png]]

View file

@ -0,0 +1,91 @@
https://www.youtube.com/watch?v=f0NifEgwh30&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=33
```php
auth()->user()
```
```php
$userId = auth()->id();
```
![[Pasted image 20241129092837.png]]
AuthController
```php
//...
// Post
public function login()
{
if(auth()->loggedIn()){
auth()->logout();
}
$rules = [
"email" => "required|valid_email",
"password" => "required"
];
if (!$this->validate($rules)) {
$response = [
"status" => false,
"message" => $this->validator->getErrors(),
"data" => []
];
} else {
// success
$credentials = [
"email" => $this->request->getVar("email"),
"password" => $this->request->getVar("password")
];
$loginAttempt = auth()->attempt($credentials);
if (!$loginAttempt->isOK()) {
$response = [
"status" => false,
"message" => "Invalid login details",
"data" => []
];
} else {
// to test: directly getting $userObject via auth()->user()
// We have a valid data set
$userObject = new UserModel();
$userData = $userObject->findById(auth()->id());
$token = $userData->generateAccessToken("thisismysecretkey");
$auth_token = $token->raw_token;
$response = [
"status" => true,
"message" => "User logged in successfully",
"data" => [
"token" => $auth_token
]
];
}
}
return $this->respondCreated($response);
}
//...
```

View file

@ -0,0 +1,21 @@
https://www.youtube.com/watch?v=H1KiXEsmsCs&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=34
Routes.php:
```php
//Get
$routes->get("logout", "AuthController::logout", ["filter" => "apiauth"]);
```
AuthController.php
```php
public function logout() {
auth()->logout();
auth()->user()->revokeAllAccessTokens();
return $this->respondCreated([
"status" => true,
"message" => "User logged out successfully",
"data" => []
]);
}
```

View file

@ -0,0 +1,46 @@
https://www.youtube.com/watch?v=104Q76g7DWA&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=36
ProjectController
```php
// Post
public function addProject()
{
$rules = [
"name" => "required",
"budget" => "required"
];
if(!$this->validate($rules)) {
$response = [
"status" => false,
"message" => $this->validator->getErrors(),
"data" => []
];
}else {
$user_id = auth()-id();
$name = $this->request->getVar("name");
$budget = $this->request->getVar("budget");
$pm = new ProjectModel();
$data= [
"user_id" => $user_id,
"name" => name,
"budget" => budget
];
if($pm->insert($data)) {
$response = [
"status" => true,
"message" => "project saved",
"data" => []
];
}else{
$response = [
"status" => false,
"message" => "project save failed",
"data" => []
];
}
}
$return $this->respondCreated($response);
}
```

View file

@ -0,0 +1 @@
https://www.youtube.com/watch?v=B5GafCSXj9U&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=37

View file

@ -0,0 +1,35 @@
https://www.youtube.com/watch?v=-NIoOj-FtHE&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=36
ProjectController.php
```php
public function listProjects()
{
$user_id = auth()->id();
$pm = new ProjectModel();
$projectData = $pm->where([
"user_id" => $user_id
])->findAll();
if (count($projectData)>0) {
$response = [
"status" => true,
"message" => "Project data",
"data" => $projectData
];
}else {
$response = [
"status" => false,
"message" => "No Project data found",
"data" => []
];
}
return $this->respondCreated($response);
}
```
Routes.php
```php
$routes->get("list-projects", "ProjectController::listProjects", ["filter" => "apiauth"]);
```

View file

@ -0,0 +1,98 @@
https://www.youtube.com/watch?v=B5GafCSXj9U&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=37
ProjectController.php
```php
public function deleteProject($project_id)
{
$user_id = auth()->id();
if(!empty($project_id)) {
$pm = new ProjectModel();
$projectData = $pm->where([
"user_id" => $user_id,
"id" => $project_id
])->first();
if (!empty($projectData)) {
if(pm->delete($project_id)) {
$response = [
"status" => true,
"message" => "project deleted",
"data" => []
];
}else {
$response = [
"status" => false,
"message" => "failed to delete project",
"data" => []
];
}
}else{
$response = [
"status" => false,
"message" => "project not found",
"data" => []
];
}
}else{
$response = [
"status" => false,
"message" => "Project Id required",
"data" => []
];
}
return $this->respondCreated($response);
}
```
Routes.php
```php
$routes->delete("delete-project/(:num)", "ProjectController::deleteProject/$1",["filter"=>"apiauth"]);
```
Alternative version:
```php
public function deleteProject($project_id)
{
$user_id = auth()->id();
if(empty($project_id)) {
return $this->respondCreated([
"status" => false,
"message" => "project id required",
"data" => []
]);
}
$pm = new ProjectModel();
$projectData = $pm->where([
"user_id" => $user_id,
"id" => $project_id
])->first();
if(empty($projectData)) {
return $this->respondCreated([
"status" => false,
"message" => "project not found",
"data" => []
]);
}
if(!$pm->delete($project_id)) {
return $this->respondCreated([
"status" => false,
"message" => "failed to delete project",
"data" => []
]);
}
return $this->respondCreated([
"status" => true,
"message" => "project deleted",
"data" => []
]);
}
```

View file

@ -0,0 +1,25 @@
https://www.youtube.com/watch?v=8GhfHZlEaEo&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=41
In case Web Server throws CORS errors when trying to access API.
We add a filter that adds header with CORS info to define which kind of HTTP are allowed from foreign source.
Create CorsFilter.php
```bash
php spark make:filter Cors --suffix
```
Add to before method:
```php
public function before(RequestInterface $request, $arguments=null)
{
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Authorization, Content-Type, Accept, API-KEY");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH");
}
```
Add in app/Config/Filters.php:
![[Pasted image 20241202084231.png]]
![[Pasted image 20241202084303.png]]

View file

@ -0,0 +1,3 @@
```bash
python3 -m http.server 8000
```

View file

@ -0,0 +1,20 @@
```php
<?php
$DBUSER="user";
$DBPASSWD="password";
$DATABASE="user_db";
$filename = "backup-" . date("d-m-Y") . ".sql.gz";
$mime = "application/x-gzip";
header( "Content-Type: " . $mime );
header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
$cmd = "mysqldump -u $DBUSER --password=$DBPASSWD $DATABASE | gzip --best";
passthru( $cmd );
exit(0);
?>
```

View file

@ -0,0 +1,4 @@
developer.tomtom.com
tschneider
Supertomtom1!

View file

@ -0,0 +1,44 @@
```php
<!-- test1.php -->
<?php
//The URL with parameters / query string.
// Escape: $str = preg_replace('/[^a-z\d ]/i', '', $str);
$url='https://api.tomtom.com/search/2/search/Am%20Kapellchen%2021%2C%2041470%20Neuss.json?lat=37.337&lon=-121.89&minFuzzyLevel=1&maxFuzzyLevel=2&view=Unified&relatedPois=off&key=xcA6NjLwDPmayP6y56AoVoloz1iohbUV';
//we use file_get_contents to GET the URL in question.
$contents = file_get_contents($url);
$result = json_decode($contents);
$lat = $result->results[0]->position->lat;
$lon = $result->results[0]->position->lon;
echo "Lat=" . $lat . ", Lon=" . $lon;
```
````php
<?php
//The URL with parameters / query string.
$url='https://api.tomtom.com/search/2/search/Am%20Kapellchen%2021%2C%2041470%20Neuss.json?lat=37.337&lon=-121.89&minFuzzyLevel=1&maxFuzzyLevel=2&view=Unified&relatedPois=off&key=xcA6NjLwDPmayP6y56AoVoloz1iohbUV';
//we use file_get_contents to GET the URL in question.
$contents = file_get_contents($url);
//If $contents is not a boolean FALSE value.
if($contents !== false){
//Print out the contents.
echo $contents;
}
```
````

View file

@ -0,0 +1,146 @@
````php
<!DOCTYPE html>
<html class='use-all-space'>
<head>
<meta http-equiv='X-UA-Compatible' content='IE=Edge' />
<meta charset='UTF-8'>
<title>Maps SDK for Web - Static route</title>
<meta name='viewport'
content='width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no'/>
<link rel='stylesheet' type='text/css' href='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.20.0/maps/maps.css'>
<link rel='stylesheet' type='text/css' href='../test/assets/ui-library/icons-css/routing.css'/>
<link rel='stylesheet' type='text/css' href='../test/assets/ui-library/index.css'/>
</head>
<style>
.icon {
background-size: cover;
height: 30px;
width: 30px;
}
.route-marker {
align-items: center;
background-color: #4a90e2;
border: solid 3px #2faaff;
border-radius: 50%;
display: flex;
height: 32px;
justify-content: center;
transition: width .1s, height .1s;
width: 32px;
}
</style>
<body>
<div><h1>TEST KARTE</<h1></div>
<div style="height: 90%" id='map' class='map'></div>
<script src='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.20.0/maps/maps-web.min.js'></script>
<script src='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.20.0/services/services-web.min.js'></script>
<script type='text/javascript' src='../test/assets/js/mobile-or-tablet.js'></script>
<script>
var map = tt.map({
key: 'xcA6NjLwDPmayP6y56AoVoloz1iohbUV',
container: 'map',
//dragPan: !isMobileOrTablet()
dragPan: false
});
map.scrollZoom.disable()
//map.addControl(new tt.FullscreenControl());
//map.addControl(new tt.NavigationControl());
function createMarkerElement(type) {
var element = document.createElement('div');
var innerElement = document.createElement('div');
element.className = 'route-marker';
innerElement.className = 'icon tt-icon -white -' + type;
element.appendChild(innerElement);
return element;
}
function addMarkers(feature) {
var startPoint, endPoint;
if (feature.geometry.type === 'MultiLineString') {
startPoint = feature.geometry.coordinates[0][0]; //get first point from first line
endPoint = feature.geometry.coordinates.slice(-1)[0].slice(-1)[0]; //get last point from last line
} else {
startPoint = feature.geometry.coordinates[0];
endPoint = feature.geometry.coordinates.slice(-1)[0];
}
new tt.Marker({ element: createMarkerElement('start') }).setLngLat(startPoint).addTo(map);
new tt.Marker({ element: createMarkerElement('finish') }).setLngLat(endPoint).addTo(map);
}
function findFirstBuildingLayerId() {
var layers = map.getStyle().layers;
for (var index in layers) {
if (layers[index].type === 'fill-extrusion') {
return layers[index].id;
}
}
throw new Error('Map style does not contain any layer with fill-extrusion type.');
}
map.once('load', function() {
tt.services.calculateRoute({
key: 'xcA6NjLwDPmayP6y56AoVoloz1iohbUV',
traffic: false,
locations: '4.8786,52.3679:4.8798,52.3679'
})
.then(function(response) {
var geojson = response.toGeoJson();
map.addLayer({
'id': 'route',
'type': 'line',
'source': {
'type': 'geojson',
'data': geojson
},
'paint': {
'line-color': '#4a90e2',
'line-width': 8
}
}, findFirstBuildingLayerId());
addMarkers(geojson.features[0]);
var bounds = new tt.LngLatBounds();
geojson.features[0].geometry.coordinates.forEach(function(point) {
bounds.extend(tt.LngLat.convert(point));
});
map.fitBounds(bounds, { duration: 0, padding: 50 });
});
});
</script>
</body>
</html>
```