Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3aeb452887 | |||
| dfc185a350 | |||
| 51e6574693 | |||
| 25812dea56 | |||
| 78d32d95f7 | |||
| 1a6024a7e5 | |||
| d83b7e96d7 | |||
| fc015a0b4a | |||
| 82e9d3d9e1 | |||
| de1ccbf443 | |||
| 7e294cefe1 | |||
| 1dc139775a | |||
| 90f19cc4c4 | |||
| aacc7348bc | |||
| 8a49d07fd8 | |||
| 7217b62419 | |||
| b20eb28e94 | |||
| fdc31689e3 | |||
| 09872e27c7 | |||
| 1695fc21d8 | |||
| 0d90afd4d1 | |||
| bc6b506077 | |||
| 9da16e9d7c | |||
| 75f3cd1c99 | |||
| 4c0a8b4146 | |||
| 5f1fb301b6 | |||
| 51dd024af7 | |||
| 6e1e98f89a | |||
| deaff8e1f1 | |||
| d9a233efbd |
+41
-1
@@ -1 +1,41 @@
|
||||
IyBFZGl0b3JDb25maWcgaGVscHMgbWFpbnRhaW4gY29uc2lzdGVudCBjb2Rpbmcgc3R5bGVzIGFjcm9zcyBkaWZmZXJlbnQgZWRpdG9ycyBhbmQgSURFcwojIGh0dHBzOi8vZWRpdG9yY29uZmlnLm9yZy8KCnJvb3QgPSB0cnVlCgojIERlZmF1bHQgc2V0dGluZ3Mg4oCUIFRhYnMgcHJlZmVycmVkLCB3aWR0aCA9IDIgc3BhY2VzClsqXQpjaGFyc2V0ID0gdXRmLTgKZW5kX29mX2xpbmUgPSBsZgppbnNlcnRfZmluYWxfbmV3bGluZSA9IHRydWUKdHJpbV90cmFpbGluZ193aGl0ZXNwYWNlID0gdHJ1ZQppbmRlbnRfc3R5bGUgPSB0YWIKdGFiX3dpZHRoID0gMgoKIyBQb3dlclNoZWxsIHNjcmlwdHMg4oCUIHRhYnMsIDItc3BhY2UgdmlzdWFsIHdpZHRoClsqLnBzMV0KaW5kZW50X3N0eWxlID0gdGFiCnRhYl93aWR0aCA9IDIKZW5kX29mX2xpbmUgPSBjcmxmCgojIE1hcmtkb3duIGZpbGVzIOKAlCBrZWVwIHRyYWlsaW5nIHdoaXRlc3BhY2UgZm9yIGxpbmUgYnJlYWtzClsqLm1kXQp0cmltX3RyYWlsaW5nX3doaXRlc3BhY2UgPSBmYWxzZQoKIyBKU09OIC8gWUFNTCBmaWxlcyDigJQgdGFicywgMi1zcGFjZSB2aXN1YWwgd2lkdGgKWyoue2pzb24seW1sLHlhbWx9XQppbmRlbnRfc3R5bGUgPSB0YWIKdGFiX3dpZHRoID0gMgoKIyBNYWtlZmlsZXMg4oCUIGFsd2F5cyB0YWJzLCBkZWZhdWx0IHdpZHRoCltNYWtlZmlsZV0KaW5kZW50X3N0eWxlID0gdGFiCnRhYl93aWR0aCA9IDIKCiMgV2luZG93cyBiYXRjaCBzY3JpcHRzIOKAlCBrZWVwIENSTEYgZW5kaW5ncwpbKi57YmF0LGNtZH1dCmVuZF9vZl9saW5lID0gY3JsZgoKIyBTaGVsbCBzY3JpcHRzIOKAlCBlbnN1cmUgTEYgZW5kaW5ncwpbKi5zaF0KZW5kX29mX2xpbmUgPSBsZgo=
|
||||
# EditorConfig helps maintain consistent coding styles across different editors and IDEs
|
||||
# https://editorconfig.org/
|
||||
|
||||
root = true
|
||||
|
||||
# Default settings — Tabs preferred, width = 2 spaces
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
|
||||
# PowerShell scripts — tabs, 2-space visual width
|
||||
[*.ps1]
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
end_of_line = crlf
|
||||
|
||||
# Markdown files — keep trailing whitespace for line breaks
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# JSON / YAML files — tabs, 2-space visual width
|
||||
[*.{json,yml,yaml}]
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
|
||||
# Makefiles — always tabs, default width
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
|
||||
# Windows batch scripts — keep CRLF endings
|
||||
[*.{bat,cmd}]
|
||||
end_of_line = crlf
|
||||
|
||||
# Shell scripts — ensure LF endings
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
# MokoSuite standard ignores
|
||||
.claude/
|
||||
.mcp.json
|
||||
TODO.md
|
||||
vendor/
|
||||
node_modules/
|
||||
.env
|
||||
*.min.css
|
||||
*.min.js
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.ppk
|
||||
@@ -1 +0,0 @@
|
||||
LS0tCm5hbWU6IEFyY2hpdGVjdHVyZSBEZWNpc2lvbiBSZWNvcmQgKEFEUikKYWJvdXQ6IFByb3Bvc2Ugb3IgZG9jdW1lbnQgYW4gYXJjaGl0ZWN0dXJhbCBkZWNpc2lvbgp0aXRsZTogJ1tBRFJdICcKbGFiZWxzOiAnYXJjaGl0ZWN0dXJlLCBkZWNpc2lvbicKYXNzaWduZWVzOiAnJwoKLS0tCgoKIyMgQURSIE51bWJlcgpBRFItWFhYWAoKIyMgU3RhdHVzCi0gWyBdIFByb3Bvc2VkCi0gWyBdIEFjY2VwdGVkCi0gWyBdIERlcHJlY2F0ZWQKLSBbIF0gU3VwZXJzZWRlZCBieSBBRFItWFhYWAoKIyMgQ29udGV4dApEZXNjcmliZSB0aGUgaXNzdWUgb3IgcHJvYmxlbSB0aGF0IG1vdGl2YXRlcyB0aGlzIGRlY2lzaW9uLgoKIyMgRGVjaXNpb24KU3RhdGUgdGhlIGFyY2hpdGVjdHVyZSBkZWNpc2lvbiBhbmQgcHJvdmlkZSByYXRpb25hbGUuCgojIyBDb25zZXF1ZW5jZXMKIyMjIFBvc2l0aXZlCi0gTGlzdCBwb3NpdGl2ZSBjb25zZXF1ZW5jZXMKCiMjIyBOZWdhdGl2ZQotIExpc3QgbmVnYXRpdmUgY29uc2VxdWVuY2VzIG9yIHRyYWRlLW9mZnMKCiMjIyBOZXV0cmFsCi0gTGlzdCBuZXV0cmFsIGFzcGVjdHMKCiMjIEFsdGVybmF0aXZlcyBDb25zaWRlcmVkCiMjIyBBbHRlcm5hdGl2ZSAxCi0gRGVzY3JpcHRpb24KLSBQcm9zCi0gQ29ucwotIFdoeSBub3QgY2hvc2VuCgojIyMgQWx0ZXJuYXRpdmUgMgotIERlc2NyaXB0aW9uCi0gUHJvcwotIENvbnMKLSBXaHkgbm90IGNob3NlbgoKIyMgSW1wbGVtZW50YXRpb24gUGxhbgoxLiBTdGVwIDEKMi4gU3RlcCAyCjMuIFN0ZXAgMwoKIyMgU3Rha2Vob2xkZXJzCi0gKipEZWNpc2lvbiBNYWtlcnMqKjogQHVzZXIxLCBAdXNlcjIKLSAqKkNvbnN1bHRlZCoqOiBAdXNlcjMsIEB1c2VyNAotICoqSW5mb3JtZWQqKjogdGVhbS1uYW1lCgojIyBUZWNobmljYWwgRGV0YWlscwojIyMgQXJjaGl0ZWN0dXJlIERpYWdyYW0KYGBgCltBZGQgZGlhZ3JhbSBvciBsaW5rXQpgYGAKCiMjIyBEZXBlbmRlbmNpZXMKLSBEZXBlbmRlbmN5IDEKLSBEZXBlbmRlbmN5IDIKCiMjIyBJbXBhY3QgQW5hbHlzaXMKLSAqKlBlcmZvcm1hbmNlKio6IFtJbXBhY3QgZGVzY3JpcHRpb25dCi0gKipTZWN1cml0eSoqOiBbSW1wYWN0IGRlc2NyaXB0aW9uXQotICoqU2NhbGFiaWxpdHkqKjogW0ltcGFjdCBkZXNjcmlwdGlvbl0KLSAqKk1haW50YWluYWJpbGl0eSoqOiBbSW1wYWN0IGRlc2NyaXB0aW9uXQoKIyMgVGVzdGluZyBTdHJhdGVneQotIFsgXSBVbml0IHRlc3RzCi0gWyBdIEludGVncmF0aW9uIHRlc3RzCi0gWyBdIFBlcmZvcm1hbmNlIHRlc3RzCi0gWyBdIFNlY3VyaXR5IHRlc3RzCgojIyBEb2N1bWVudGF0aW9uCi0gWyBdIEFyY2hpdGVjdHVyZSBkb2N1bWVudGF0aW9uIHVwZGF0ZWQKLSBbIF0gQVBJIGRvY3VtZW50YXRpb24gdXBkYXRlZAotIFsgXSBEZXZlbG9wZXIgZ3VpZGUgdXBkYXRlZAotIFsgXSBSdW5ib29rIGNyZWF0ZWQKCiMjIE1pZ3JhdGlvbiBQYXRoCkRlc2NyaWJlIGhvdyB0byBtaWdyYXRlIGZyb20gY3VycmVudCBzdGF0ZSB0byBuZXcgYXJjaGl0ZWN0dXJlLgoKIyMgUm9sbGJhY2sgUGxhbgpEZXNjcmliZSBob3cgdG8gcm9sbGJhY2sgaWYgaXNzdWVzIG9jY3VyLgoKIyMgVGltZWxpbmUKLSAqKlByb3Bvc2FsIERhdGUqKjoKLSAqKkRlY2lzaW9uIERhdGUqKjoKLSAqKkltcGxlbWVudGF0aW9uIFN0YXJ0Kio6Ci0gKipFeHBlY3RlZCBDb21wbGV0aW9uKio6CgojIyBSZWZlcmVuY2VzCi0gUmVsYXRlZCBBRFJzOgotIEV4dGVybmFsIHJlc291cmNlczoKLSBSRkNzOgoKIyMgUmV2aWV3IENoZWNrbGlzdAotIFsgXSBBbGlnbnMgd2l0aCBlbnRlcnByaXNlIGFyY2hpdGVjdHVyZSBwcmluY2lwbGVzCi0gWyBdIFNlY3VyaXR5IGltcGxpY2F0aW9ucyByZXZpZXdlZAotIFsgXSBQZXJmb3JtYW5jZSBpbXBsaWNhdGlvbnMgcmV2aWV3ZWQKLSBbIF0gQ29zdCBpbXBsaWNhdGlvbnMgcmV2aWV3ZWQKLSBbIF0gQ29tcGxpYW5jZSByZXF1aXJlbWVudHMgbWV0Ci0gWyBdIFRlYW0gY29uc2Vuc3VzIGFjaGlldmVkCg==
|
||||
@@ -1 +0,0 @@
|
||||
LS0tCm5hbWU6IEJ1ZyBSZXBvcnQKYWJvdXQ6IFJlcG9ydCBhIGJ1ZyBvciBpc3N1ZSB3aXRoIHRoZSBwcm9qZWN0CnRpdGxlOiAnW0JVR10gJwpsYWJlbHM6ICdidWcnCmFzc2lnbmVlczogJycKCi0tLQoKCiMjIEJ1ZyBEZXNjcmlwdGlvbgpBIGNsZWFyIGFuZCBjb25jaXNlIGRlc2NyaXB0aW9uIG9mIHdoYXQgdGhlIGJ1ZyBpcy4KCiMjIFN0ZXBzIHRvIFJlcHJvZHVjZQoxLiBHbyB0byAnLi4uJwoyLiBDbGljayBvbiAnLi4uJwozLiBTY3JvbGwgZG93biB0byAnLi4uJwo0LiBTZWUgZXJyb3IKCiMjIEV4cGVjdGVkIEJlaGF2aW9yCkEgY2xlYXIgYW5kIGNvbmNpc2UgZGVzY3JpcHRpb24gb2Ygd2hhdCB5b3UgZXhwZWN0ZWQgdG8gaGFwcGVuLgoKIyMgQWN0dWFsIEJlaGF2aW9yCkEgY2xlYXIgYW5kIGNvbmNpc2UgZGVzY3JpcHRpb24gb2Ygd2hhdCBhY3R1YWxseSBoYXBwZW5lZC4KCiMjIFNjcmVlbnNob3RzCklmIGFwcGxpY2FibGUsIGFkZCBzY3JlZW5zaG90cyB0byBoZWxwIGV4cGxhaW4geW91ciBwcm9ibGVtLgoKIyMgRW52aXJvbm1lbnQKLSAqKlByb2plY3QqKjogW2UuZy4sIE1va29Eb2xpVG9vbHMsIG1va28tY2Fzc2lvcGVpYV0KLSAqKlZlcnNpb24qKjogW2UuZy4sIDEuMi4zXQotICoqUGxhdGZvcm0qKjogW2UuZy4sIERvbGliYXJyIDE4LjAsIEpvb21sYSA1LjBdCi0gKipQSFAgVmVyc2lvbioqOiBbZS5nLiwgOC4xXQotICoqRGF0YWJhc2UqKjogW2UuZy4sIE15U1FMIDguMCwgUG9zdGdyZVNRTCAxNF0KLSAqKkJyb3dzZXIqKiAoaWYgYXBwbGljYWJsZSk6IFtlLmcuLCBDaHJvbWUgMTIwLCBGaXJlZm94IDEyMV0KLSAqKk9TKio6IFtlLmcuLCBVYnVudHUgMjIuMDQsIFdpbmRvd3MgMTFdCgojIyBBZGRpdGlvbmFsIENvbnRleHQKQWRkIGFueSBvdGhlciBjb250ZXh0IGFib3V0IHRoZSBwcm9ibGVtIGhlcmUuCgojIyBQb3NzaWJsZSBTb2x1dGlvbgpJZiB5b3UgaGF2ZSBzdWdnZXN0aW9ucyBvbiBob3cgdG8gZml4IHRoZSBpc3N1ZSwgcGxlYXNlIGRlc2NyaWJlIHRoZW0gaGVyZS4KCiMjIENoZWNrbGlzdAotIFsgXSBJIGhhdmUgc2VhcmNoZWQgZm9yIHNpbWlsYXIgaXNzdWVzIGJlZm9yZSBjcmVhdGluZyB0aGlzIG9uZQotIFsgXSBJIGhhdmUgcHJvdmlkZWQgYWxsIHRoZSByZXF1ZXN0ZWQgaW5mb3JtYXRpb24KLSBbIF0gSSBoYXZlIHRlc3RlZCB0aGlzIG9uIHRoZSBsYXRlc3Qgc3RhYmxlIHZlcnNpb24KLSBbIF0gSSBoYXZlIGNoZWNrZWQgdGhlIGRvY3VtZW50YXRpb24gYW5kIGNvdWxkbid0IGZpbmQgYSBzb2x1dGlvbgo=
|
||||
@@ -1 +0,0 @@
|
||||
LS0tCmJsYW5rX2lzc3Vlc19lbmFibGVkOiB0cnVlCmNvbnRhY3RfbGlua3M6CiAgLSBuYW1lOiDwn5K8IEVudGVycHJpc2UgU3VwcG9ydAogICAgdXJsOiBodHRwczovL21va29jb25zdWx0aW5nLnRlY2gvZW50ZXJwcmlzZQogICAgYWJvdXQ6IEVudGVycHJpc2UtbGV2ZWwgc3VwcG9ydCBhbmQgY29uc3VsdGF0aW9uIHNlcnZpY2VzCiAgLSBuYW1lOiDwn5KsIEFzayBhIFF1ZXN0aW9uCiAgICB1cmw6IGh0dHBzOi8vbW9rb2NvbnN1bHRpbmcudGVjaC8KICAgIGFib3V0OiBHZXQgaGVscCBvciBhc2sgcXVlc3Rpb25zIHRocm91Z2ggb3VyIHdlYnNpdGUKICAtIG5hbWU6IPCfk5ogTW9rb1N0YW5kYXJkcyBEb2N1bWVudGF0aW9uCiAgICB1cmw6IGh0dHBzOi8vZ2l0Lm1va29jb25zdWx0aW5nLnRlY2gvTW9rb0NvbnN1bHRpbmcvbW9rby1wbGF0Zm9ybQogICAgYWJvdXQ6IFZpZXcgb3VyIGNvZGluZyBzdGFuZGFyZHMgYW5kIGJlc3QgcHJhY3RpY2VzCiAgLSBuYW1lOiDwn5SSIFJlcG9ydCBhIFNlY3VyaXR5IFZ1bG5lcmFiaWxpdHkKICAgIHVybDogaHR0cHM6Ly9naXQubW9rb2NvbnN1bHRpbmcudGVjaC9tb2tvY29uc3VsdGluZy10ZWNoLy5naXRodWItcHJpdmF0ZS9zZWN1cml0eS9hZHZpc29yaWVzL25ldwogICAgYWJvdXQ6IFJlcG9ydCBzZWN1cml0eSB2dWxuZXJhYmlsaXRpZXMgcHJpdmF0ZWx5IChmb3IgY3JpdGljYWwgaXNzdWVzKQogIC0gbmFtZTog8J+SoSBDb21tdW5pdHkgRGlzY3Vzc2lvbnMKICAgIHVybDogaHR0cHM6Ly9naXRodWIuY29tL29yZ3MvbW9rb2NvbnN1bHRpbmctdGVjaC9kaXNjdXNzaW9ucwogICAgYWJvdXQ6IEpvaW4gY29tbXVuaXR5IGRpc2N1c3Npb25zIGFuZCBRJkEK
|
||||
@@ -1 +0,0 @@
|
||||
LS0tCm5hbWU6IERvY3VtZW50YXRpb24gSXNzdWUKYWJvdXQ6IFJlcG9ydCBhbiBpc3N1ZSB3aXRoIGRvY3VtZW50YXRpb24KdGl0bGU6ICdbRE9DU10gJwpsYWJlbHM6ICdkb2N1bWVudGF0aW9uJwphc3NpZ25lZXM6ICcnCgotLS0KCgojIyBEb2N1bWVudGF0aW9uIElzc3VlCgoqKkxvY2F0aW9uKio6IAo8IS0tIFNwZWNpZnkgdGhlIGZpbGUsIHBhZ2UsIG9yIHNlY3Rpb24gd2l0aCB0aGUgaXNzdWUgLS0+CgojIyBJc3N1ZSBUeXBlCjwhLS0gTWFyayB0aGUgcmVsZXZhbnQgb3B0aW9uIHdpdGggYW4gIngiIC0tPgotIFsgXSBUeXBvIG9yIGdyYW1tYXIgZXJyb3IKLSBbIF0gT3V0ZGF0ZWQgaW5mb3JtYXRpb24KLSBbIF0gTWlzc2luZyBkb2N1bWVudGF0aW9uCi0gWyBdIFVuY2xlYXIgZXhwbGFuYXRpb24KLSBbIF0gQnJva2VuIGxpbmtzCi0gWyBdIE1pc3NpbmcgZXhhbXBsZXMKLSBbIF0gT3RoZXIgKHNwZWNpZnkgYmVsb3cpCgojIyBEZXNjcmlwdGlvbgo8IS0tIENsZWFybHkgZGVzY3JpYmUgdGhlIGRvY3VtZW50YXRpb24gaXNzdWUgLS0+CgojIyBDdXJyZW50IENvbnRlbnQKPCEtLSBRdW90ZSBvciBkZXNjcmliZSB0aGUgY3VycmVudCBkb2N1bWVudGF0aW9uIChpZiBhcHBsaWNhYmxlKSAtLT4KYGBgCkN1cnJlbnQgdGV4dCBoZXJlCmBgYAoKIyMgU3VnZ2VzdGVkIEltcHJvdmVtZW50CjwhLS0gUHJvdmlkZSB5b3VyIHN1Z2dlc3Rpb24gZm9yIGhvdyB0byBpbXByb3ZlIHRoZSBkb2N1bWVudGF0aW9uIC0tPgpgYGAKU3VnZ2VzdGVkIHRleHQgaGVyZQpgYGAKCiMjIEFkZGl0aW9uYWwgQ29udGV4dAo8IS0tIEFkZCBhbnkgb3RoZXIgY29udGV4dCwgc2NyZWVuc2hvdHMsIG9yIHJlZmVyZW5jZXMgLS0+CgojIyBTdGFuZGFyZHMgQWxpZ25tZW50Ci0gWyBdIEZvbGxvd3MgTW9rb1N0YW5kYXJkcyBkb2N1bWVudGF0aW9uIGd1aWRlbGluZXMKLSBbIF0gVXNlcyBlbl9VUy9lbl9HQiBsb2NhbGl6YXRpb24KLSBbIF0gSW5jbHVkZXMgcHJvcGVyIFNQRFggaGVhZGVycyB3aGVyZSBhcHBsaWNhYmxlCgojIyBDaGVja2xpc3QKLSBbIF0gSSBoYXZlIHNlYXJjaGVkIGZvciBzaW1pbGFyIGRvY3VtZW50YXRpb24gaXNzdWVzCi0gWyBdIEkgaGF2ZSBwcm92aWRlZCBhIGNsZWFyIGRlc2NyaXB0aW9uCi0gWyBdIEkgaGF2ZSBzdWdnZXN0ZWQgYW4gaW1wcm92ZW1lbnQgKGlmIGFwcGxpY2FibGUpCg==
|
||||
@@ -1 +0,0 @@
|
||||
LS0tCm5hbWU6IEZlYXR1cmUgUmVxdWVzdAphYm91dDogU3VnZ2VzdCBhIG5ldyBmZWF0dXJlIG9yIGVuaGFuY2VtZW50CnRpdGxlOiAnW0ZFQVRVUkVdICcKbGFiZWxzOiAnZW5oYW5jZW1lbnQnCmFzc2lnbmVlczogJycKCi0tLQoKCiMjIEZlYXR1cmUgRGVzY3JpcHRpb24KQSBjbGVhciBhbmQgY29uY2lzZSBkZXNjcmlwdGlvbiBvZiB0aGUgZmVhdHVyZSB5b3UnZCBsaWtlIHRvIHNlZS4KCiMjIFByb2JsZW0gb3IgVXNlIENhc2UKRGVzY3JpYmUgdGhlIHByb2JsZW0gdGhpcyBmZWF0dXJlIHdvdWxkIHNvbHZlIG9yIHRoZSB1c2UgY2FzZSBpdCBhZGRyZXNzZXMuCkV4LiBJJ20gYWx3YXlzIGZydXN0cmF0ZWQgd2hlbiBbLi4uXQoKIyMgUHJvcG9zZWQgU29sdXRpb24KQSBjbGVhciBhbmQgY29uY2lzZSBkZXNjcmlwdGlvbiBvZiB3aGF0IHlvdSB3YW50IHRvIGhhcHBlbi4KCiMjIEFsdGVybmF0aXZlIFNvbHV0aW9ucwpBIGNsZWFyIGFuZCBjb25jaXNlIGRlc2NyaXB0aW9uIG9mIGFueSBhbHRlcm5hdGl2ZSBzb2x1dGlvbnMgb3IgZmVhdHVyZXMgeW91J3ZlIGNvbnNpZGVyZWQuCgojIyBCZW5lZml0cwpEZXNjcmliZSBob3cgdGhpcyBmZWF0dXJlIHdvdWxkIGJlbmVmaXQgdXNlcnM6Ci0gV2hvIHdvdWxkIHVzZSB0aGlzIGZlYXR1cmU/Ci0gV2hhdCBwcm9ibGVtcyBkb2VzIGl0IHNvbHZlPwotIFdoYXQgdmFsdWUgZG9lcyBpdCBhZGQ/CgojIyBJbXBsZW1lbnRhdGlvbiBEZXRhaWxzIChPcHRpb25hbCkKSWYgeW91IGhhdmUgaWRlYXMgYWJvdXQgaG93IHRoaXMgY291bGQgYmUgaW1wbGVtZW50ZWQsIHNoYXJlIHRoZW0gaGVyZToKLSBUZWNobmljYWwgYXBwcm9hY2gKLSBGaWxlcy9jb21wb25lbnRzIHRoYXQgbWlnaHQgbmVlZCBjaGFuZ2VzCi0gQW55IGNvbmNlcm5zIG9yIGNoYWxsZW5nZXMgeW91IGZvcmVzZWUKCiMjIEFkZGl0aW9uYWwgQ29udGV4dApBZGQgYW55IG90aGVyIGNvbnRleHQsIG1vY2t1cHMsIG9yIHNjcmVlbnNob3RzIGFib3V0IHRoZSBmZWF0dXJlIHJlcXVlc3QgaGVyZS4KCiMjIFJlbGV2YW50IFN0YW5kYXJkcwpEb2VzIHRoaXMgcmVsYXRlIHRvIGFueSBzdGFuZGFyZHMgaW4gW01va29TdGFuZGFyZHNdKGh0dHBzOi8vZ2l0Lm1va29jb25zdWx0aW5nLnRlY2gvTW9rb0NvbnN1bHRpbmcvTW9rb1N0YW5kYXJkcyk/Ci0gWyBdIEFjY2Vzc2liaWxpdHkgKFdDQUcgMi4xIEFBKQotIFsgXSBMb2NhbGl6YXRpb24gKGVuX1VTL2VuX0dCKQotIFsgXSBTZWN1cml0eSBiZXN0IHByYWN0aWNlcwotIFsgXSBDb2RlIHF1YWxpdHkgc3RhbmRhcmRzCi0gWyBdIE90aGVyOiBbc3BlY2lmeV0KCiMjIENoZWNrbGlzdAotIFsgXSBJIGhhdmUgc2VhcmNoZWQgZm9yIHNpbWlsYXIgZmVhdHVyZSByZXF1ZXN0cyBiZWZvcmUgY3JlYXRpbmcgdGhpcyBvbmUKLSBbIF0gSSBoYXZlIGNsZWFybHkgZGVzY3JpYmVkIHRoZSB1c2UgY2FzZSBhbmQgYmVuZWZpdHMKLSBbIF0gSSBoYXZlIGNvbnNpZGVyZWQgYWx0ZXJuYXRpdmUgc29sdXRpb25zCi0gWyBdIFRoaXMgZmVhdHVyZSBhbGlnbnMgd2l0aCB0aGUgcHJvamVjdCdzIGdvYWxzIGFuZCBzY29wZQo=
|
||||
@@ -1 +0,0 @@
|
||||
LS0tCm5hbWU6IEpvb21sYSBFeHRlbnNpb24gSXNzdWUKYWJvdXQ6IFJlcG9ydCBhbiBpc3N1ZSB3aXRoIGEgSm9vbWxhIGV4dGVuc2lvbgp0aXRsZTogJ1tKT09NTEFdICcKbGFiZWxzOiAnam9vbWxhJwphc3NpZ25lZXM6ICcnCgotLS0KCgojIyBJc3N1ZSBUeXBlCi0gWyBdIENvbXBvbmVudCBJc3N1ZQotIFsgXSBNb2R1bGUgSXNzdWUKLSBbIF0gUGx1Z2luIElzc3VlCi0gWyBdIFRlbXBsYXRlIElzc3VlCgojIyBFeHRlbnNpb24gRGV0YWlscwotICoqRXh0ZW5zaW9uIE5hbWUqKjogW2UuZy4sIG1va28tY2Fzc2lvcGVpYV0KLSAqKkV4dGVuc2lvbiBWZXJzaW9uKio6IFtlLmcuLCAxLjIuM10KLSAqKkV4dGVuc2lvbiBUeXBlKio6IFtDb21wb25lbnQgLyBNb2R1bGUgLyBQbHVnaW4gLyBUZW1wbGF0ZV0KCiMjIEpvb21sYSBFbnZpcm9ubWVudAotICoqSm9vbWxhIFZlcnNpb24qKjogW2UuZy4sIDQuNC4wLCA1LjAuMF0KLSAqKlBIUCBWZXJzaW9uKio6IFtlLmcuLCA4LjEuMF0KLSAqKkRhdGFiYXNlKio6IFtNeVNRTCAvIFBvc3RncmVTUUwgLyBNYXJpYURCXQotICoqRGF0YWJhc2UgVmVyc2lvbioqOiBbZS5nLiwgOC4wXQotICoqU2VydmVyKio6IFtBcGFjaGUgLyBOZ2lueCAvIElJU10KLSAqKkhvc3RpbmcqKjogW1NoYXJlZCAvIFZQUyAvIERlZGljYXRlZCAvIENsb3VkXQoKIyMgSXNzdWUgRGVzY3JpcHRpb24KUHJvdmlkZSBhIGNsZWFyIGFuZCBkZXRhaWxlZCBkZXNjcmlwdGlvbiBvZiB0aGUgaXNzdWUuCgojIyBTdGVwcyB0byBSZXByb2R1Y2UKMS4gR28gdG8gJy4uLicKMi4gQ2xpY2sgb24gJy4uLicKMy4gQ29uZmlndXJlICcuLi4nCjQuIFNlZSBlcnJvcgoKIyMgRXhwZWN0ZWQgQmVoYXZpb3IKV2hhdCB5b3UgZXhwZWN0ZWQgdG8gaGFwcGVuLgoKIyMgQWN0dWFsIEJlaGF2aW9yCldoYXQgYWN0dWFsbHkgaGFwcGVuZWQuCgojIyBFcnJvciBNZXNzYWdlcwpgYGAKIyBQYXN0ZSBhbnkgZXJyb3IgbWVzc2FnZXMgZnJvbSBKb29tbGEgZXJyb3IgbG9ncwojIExvY2F0aW9uOiBhZG1pbmlzdHJhdG9yL2xvZ3MvZXJyb3IucGhwCmBgYAoKIyMgQnJvd3NlciBDb25zb2xlIEVycm9ycwpgYGBqYXZhc2NyaXB0Ci8vIFBhc3RlIGFueSBKYXZhU2NyaXB0IGNvbnNvbGUgZXJyb3JzIChGMTIgaW4gYnJvd3NlcikKYGBgCgojIyBTY3JlZW5zaG90cwpBZGQgc2NyZWVuc2hvdHMgdG8gaGVscCBleHBsYWluIHRoZSBpc3N1ZS4KCiMjIENvbmZpZ3VyYXRpb24KYGBgaW5pCiMgUGFzdGUgZXh0ZW5zaW9uIGNvbmZpZ3VyYXRpb24gKHNhbml0aXplIHNlbnNpdGl2ZSBkYXRhKQpgYGAKCiMjIEluc3RhbGxlZCBFeHRlbnNpb25zCkxpc3Qgb3RoZXIgaW5zdGFsbGVkIGV4dGVuc2lvbnMgdGhhdCBtaWdodCBjb25mbGljdDoKLSBFeHRlbnNpb24gMSAodmVyc2lvbikKLSBFeHRlbnNpb24gMiAodmVyc2lvbikKCiMjIFRlbXBsYXRlIE92ZXJyaWRlcwotIFsgXSBVc2luZyB0ZW1wbGF0ZSBvdmVycmlkZXMKLSBbIF0gQ3VzdG9tIENTUwotIFsgXSBDdXN0b20gSmF2YVNjcmlwdAoKIyMgQWRkaXRpb25hbCBDb250ZXh0Ci0gKipNdWx0aWxpbmd1YWwgU2l0ZSoqOiBbWWVzIC8gTm9dCi0gKipDYWNoZSBFbmFibGVkKio6IFtZZXMgLyBOb10KLSAqKkRlYnVnIE1vZGUqKjogW1llcyAvIE5vXQotICoqU0VGIFVSTHMqKjogW1llcyAvIE5vXQoKIyMgQ2hlY2tsaXN0Ci0gWyBdIEkgaGF2ZSBjbGVhcmVkIEpvb21sYSBjYWNoZQotIFsgXSBJIGhhdmUgZGlzYWJsZWQgb3RoZXIgZXh0ZW5zaW9ucyB0byB0ZXN0IGZvciBjb25mbGljdHMKLSBbIF0gSSBoYXZlIGNoZWNrZWQgSm9vbWxhIGVycm9yIGxvZ3MKLSBbIF0gSSBoYXZlIHRlc3RlZCB3aXRoIGEgZGVmYXVsdCBKb29tbGEgdGVtcGxhdGUKLSBbIF0gSSBoYXZlIGNoZWNrZWQgYnJvd3NlciBjb25zb2xlIGZvciBKYXZhU2NyaXB0IGVycm9ycwotIFsgXSBJIGhhdmUgc2VhcmNoZWQgZm9yIHNpbWlsYXIgaXNzdWVzCi0gWyBdIEkgYW0gdXNpbmcgYSBzdXBwb3J0ZWQgSm9vbWxhIHZlcnNpb24K
|
||||
@@ -1 +0,0 @@
|
||||
LS0tCm5hbWU6IFF1ZXN0aW9uCmFib3V0OiBBc2sgYSBxdWVzdGlvbiBhYm91dCB1c2FnZSwgZmVhdHVyZXMsIG9yIGJlc3QgcHJhY3RpY2VzCnRpdGxlOiAnW1FVRVNUSU9OXSAnCmxhYmVsczogWydxdWVzdGlvbiddCmFzc2lnbmVlczogWydqbWlsbGVyJ10KLS0tCgoKIyMgUXVlc3Rpb24KCioqWW91ciBxdWVzdGlvbjoqKgoKCiMjIENvbnRleHQKCioqV2hhdCBhcmUgeW91IHRyeWluZyB0byBhY2NvbXBsaXNoPyoqCgoKKipXaGF0IGhhdmUgeW91IGFscmVhZHkgdHJpZWQ/KioKCgoqKkNhdGVnb3J5Kio6Ci0gWyBdIFNjcmlwdCB1c2FnZQotIFsgXSBDb25maWd1cmF0aW9uCi0gWyBdIFdvcmtmbG93IHNldHVwCi0gWyBdIERvY3VtZW50YXRpb24gaW50ZXJwcmV0YXRpb24KLSBbIF0gQmVzdCBwcmFjdGljZXMKLSBbIF0gSW50ZWdyYXRpb24KLSBbIF0gT3RoZXI6IF9fX19fX19fX18KCiMjIEVudmlyb25tZW50IChpZiByZWxldmFudCkKCioqWW91ciBzZXR1cCoqOgotIE9wZXJhdGluZyBTeXN0ZW06Ci0gVmVyc2lvbjoKCiMjIFdoYXQgWW91J3ZlIFJlc2VhcmNoZWQKCioqRG9jdW1lbnRhdGlvbiByZXZpZXdlZCoqOgotIFsgXSBSRUFETUUubWQKLSBbIF0gUHJvamVjdCBkb2N1bWVudGF0aW9uCi0gWyBdIE90aGVyIChzcGVjaWZ5KTogX19fX19fX19fXwoKKipTaW1pbGFyIGlzc3Vlcy9xdWVzdGlvbnMgZm91bmQqKjoKLSAjCi0gIwoKIyMgRXhwZWN0ZWQgT3V0Y29tZQoKKipXaGF0IHJlc3VsdCBhcmUgeW91IGhvcGluZyBmb3I/KioKCgojIyBDb2RlL0NvbmZpZ3VyYXRpb24gU2FtcGxlcwoKKipSZWxldmFudCBjb2RlIG9yIGNvbmZpZ3VyYXRpb24qKiAoaWYgYXBwbGljYWJsZSk6CgpgYGBiYXNoCiMgWW91ciBjb2RlIGhlcmUKYGBgCgojIyBBZGRpdGlvbmFsIENvbnRleHQKCioqQW55IG90aGVyIHJlbGV2YW50IGluZm9ybWF0aW9uOioqCgoKKipTY3JlZW5zaG90cyoqIChpZiBoZWxwZnVsKToKCgojIyBVcmdlbmN5CgotIFsgXSBVcmdlbnQgKGJsb2NraW5nIHdvcmspCi0gWyBdIE5vcm1hbCAoY2FuIHdvcmsgb24gb3RoZXIgdGhpbmdzIG1lYW53aGlsZSkKLSBbIF0gTG93IHByaW9yaXR5IChqdXN0IGN1cmlvdXMpCgojIyBDaGVja2xpc3QKCi0gWyBdIEkgaGF2ZSBzZWFyY2hlZCBleGlzdGluZyBpc3N1ZXMgYW5kIGRpc2N1c3Npb25zCi0gWyBdIEkgaGF2ZSByZXZpZXdlZCByZWxldmFudCBkb2N1bWVudGF0aW9uCi0gWyBdIEkgaGF2ZSBwcm92aWRlZCBzdWZmaWNpZW50IGNvbnRleHQKLSBbIF0gSSBoYXZlIGluY2x1ZGVkIGNvZGUvY29uZmlndXJhdGlvbiBzYW1wbGVzIGlmIHJlbGV2YW50Ci0gWyBdIFRoaXMgaXMgYSBnZW51aW5lIHF1ZXN0aW9uIChub3QgYSBidWcgcmVwb3J0IG9yIGZlYXR1cmUgcmVxdWVzdCkK
|
||||
@@ -1 +0,0 @@
|
||||
LS0tCm5hbWU6IFJlcXVlc3QgZm9yIENvbW1lbnRzIChSRkMpCmFib3V0OiBQcm9wb3NlIGEgc2lnbmlmaWNhbnQgY2hhbmdlIGZvciBjb21tdW5pdHkgZGlzY3Vzc2lvbgp0aXRsZTogJ1tSRkNdICcKbGFiZWxzOiAncmZjLCBkaXNjdXNzaW9uJwphc3NpZ25lZXM6ICcnCgotLS0KCgojIyBSRkMgU3VtbWFyeQpPbmUtcGFyYWdyYXBoIHN1bW1hcnkgb2YgdGhlIHByb3Bvc2FsLgoKIyMgTW90aXZhdGlvbgpXaHkgYXJlIHdlIGRvaW5nIHRoaXM/IFdoYXQgdXNlIGNhc2VzIGRvZXMgaXQgc3VwcG9ydD8gV2hhdCBpcyB0aGUgZXhwZWN0ZWQgb3V0Y29tZT8KCiMjIERldGFpbGVkIERlc2lnbgojIyMgT3ZlcnZpZXcKUHJvdmlkZSBhIGRldGFpbGVkIGV4cGxhbmF0aW9uIG9mIHRoZSBwcm9wb3NlZCBjaGFuZ2UuCgojIyMgQVBJIENoYW5nZXMgKGlmIGFwcGxpY2FibGUpCmBgYHBocAovLyBCZWZvcmUKZnVuY3Rpb24gb2xkQXBpKCRwYXJhbTEpIHsgfQoKLy8gQWZ0ZXIKZnVuY3Rpb24gbmV3QXBpKCRwYXJhbTEsICRwYXJhbTIpIHsgfQpgYGAKCiMjIyBVc2VyIEV4cGVyaWVuY2UgQ2hhbmdlcwpEZXNjcmliZSBob3cgdXNlcnMgd2lsbCBpbnRlcmFjdCB3aXRoIHRoaXMgY2hhbmdlLgoKIyMjIEltcGxlbWVudGF0aW9uIEFwcHJvYWNoCkhpZ2gtbGV2ZWwgaW1wbGVtZW50YXRpb24gc3RyYXRlZ3kuCgojIyBEcmF3YmFja3MKV2h5IHNob3VsZCB3ZSAqbm90KiBkbyB0aGlzPwoKIyMgQWx0ZXJuYXRpdmVzCldoYXQgb3RoZXIgZGVzaWducyBoYXZlIGJlZW4gY29uc2lkZXJlZD8gV2hhdCBpcyB0aGUgaW1wYWN0IG9mIG5vdCBkb2luZyB0aGlzPwoKIyMjIEFsdGVybmF0aXZlIDEKLSBEZXNjcmlwdGlvbgotIFRyYWRlLW9mZnMKCiMjIyBBbHRlcm5hdGl2ZSAyCi0gRGVzY3JpcHRpb24KLSBUcmFkZS1vZmZzCgojIyBBZG9wdGlvbiBTdHJhdGVneQpIb3cgd2lsbCBleGlzdGluZyB1c2VycyBhZG9wdCB0aGlzPyBJcyB0aGlzIGEgYnJlYWtpbmcgY2hhbmdlPwoKIyMjIE1pZ3JhdGlvbiBHdWlkZQpgYGBiYXNoCiMgU3RlcHMgdG8gbWlncmF0ZQpgYGAKCiMjIyBEZXByZWNhdGlvbiBUaW1lbGluZQotICoqQW5ub3VuY2VtZW50Kio6Ci0gKipEZXByZWNhdGlvbioqOgotICoqUmVtb3ZhbCoqOgoKIyMgVW5yZXNvbHZlZCBRdWVzdGlvbnMKLSBRdWVzdGlvbiAxCi0gUXVlc3Rpb24gMgoKIyMgRnV0dXJlIFBvc3NpYmlsaXRpZXMKV2hhdCBmdXR1cmUgd29yayBkb2VzIHRoaXMgZW5hYmxlPwoKIyMgSW1wYWN0IEFzc2Vzc21lbnQKIyMjIFBlcmZvcm1hbmNlCkV4cGVjdGVkIHBlcmZvcm1hbmNlIGltcGFjdC4KCiMjIyBTZWN1cml0eQpTZWN1cml0eSBjb25zaWRlcmF0aW9ucyBhbmQgaW1wbGljYXRpb25zLgoKIyMjIENvbXBhdGliaWxpdHkKLSAqKkJhY2t3YXJkIENvbXBhdGlibGUqKjogW1llcyAvIE5vXQotICoqQnJlYWtpbmcgQ2hhbmdlcyoqOiBbTGlzdF0KCiMjIyBNYWludGVuYW5jZQpMb25nLXRlcm0gbWFpbnRlbmFuY2UgY29uc2lkZXJhdGlvbnMuCgojIyBDb21tdW5pdHkgSW5wdXQKIyMjIFN0YWtlaG9sZGVycwotIFsgXSBDb3JlIHRlYW0KLSBbIF0gTW9kdWxlIGRldmVsb3BlcnMKLSBbIF0gRW5kIHVzZXJzCi0gWyBdIEVudGVycHJpc2UgY3VzdG9tZXJzCgojIyMgRmVlZGJhY2sgUGVyaW9kCioqRHVyYXRpb24qKjogW2UuZy4sIDIgd2Vla3NdCioqRGVhZGxpbmUqKjogW2RhdGVdCgojIyBJbXBsZW1lbnRhdGlvbiBUaW1lbGluZQojIyMgUGhhc2UgMTogRGVzaWduCi0gWyBdIFJGQyBkaXNjdXNzaW9uCi0gWyBdIERlc2lnbiBmaW5hbGl6YXRpb24KLSBbIF0gQXBwcm92YWwKCiMjIyBQaGFzZSAyOiBJbXBsZW1lbnRhdGlvbgotIFsgXSBDb3JlIGltcGxlbWVudGF0aW9uCi0gWyBdIFRlc3RzCi0gWyBdIERvY3VtZW50YXRpb24KCiMjIyBQaGFzZSAzOiBSZWxlYXNlCi0gWyBdIEJldGEgcmVsZWFzZQotIFsgXSBGZWVkYmFjayBjb2xsZWN0aW9uCi0gWyBdIFN0YWJsZSByZWxlYXNlCgojIyBTdWNjZXNzIE1ldHJpY3MKSG93IHdpbGwgd2UgbWVhc3VyZSBzdWNjZXNzPwotIE1ldHJpYyAxCi0gTWV0cmljIDIKCiMjIFJlZmVyZW5jZXMKLSBSZWxhdGVkIFJGQ3M6Ci0gRXh0ZXJuYWwgZG9jdW1lbnRhdGlvbjoKLSBQcmlvciBhcnQ6CgojIyBPcGVuIFF1ZXN0aW9ucyBmb3IgQ29tbXVuaXR5CjEuIFF1ZXN0aW9uIDE/CjIuIFF1ZXN0aW9uIDI/CgotLS0KKipOb3RlKio6IFRoaXMgUkZDIGlzIG9wZW4gZm9yIGNvbW11bml0eSBkaXNjdXNzaW9uLiBQbGVhc2UgcHJvdmlkZSBmZWVkYmFjayBpbiB0aGUgY29tbWVudHMgYmVsb3cuCg==
|
||||
@@ -1 +0,0 @@
|
||||
LS0tCm5hbWU6IFNlY3VyaXR5IFZ1bG5lcmFiaWxpdHkgUmVwb3J0CmFib3V0OiBSZXBvcnQgYSBzZWN1cml0eSB2dWxuZXJhYmlsaXR5ICh1c2Ugb25seSBmb3Igbm9uLWNyaXRpY2FsIGlzc3VlcykKdGl0bGU6ICdbU0VDVVJJVFldICcKbGFiZWxzOiAnc2VjdXJpdHknCmFzc2lnbmVlczogJycKCi0tLQoKCiMjIOKaoO+4jyBJTVBPUlRBTlQ6IFByaXZhdGUgRGlzY2xvc3VyZSBSZXF1aXJlZAoKKipGb3IgY3JpdGljYWwgc2VjdXJpdHkgdnVsbmVyYWJpbGl0aWVzLCBETyBOT1QgdXNlIHRoaXMgdGVtcGxhdGUuKioKRm9sbG93IHRoZSBwcm9jZXNzIGluIFtTRUNVUklUWS5tZF0oLi4vU0VDVVJJVFkubWQpIGZvciByZXNwb25zaWJsZSBkaXNjbG9zdXJlLgoKVXNlIHRoaXMgdGVtcGxhdGUgb25seSBmb3I6Ci0gU2VjdXJpdHkgaW1wcm92ZW1lbnRzCi0gTm9uLWNyaXRpY2FsIHNlY3VyaXR5IHN1Z2dlc3Rpb25zCi0gU2VjdXJpdHkgZG9jdW1lbnRhdGlvbiB1cGRhdGVzCgotLS0KCiMjIFNlY3VyaXR5IElzc3VlCgoqKlNldmVyaXR5Kio6IAo8IS0tIExvdywgTWVkaXVtLCBvciBpbmZvcm1hdGlvbmFsIG9ubHkgLS0+CgojIyBEZXNjcmlwdGlvbgo8IS0tIERlc2NyaWJlIHRoZSBzZWN1cml0eSBjb25jZXJuIG9yIGltcHJvdmVtZW50IHN1Z2dlc3Rpb24gLS0+CgojIyBBZmZlY3RlZCBDb21wb25lbnRzCjwhLS0gTGlzdCB0aGUgYWZmZWN0ZWQgZmlsZXMsIGZlYXR1cmVzLCBvciBjb21wb25lbnRzIC0tPgoKIyMgU3VnZ2VzdGVkIE1pdGlnYXRpb24KPCEtLSBEZXNjcmliZSBob3cgdGhpcyBjb3VsZCBiZSBhZGRyZXNzZWQgLS0+CgojIyBTdGFuZGFyZHMgUmVmZXJlbmNlCkRvZXMgdGhpcyByZWxhdGUgdG8gc2VjdXJpdHkgc3RhbmRhcmRzIGluIFtNb2tvU3RhbmRhcmRzXShodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoL01va29Db25zdWx0aW5nL01va29TdGFuZGFyZHMpPwotIFsgXSBTUERYIGxpY2Vuc2UgaWRlbnRpZmllcnMKLSBbIF0gU2VjcmV0IG1hbmFnZW1lbnQKLSBbIF0gRGVwZW5kZW5jeSBzZWN1cml0eQotIFsgXSBBY2Nlc3MgY29udHJvbAotIFsgXSBPdGhlcjogW3NwZWNpZnldCgojIyBBZGRpdGlvbmFsIENvbnRleHQKPCEtLSBBZGQgYW55IG90aGVyIGNvbnRleHQgYWJvdXQgdGhlIHNlY3VyaXR5IGNvbmNlcm4gLS0+CgojIyBDaGVja2xpc3QKLSBbIF0gVGhpcyBpcyBOT1QgYSBjcml0aWNhbCB2dWxuZXJhYmlsaXR5IHJlcXVpcmluZyBwcml2YXRlIGRpc2Nsb3N1cmUKLSBbIF0gSSBoYXZlIHJldmlld2VkIHRoZSBTRUNVUklUWS5tZCBwb2xpY3kKLSBbIF0gSSBoYXZlIHByb3ZpZGVkIHN1ZmZpY2llbnQgZGV0YWlsIGZvciBldmFsdWF0aW9uCg==
|
||||
@@ -1 +0,0 @@
|
||||
LS0tCm5hbWU6IFZlcnNpb24gQnVtcAphYm91dDogUmVxdWVzdCBvciB0cmFjayBhIHZlcnNpb24gY2hhbmdlCnRpdGxlOiAnW1ZFUlNJT05dICcKbGFiZWxzOiAndmVyc2lvbiwgdHlwZTogdmVyc2lvbicKYXNzaWduZWVzOiAnam1pbGxlcicKLS0tCgojIyBWZXJzaW9uIENoYW5nZQoKKipDdXJyZW50IHZlcnNpb24qKjogPCEtLSBlLmcuLCAwMS4wMi4wMyAtLT4KKipSZXF1ZXN0ZWQgdmVyc2lvbioqOiA8IS0tIGUuZy4sIDAxLjAzLjAwIC0tPgoqKkNoYW5nZSB0eXBlKio6IDwhLS0gcGF0Y2ggLyBtaW5vciAvIG1ham9yIC0tPgoKIyMgUmVhc29uCgo8IS0tIFdoeSBpcyB0aGlzIHZlcnNpb24gYnVtcCBuZWVkZWQ/IC0tPgoKIyMgQ2hlY2tsaXN0CgotIFsgXSBSRUFETUUubWQgYFZFUlNJT046YCBmaWVsZCB1cGRhdGVkCi0gWyBdIENIQU5HRUxPRy5tZCBlbnRyeSBhZGRlZAotIFsgXSBNb2R1bGUgZGVzY3JpcHRvciB2ZXJzaW9uIHVwZGF0ZWQgKERvbGliYXJyOiBgJHRoaXMtPnZlcnNpb25gLCBKb29tbGE6IGA8dmVyc2lvbj5gKQotIFsgXSBBbGwgZmlsZSBoZWFkZXJzIHdpbGwgYmUgYXV0by1wcm9wYWdhdGVkIGJ5IGBzeW5jLXZlcnNpb24tb24tbWVyZ2VgIHdvcmtmbG93Cg==
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1,66 @@
|
||||
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojCiMgU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEdQTC0zLjAtb3ItbGF0ZXIKIwojIEZJTEUgSU5GT1JNQVRJT04KIyBERUZHUk9VUDogR2l0ZWEuV29ya2Zsb3cKIyBJTkdST1VQOiBtb2tvY2xpLlJlbGVhc2UKIyBSRVBPOiBodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoL01va29Db25zdWx0aW5nL21va29jbGkKIyBQQVRIOiAvLm1va29naXRlYS93b3JrZmxvd3MvYXV0by1idW1wLnltbAojIFZFUlNJT046IDA5LjAyLjAwCiMgQlJJRUY6IEF1dG8gcGF0Y2gtYnVtcCB2ZXJzaW9uIG9uIGV2ZXJ5IHB1c2ggdG8gZGV2IChza2lwcyBtZXJnZSBjb21taXRzKQoKbmFtZTogIlVuaXZlcnNhbDogQXV0byBWZXJzaW9uIEJ1bXAiCgpvbjoKICBwdXNoOgogICAgYnJhbmNoZXM6CiAgICAgIC0gZGV2CiAgICAgIC0gcmMKICAgICAgLSAnZmVhdHVyZS8qKicKICAgICAgLSAncGF0Y2gvKionCgplbnY6CiAgRk9SQ0VfSkFWQVNDUklQVF9BQ1RJT05TX1RPX05PREUyNDogdHJ1ZQogIE1PS09HSVRFQV9VUkw6ICR7eyB2YXJzLkdJVEVBX1VSTCB8fCAnaHR0cHM6Ly9naXQubW9rb2NvbnN1bHRpbmcudGVjaCcgfX0KCnBlcm1pc3Npb25zOgogIGNvbnRlbnRzOiB3cml0ZQoKam9iczoKICBidW1wOgogICAgbmFtZTogVmVyc2lvbiBCdW1wCiAgICBydW5zLW9uOiByZWxlYXNlCiAgICBpZjogPi0KICAgICAgIWNvbnRhaW5zKGdpdGh1Yi5ldmVudC5oZWFkX2NvbW1pdC5tZXNzYWdlLCAnW3NraXAgY2ldJykgJiYKICAgICAgIWNvbnRhaW5zKGdpdGh1Yi5ldmVudC5oZWFkX2NvbW1pdC5tZXNzYWdlLCAnW3NraXAgYnVtcF0nKSAmJgogICAgICAhc3RhcnRzV2l0aChnaXRodWIuZXZlbnQuaGVhZF9jb21taXQubWVzc2FnZSwgJ01lcmdlIHB1bGwgcmVxdWVzdCcpCgogICAgc3RlcHM6CiAgICAgIC0gbmFtZTogQ2hlY2tvdXQKICAgICAgICB1c2VzOiBhY3Rpb25zL2NoZWNrb3V0QGRlMGZhYzJlNDUwMGRhYmUwMDA5ZTY3MjE0ZmY1ZjU0NDdjZTgzZGQgIyB2NgogICAgICAgIHdpdGg6CiAgICAgICAgICB0b2tlbjogJHt7IHNlY3JldHMuTU9LT0dJVEVBX1RPS0VOIH19CiAgICAgICAgICBmZXRjaC1kZXB0aDogMQoKICAgICAgLSBuYW1lOiBTZXR1cCBtb2tvY2xpIHRvb2xzCiAgICAgICAgcnVuOiB8CiAgICAgICAgICBpZiAhIGNvbW1hbmQgLXYgY29tcG9zZXIgJj4gL2Rldi9udWxsOyB0aGVuCiAgICAgICAgICAgIHN1ZG8gYXB0LWdldCB1cGRhdGUgLXFxICYmIHN1ZG8gYXB0LWdldCBpbnN0YWxsIC15IC1xcSBwaHAtY2xpIHBocC1tYnN0cmluZyBwaHAteG1sIHBocC16aXAgcGhwLWN1cmwgY29tcG9zZXIgPi9kZXYvbnVsbCAyPiYxCiAgICAgICAgICBmaQogICAgICAgICAgaWYgWyAtZCAiL29wdC9tb2tvY2xpL2NsaSIgXTsgdGhlbgogICAgICAgICAgICBlY2hvICJNT0tPX0NMST0vb3B0L21va29jbGkvY2xpIiA+PiAiJEdJVEhVQl9FTlYiCiAgICAgICAgICBlbHNlCiAgICAgICAgICAgIGdpdCBjbG9uZSAtLWRlcHRoIDEgLS1icmFuY2ggbWFpbiAtLXF1aWV0IFwKICAgICAgICAgICAgICAiaHR0cHM6Ly94LWFjY2Vzcy10b2tlbjoke3sgc2VjcmV0cy5NT0tPR0lURUFfVE9LRU4gfX1AZ2l0Lm1va29jb25zdWx0aW5nLnRlY2gvTW9rb0NvbnN1bHRpbmcvbW9rb2NsaS5naXQiIFwKICAgICAgICAgICAgICAvdG1wL21va29jbGkKICAgICAgICAgICAgY2QgL3RtcC9tb2tvY2xpICYmIGNvbXBvc2VyIGluc3RhbGwgLS1uby1kZXYgLS1uby1pbnRlcmFjdGlvbiAtLXF1aWV0CiAgICAgICAgICAgIGVjaG8gIk1PS09fQ0xJPS90bXAvbW9rb2NsaS9jbGkiID4+ICIkR0lUSFVCX0VOViIKICAgICAgICAgIGZpCgogICAgICAtIG5hbWU6IEJ1bXAgdmVyc2lvbgogICAgICAgIHJ1bjogfAogICAgICAgICAgcGhwICR7TU9LT19DTEl9L3ZlcnNpb25fYXV0b19idW1wLnBocCBcCiAgICAgICAgICAgIC0tcGF0aCAuIC0tYnJhbmNoICIke0dJVEhVQl9SRUZfTkFNRX0iIFwKICAgICAgICAgICAgLS10b2tlbiAiJHt7IHNlY3JldHMuTU9LT0dJVEVBX1RPS0VOIH19IiBcCiAgICAgICAgICAgIC0tcmVwby11cmwgImh0dHBzOi8veC1hY2Nlc3MtdG9rZW46JHt7IHNlY3JldHMuTU9LT0dJVEVBX1RPS0VOIH19QGdpdC5tb2tvY29uc3VsdGluZy50ZWNoLyR7eyBnaXRodWIucmVwb3NpdG9yeSB9fS5naXQiCg==
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||
# VERSION: 09.02.00
|
||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||
|
||||
name: "Universal: Auto Version Bump"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- rc
|
||||
- 'feature/**'
|
||||
- 'patch/**'
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
name: Version Bump
|
||||
runs-on: release
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup mokocli tools
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
if [ -d "/opt/mokocli/cli" ]; then
|
||||
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
|
||||
/tmp/mokocli
|
||||
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
php ${MOKO_CLI}/version_auto_bump.php \
|
||||
--path . --branch "${GITHUB_REF_NAME}" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1,48 @@
|
||||
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojCiMgU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEdQTC0zLjAtb3ItbGF0ZXIKIwojIEZJTEUgSU5GT1JNQVRJT04KIyBERUZHUk9VUDogR2l0ZWEuV29ya2Zsb3cKIyBJTkdST1VQOiBNb2tvU3RhbmRhcmRzLlVuaXZlcnNhbAojIFJFUE86IGh0dHBzOi8vZ2l0Lm1va29jb25zdWx0aW5nLnRlY2gvTW9rb0NvbnN1bHRpbmcvbW9rb2NsaQojIFBBVEg6IC8ubW9rb2dpdGVhL3dvcmtmbG93cy9icmFuY2gtY2xlYW51cC55bWwKIyBWRVJTSU9OOiAwMS4wMC4wMAojIEJSSUVGOiBEZWxldGUgZmVhdHVyZSBicmFuY2hlcyBhZnRlciBQUiBtZXJnZQoKbmFtZTogIkJyYW5jaCBDbGVhbnVwIgoKb246CiAgcHVsbF9yZXF1ZXN0OgogICAgdHlwZXM6IFtjbG9zZWRdCgplbnY6CiAgRk9SQ0VfSkFWQVNDUklQVF9BQ1RJT05TX1RPX05PREUyNDogdHJ1ZQoKam9iczoKICBjbGVhbnVwOgogICAgbmFtZTogRGVsZXRlIG1lcmdlZCBicmFuY2gKICAgIHJ1bnMtb246IHVidW50dS1sYXRlc3QKICAgIGlmOiA+LQogICAgICBnaXRodWIuZXZlbnQucHVsbF9yZXF1ZXN0Lm1lcmdlZCA9PSB0cnVlICYmCiAgICAgIGdpdGh1Yi5ldmVudC5wdWxsX3JlcXVlc3QuaGVhZC5yZWYgIT0gJ2RldicgJiYKICAgICAgZ2l0aHViLmV2ZW50LnB1bGxfcmVxdWVzdC5oZWFkLnJlZiAhPSAnbWFpbicKCiAgICBzdGVwczoKICAgICAgLSBuYW1lOiBEZWxldGUgc291cmNlIGJyYW5jaAogICAgICAgIHJ1bjogfAogICAgICAgICAgQlJBTkNIPSIke3sgZ2l0aHViLmV2ZW50LnB1bGxfcmVxdWVzdC5oZWFkLnJlZiB9fSIKICAgICAgICAgIEFQST0iJHt7IHZhcnMuR0lURUFfVVJMIHx8ICdodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoJyB9fS9hcGkvdjEvcmVwb3MvJHt7IGdpdGh1Yi5yZXBvc2l0b3J5IH19L2JyYW5jaGVzIgogICAgICAgICAgRU5DT0RFRD0kKHBocCAtciAiZWNobyByYXd1cmxlbmNvZGUoJyR7QlJBTkNIfScpOyIpCgogICAgICAgICAgU1RBVFVTPSQoY3VybCAtc2YgLW8gL2Rldi9udWxsIC13ICIle2h0dHBfY29kZX0iIC1YIERFTEVURSBcCiAgICAgICAgICAgIC1IICJBdXRob3JpemF0aW9uOiB0b2tlbiAke3sgc2VjcmV0cy5NT0tPR0lURUFfVE9LRU4gfX0iIFwKICAgICAgICAgICAgIiR7QVBJfS8ke0VOQ09ERUR9IiAyPi9kZXYvbnVsbCB8fCB0cnVlKQoKICAgICAgICAgIGlmIFsgIiRTVEFUVVMiID0gIjIwNCIgXTsgdGhlbgogICAgICAgICAgICBlY2hvICJEZWxldGVkIGJyYW5jaDogJHtCUkFOQ0h9IiA+PiAkR0lUSFVCX1NURVBfU1VNTUFSWQogICAgICAgICAgZWxpZiBbICIkU1RBVFVTIiA9ICI0MDQiIF07IHRoZW4KICAgICAgICAgICAgZWNobyAiQnJhbmNoIGFscmVhZHkgZGVsZXRlZDogJHtCUkFOQ0h9IiA+PiAkR0lUSFVCX1NURVBfU1VNTUFSWQogICAgICAgICAgZWxzZQogICAgICAgICAgICBlY2hvICI6Ondhcm5pbmc6OkZhaWxlZCB0byBkZWxldGUgYnJhbmNoICR7QlJBTkNIfSAoSFRUUCAke1NUQVRVU30pIgogICAgICAgICAgZmkK
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Delete feature branches after PR merge
|
||||
|
||||
name: "Branch Cleanup"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
name: Delete merged branch
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request.merged == true &&
|
||||
github.event.pull_request.head.ref != 'dev' &&
|
||||
github.event.pull_request.head.ref != 'main'
|
||||
|
||||
steps:
|
||||
- name: Delete source branch
|
||||
run: |
|
||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
||||
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
||||
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||
|
||||
if [ "$STATUS" = "204" ]; then
|
||||
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "$STATUS" = "404" ]; then
|
||||
echo "Branch already deleted: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "::warning::Failed to delete branch ${BRANCH} (HTTP ${STATUS})"
|
||||
fi
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
IyBESVNBQkxFRCDigJQgYXV0by1yZWxlYXNlIFN0ZXAgMTEgcmVjcmVhdGVzIGRldiBmcm9tIG1haW4gYWZ0ZXIgZXZlcnkgcmVsZWFzZS4KIyBDYXNjYWRlLWRldiBpcyByZWR1bmRhbnQgYW5kIGNhdXNlcyB2ZXJzaW9uIGNvbmZsaWN0cyB3aGVuIGJvdGggbWFpbiBhbmQgZGV2CiMgaGF2ZSBkaWZmZXJlbnQgdmVyc2lvbiBudW1iZXJzIGluIHRlbXBsYXRlRGV0YWlscy54bWwgLyBtYW5pZmVzdC54bWwuCm5hbWU6ICJDYXNjYWRlIE1haW4g4oaSIERldiAoRElTQUJMRUQpIgpvbjogd29ya2Zsb3dfZGlzcGF0Y2gKam9iczoKICBub29wOgogICAgcnVucy1vbjogdWJ1bnR1LWxhdGVzdAogICAgc3RlcHM6CiAgICAgIC0gcnVuOiBlY2hvICJDYXNjYWRlIGRpc2FibGVkIOKAlCBhdXRvLXJlbGVhc2UgaGFuZGxlcyBkZXYgcmVjcmVhdGlvbiIK
|
||||
# DISABLED — auto-release Step 11 recreates dev from main after every release.
|
||||
# Cascade-dev is redundant and causes version conflicts when both main and dev
|
||||
# have different version numbers in templateDetails.xml / manifest.xml.
|
||||
name: "Cascade Main → Dev (DISABLED)"
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
noop:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "Cascade disabled — auto-release handles dev recreation"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1,68 @@
|
||||
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojCiMgU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEdQTC0zLjAtb3ItbGF0ZXIKIwojIEZJTEUgSU5GT1JNQVRJT04KIyBERUZHUk9VUDogR2l0ZWEuV29ya2Zsb3cKIyBJTkdST1VQOiBtb2tvY2xpLlVuaXZlcnNhbAojIFJFUE86IGh0dHBzOi8vZ2l0Lm1va29jb25zdWx0aW5nLnRlY2gvTW9rb0NvbnN1bHRpbmcvbW9rb2NsaQojIFBBVEg6IC8ubW9rb2dpdGVhL3dvcmtmbG93cy9jaS1pc3N1ZS1yZXBvcnRlci55bWwKIyBWRVJTSU9OOiAwMS4wMC4wMAojIEJSSUVGOiBSZXVzYWJsZSB3b3JrZmxvdyDigJQgY3JlYXRlcy91cGRhdGVzIGEgR2l0ZWEgaXNzdWUgd2hlbiBhIENJIGdhdGUgZmFpbHMuCiMgICAgICAgIENsb25lcyBNb2tvQ0xJIGFuZCBydW5zIGNsaS9jaV9pc3N1ZV9yZXBvcnRlci5zaC4KCm5hbWU6ICJVbml2ZXJzYWw6IENJIElzc3VlIFJlcG9ydGVyIgoKb246CiAgd29ya2Zsb3dfY2FsbDoKICAgIGlucHV0czoKICAgICAgZ2F0ZToKICAgICAgICBkZXNjcmlwdGlvbjogIkNJIGdhdGUgbmFtZSAoZS5nLiBQUiBWYWxpZGF0aW9uLCBSZXBvc2l0b3J5IEhlYWx0aCkiCiAgICAgICAgcmVxdWlyZWQ6IHRydWUKICAgICAgICB0eXBlOiBzdHJpbmcKICAgICAgZGV0YWlsczoKICAgICAgICBkZXNjcmlwdGlvbjogIkh1bWFuLXJlYWRhYmxlIGZhaWx1cmUgZGVzY3JpcHRpb24iCiAgICAgICAgcmVxdWlyZWQ6IHRydWUKICAgICAgICB0eXBlOiBzdHJpbmcKICAgICAgc2V2ZXJpdHk6CiAgICAgICAgZGVzY3JpcHRpb246ICJlcnJvciBvciB3YXJuaW5nIgogICAgICAgIHJlcXVpcmVkOiBmYWxzZQogICAgICAgIHR5cGU6IHN0cmluZwogICAgICAgIGRlZmF1bHQ6ICJlcnJvciIKICAgICAgd29ya2Zsb3c6CiAgICAgICAgZGVzY3JpcHRpb246ICJXb3JrZmxvdyBuYW1lIGZvciB0aGUgaXNzdWUgdGl0bGUiCiAgICAgICAgcmVxdWlyZWQ6IGZhbHNlCiAgICAgICAgdHlwZTogc3RyaW5nCiAgICAgICAgZGVmYXVsdDogIiIKICAgIHNlY3JldHM6CiAgICAgIE1PS09HSVRFQV9UT0tFTjoKICAgICAgICByZXF1aXJlZDogdHJ1ZQoKZW52OgogIEZPUkNFX0pBVkFTQ1JJUFRfQUNUSU9OU19UT19OT0RFMjQ6IHRydWUKCmpvYnM6CiAgcmVwb3J0OgogICAgbmFtZTogIlJlcG9ydDogJHt7IGlucHV0cy5nYXRlIH19IgogICAgcnVucy1vbjogdWJ1bnR1LWxhdGVzdAoKICAgIHN0ZXBzOgogICAgICAtIG5hbWU6IENsb25lIE1va29DTEkKICAgICAgICBlbnY6CiAgICAgICAgICBNT0tPR0lURUFfVE9LRU46ICR7eyBzZWNyZXRzLk1PS09HSVRFQV9UT0tFTiB9fQogICAgICAgIHJ1bjogfAogICAgICAgICAgTU9LT0dJVEVBX1VSTD0iJHt7IHZhcnMuR0lURUFfVVJMIHx8ICdodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoJyB9fSIKICAgICAgICAgIGdpdCBjbG9uZSAtLWRlcHRoIDEgLS1maWx0ZXI9YmxvYjpub25lIC0tc3BhcnNlICIke01PS09HSVRFQV9VUkx9L01va29Db25zdWx0aW5nL01va29DTEkuZ2l0IiAvdG1wL21va29jbGkKICAgICAgICAgIGNkIC90bXAvbW9rb2NsaSAmJiBnaXQgc3BhcnNlLWNoZWNrb3V0IHNldCBjbGkvY2lfaXNzdWVfcmVwb3J0ZXIuc2gKCiAgICAgIC0gbmFtZTogUmVwb3J0IENJIGZhaWx1cmUKICAgICAgICBlbnY6CiAgICAgICAgICBNT0tPR0lURUFfVE9LRU46ICR7eyBzZWNyZXRzLk1PS09HSVRFQV9UT0tFTiB9fQogICAgICAgICAgTU9LT0dJVEVBX1VSTDogJHt7IHZhcnMuR0lURUFfVVJMIHx8ICdodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoJyB9fQogICAgICAgIHJ1bjogfAogICAgICAgICAgY2htb2QgK3ggL3RtcC9tb2tvY2xpL2NsaS9jaV9pc3N1ZV9yZXBvcnRlci5zaAogICAgICAgICAgL3RtcC9tb2tvY2xpL2NsaS9jaV9pc3N1ZV9yZXBvcnRlci5zaCBcCiAgICAgICAgICAgIC0tZ2F0ZSAiJHt7IGlucHV0cy5nYXRlIH19IiBcCiAgICAgICAgICAgIC0tZGV0YWlscyAiJHt7IGlucHV0cy5kZXRhaWxzIH19IiBcCiAgICAgICAgICAgIC0tc2V2ZXJpdHkgIiR7eyBpbnB1dHMuc2V2ZXJpdHkgfX0iIFwKICAgICAgICAgICAgLS13b3JrZmxvdyAiJHt7IGlucHV0cy53b3JrZmxvdyB9fSIK
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Reusable workflow — creates/updates a Gitea issue when a CI gate fails.
|
||||
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
|
||||
|
||||
name: "Universal: CI Issue Reporter"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
gate:
|
||||
description: "CI gate name (e.g. PR Validation, Repository Health)"
|
||||
required: true
|
||||
type: string
|
||||
details:
|
||||
description: "Human-readable failure description"
|
||||
required: true
|
||||
type: string
|
||||
severity:
|
||||
description: "error or warning"
|
||||
required: false
|
||||
type: string
|
||||
default: "error"
|
||||
workflow:
|
||||
description: "Workflow name for the issue title"
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
secrets:
|
||||
MOKOGITEA_TOKEN:
|
||||
required: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
report:
|
||||
name: "Report: ${{ inputs.gate }}"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone MokoCLI
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
|
||||
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
|
||||
|
||||
- name: Report CI failure
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
run: |
|
||||
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
|
||||
/tmp/mokocli/cli/ci_issue_reporter.sh \
|
||||
--gate "${{ inputs.gate }}" \
|
||||
--details "${{ inputs.details }}" \
|
||||
--severity "${{ inputs.severity }}" \
|
||||
--workflow "${{ inputs.workflow }}"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1,87 @@
|
||||
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojCiMgU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEdQTC0zLjAtb3ItbGF0ZXIKIwojIEZJTEUgSU5GT1JNQVRJT04KIyBERUZHUk9VUDogR2l0ZWEuV29ya2Zsb3cKIyBJTkdST1VQOiBNb2tvU3RhbmRhcmRzLk1haW50ZW5hbmNlCiMgUkVQTzogaHR0cHM6Ly9naXQubW9rb2NvbnN1bHRpbmcudGVjaC9Nb2tvQ29uc3VsdGluZy9Nb2tvU3RhbmRhcmRzCiMgUEFUSDogLy5naXRlYS93b3JrZmxvd3MvY2xlYW51cC55bWwKIyBWRVJTSU9OOiAwMS4wMC4wMAojIEJSSUVGOiBTY2hlZHVsZWQgY2xlYW51cCDigJQgZGVsZXRlIG1lcmdlZCBicmFuY2hlcyBhbmQgb2xkIHdvcmtmbG93IHJ1bnMKCm5hbWU6ICJVbml2ZXJzYWw6IFJlcG9zaXRvcnkgQ2xlYW51cCIKCm9uOgogIHNjaGVkdWxlOgogICAgLSBjcm9uOiAnMCAzICogKiAwJyAgIyBXZWVrbHkgb24gU3VuZGF5IGF0IDAzOjAwIFVUQwogIHdvcmtmbG93X2Rpc3BhdGNoOgoKcGVybWlzc2lvbnM6CiAgY29udGVudHM6IHdyaXRlCgplbnY6CiAgTU9LT0dJVEVBX1VSTDogJHt7IHZhcnMuR0lURUFfVVJMIHx8ICdodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoJyB9fQoKam9iczoKICBjbGVhbnVwOgogICAgbmFtZTogQ2xlYW4gTWVyZ2VkIEJyYW5jaGVzCiAgICBydW5zLW9uOiB1YnVudHUtbGF0ZXN0CgogICAgc3RlcHM6CiAgICAgIC0gbmFtZTogQ2hlY2tvdXQKICAgICAgICB1c2VzOiBhY3Rpb25zL2NoZWNrb3V0QHY0CiAgICAgICAgd2l0aDoKICAgICAgICAgIGZldGNoLWRlcHRoOiAwCiAgICAgICAgICB0b2tlbjogJHt7IHNlY3JldHMuTU9LT0dJVEVBX1RPS0VOIH19CgogICAgICAtIG5hbWU6IERlbGV0ZSBtZXJnZWQgYnJhbmNoZXMKICAgICAgICBlbnY6CiAgICAgICAgICBNT0tPR0lURUFfVE9LRU46ICR7eyBzZWNyZXRzLk1PS09HSVRFQV9UT0tFTiB9fQogICAgICAgIHJ1bjogfAogICAgICAgICAgZWNobyAiPT09IE1lcmdlZCBCcmFuY2ggQ2xlYW51cCA9PT0iCiAgICAgICAgICBBUEk9IiR7TU9LT0dJVEVBX1VSTH0vYXBpL3YxL3JlcG9zLyR7eyBnaXRodWIucmVwb3NpdG9yeSB9fSIKCiAgICAgICAgICAjIExpc3QgYnJhbmNoZXMgdmlhIEFQSQogICAgICAgICAgQlJBTkNIRVM9JChjdXJsIC1zUyAtSCAiQXV0aG9yaXphdGlvbjogdG9rZW4gJHtNT0tPR0lURUFfVE9LRU59IiBcCiAgICAgICAgICAgICIke0FQSX0vYnJhbmNoZXM/bGltaXQ9NTAiIHwganEgLXIgJy5bXS5uYW1lJykKCiAgICAgICAgICBERUxFVEVEPTAKICAgICAgICAgIGZvciBCUkFOQ0ggaW4gJEJSQU5DSEVTOyBkbwogICAgICAgICAgICAjIFNraXAgcHJvdGVjdGVkIGJyYW5jaGVzCiAgICAgICAgICAgIGNhc2UgIiRCUkFOQ0giIGluCiAgICAgICAgICAgICAgbWFpbnxtYXN0ZXJ8ZGV2ZWxvcHxyZWxlYXNlLyp8aG90Zml4LyopIGNvbnRpbnVlIDs7CiAgICAgICAgICAgIGVzYWMKCiAgICAgICAgICAgICMgQ2hlY2sgaWYgYnJhbmNoIGlzIG1lcmdlZCBpbnRvIG1haW4KICAgICAgICAgICAgaWYgZ2l0IG1lcmdlLWJhc2UgLS1pcy1hbmNlc3RvciAib3JpZ2luLyR7QlJBTkNIfSIgb3JpZ2luL21haW4gMj4vZGV2L251bGw7IHRoZW4KICAgICAgICAgICAgICBlY2hvICIgIERlbGV0aW5nIG1lcmdlZCBicmFuY2g6ICR7QlJBTkNIfSIKICAgICAgICAgICAgICBjdXJsIC1zUyAtWCBERUxFVEUgLUggIkF1dGhvcml6YXRpb246IHRva2VuICR7TU9LT0dJVEVBX1RPS0VOfSIgXAogICAgICAgICAgICAgICAgIiR7QVBJfS9icmFuY2hlcy8ke0JSQU5DSH0iIDI+L2Rldi9udWxsIHx8IHRydWUKICAgICAgICAgICAgICBERUxFVEVEPSQoKERFTEVURUQgKyAxKSkKICAgICAgICAgICAgZmkKICAgICAgICAgIGRvbmUKCiAgICAgICAgICBlY2hvICJEZWxldGVkICR7REVMRVRFRH0gbWVyZ2VkIGJyYW5jaChlcykiCgogICAgICAtIG5hbWU6IENsZWFuIG9sZCB3b3JrZmxvdyBydW5zCiAgICAgICAgZW52OgogICAgICAgICAgTU9LT0dJVEVBX1RPS0VOOiAke3sgc2VjcmV0cy5NT0tPR0lURUFfVE9LRU4gfX0KICAgICAgICBydW46IHwKICAgICAgICAgIGVjaG8gIj09PSBXb3JrZmxvdyBSdW4gQ2xlYW51cCA9PT0iCiAgICAgICAgICBBUEk9IiR7TU9LT0dJVEVBX1VSTH0vYXBpL3YxL3JlcG9zLyR7eyBnaXRodWIucmVwb3NpdG9yeSB9fSIKICAgICAgICAgIENVVE9GRj0kKGRhdGUgLWQgIjMwIGRheXMgYWdvIiArJVktJW0tJWRUJUg6JU06JVNaIDI+L2Rldi9udWxsIHx8IGRhdGUgLXYtMzBkICslWS0lbS0lZFQlSDolTTolU1opCgogICAgICAgICAgIyBHZXQgb2xkIGNvbXBsZXRlZCBydW5zCiAgICAgICAgICBSVU5TPSQoY3VybCAtc1MgLUggIkF1dGhvcml6YXRpb246IHRva2VuICR7TU9LT0dJVEVBX1RPS0VOfSIgXAogICAgICAgICAgICAiJHtBUEl9L2FjdGlvbnMvcnVucz9zdGF0dXM9Y29tcGxldGVkJmxpbWl0PTUwIiB8IFwKICAgICAgICAgICAganEgLXIgIi53b3JrZmxvd19ydW5zW10gfCBzZWxlY3QoLmNyZWF0ZWRfYXQgPCBcIiR7Q1VUT0ZGfVwiKSB8IC5pZCIgMj4vZGV2L251bGwpCgogICAgICAgICAgREVMRVRFRD0wCiAgICAgICAgICBmb3IgUlVOX0lEIGluICRSVU5TOyBkbwogICAgICAgICAgICBjdXJsIC1zUyAtWCBERUxFVEUgLUggIkF1dGhvcml6YXRpb246IHRva2VuICR7TU9LT0dJVEVBX1RPS0VOfSIgXAogICAgICAgICAgICAgICIke0FQSX0vYWN0aW9ucy9ydW5zLyR7UlVOX0lEfSIgMj4vZGV2L251bGwgfHwgdHJ1ZQogICAgICAgICAgICBERUxFVEVEPSQoKERFTEVURUQgKyAxKSkKICAgICAgICAgIGRvbmUKCiAgICAgICAgICBlY2hvICJEZWxldGVkICR7REVMRVRFRH0gb2xkIHdvcmtmbG93IHJ1bihzKSIK
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /.gitea/workflows/cleanup.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
|
||||
|
||||
name: "Universal: Repository Cleanup"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 3 * * 0' # Weekly on Sunday at 03:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
name: Clean Merged Branches
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
|
||||
- name: Delete merged branches
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
echo "=== Merged Branch Cleanup ==="
|
||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
|
||||
# List branches via API
|
||||
BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/branches?limit=50" | jq -r '.[].name')
|
||||
|
||||
DELETED=0
|
||||
for BRANCH in $BRANCHES; do
|
||||
# Skip protected branches
|
||||
case "$BRANCH" in
|
||||
main|master|develop|release/*|hotfix/*) continue ;;
|
||||
esac
|
||||
|
||||
# Check if branch is merged into main
|
||||
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
||||
echo " Deleting merged branch: ${BRANCH}"
|
||||
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
||||
DELETED=$((DELETED + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Deleted ${DELETED} merged branch(es)"
|
||||
|
||||
- name: Clean old workflow runs
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
echo "=== Workflow Run Cleanup ==="
|
||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
# Get old completed runs
|
||||
RUNS=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/actions/runs?status=completed&limit=50" | \
|
||||
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
||||
|
||||
DELETED=0
|
||||
for RUN_ID in $RUNS; do
|
||||
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
||||
DELETED=$((DELETED + 1))
|
||||
done
|
||||
|
||||
echo "Deleted ${DELETED} old workflow run(s)"
|
||||
|
||||
@@ -1 +1,92 @@
|
||||
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojCiMgU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEdQTC0zLjAtb3ItbGF0ZXIKIwojIEZJTEUgSU5GT1JNQVRJT04KIyBERUZHUk9VUDogR2l0ZWEuV29ya2Zsb3cKIyBJTkdST1VQOiBNb2tvU3RhbmRhcmRzLlNlY3VyaXR5CiMgUkVQTzogaHR0cHM6Ly9naXQubW9rb2NvbnN1bHRpbmcudGVjaC9tb2tvY29uc3VsdGluZy10ZWNoL01va29TdGFuZGFyZHMtQVBJCiMgUEFUSDogL3RlbXBsYXRlcy93b3JrZmxvd3MvZ2l0bGVha3MueW1sLnRlbXBsYXRlCiMgVkVSU0lPTjogMDEuMDAuMDAKIyBCUklFRjogU2VjcmV0IHNjYW5uaW5nIOKAlCBkZXRlY3QgbGVha2VkIGNyZWRlbnRpYWxzLCBBUEkga2V5cywgYW5kIHRva2VucwojCiMgKz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSsKIyB8ICAgICAgICAgICAgICAgICAgICAgICAgU0VDUkVUIFNDQU5OSU5HICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAojICs9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0rCiMgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKIyB8ICBTY2FucyBjb21taXRzIGZvciBsZWFrZWQgc2VjcmV0cyB1c2luZyBHaXRsZWFrcy4gICAgICAgICAgICAgICAgICAgICAgfAojIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CiMgfCAgLSBQUiBzY2FuOiBvbmx5IG5ldyBjb21taXRzIGluIHRoZSBQUiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKIyB8ICAtIFNjaGVkdWxlZDogZnVsbCByZXBvIHNjYW4gd2Vla2x5ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAojIHwgIC0gQWxlcnRzIHZpYSBudGZ5IG9uIGZpbmRpbmdzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CiMgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKIyArPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09KwoKbmFtZTogIlVuaXZlcnNhbDogU2VjcmV0IFNjYW5uaW5nIgoKb246CiAgc2NoZWR1bGU6CiAgICAtIGNyb246ICcwIDUgKiAqIDEnICAjIFdlZWtseSBNb25kYXkgMDU6MDAgVVRDCiAgd29ya2Zsb3dfZGlzcGF0Y2g6CgpwZXJtaXNzaW9uczoKICBjb250ZW50czogcmVhZAoKZW52OgogIE5URllfVVJMOiAke3sgdmFycy5OVEZZX1VSTCB8fCAnaHR0cHM6Ly9udGZ5Lm1va29jb25zdWx0aW5nLnRlY2gnIH19CiAgTlRGWV9UT1BJQzogJHt7IHZhcnMuTlRGWV9UT1BJQyB8fCAnZ2l0ZWEtc2VjdXJpdHknIH19Cgpqb2JzOgogIGdpdGxlYWtzOgogICAgbmFtZTogR2l0bGVha3MgU2VjcmV0IFNjYW4KICAgIHJ1bnMtb246IHVidW50dS1sYXRlc3QKCiAgICBzdGVwczoKICAgICAgLSBuYW1lOiBDaGVja291dAogICAgICAgIHVzZXM6IGFjdGlvbnMvY2hlY2tvdXRAdjQKICAgICAgICB3aXRoOgogICAgICAgICAgZmV0Y2gtZGVwdGg6IDAKCiAgICAgIC0gbmFtZTogSW5zdGFsbCBHaXRsZWFrcwogICAgICAgIHJ1bjogfAogICAgICAgICAgR0lUTEVBS1NfVkVSU0lPTj0iOC4yMS4yIgogICAgICAgICAgY3VybCAtc1NMICJodHRwczovL2dpdGh1Yi5jb20vZ2l0bGVha3MvZ2l0bGVha3MvcmVsZWFzZXMvZG93bmxvYWQvdiR7R0lUTEVBS1NfVkVSU0lPTn0vZ2l0bGVha3NfJHtHSVRMRUFLU19WRVJTSU9OfV9saW51eF94NjQudGFyLmd6IiBcCiAgICAgICAgICAgIHwgdGFyIC14eiAtQyAvdXNyL2xvY2FsL2JpbiBnaXRsZWFrcwogICAgICAgICAgZ2l0bGVha3MgdmVyc2lvbgoKICAgICAgLSBuYW1lOiBTY2FuIGZvciBzZWNyZXRzCiAgICAgICAgaWQ6IHNjYW4KICAgICAgICBydW46IHwKICAgICAgICAgIGVjaG8gIiMjIyBTZWNyZXQgU2Nhbm5pbmciID4+ICRHSVRIVUJfU1RFUF9TVU1NQVJZCiAgICAgICAgICBBUkdTPSItLXNvdXJjZSAuIC0tdmVyYm9zZSAtLXJlcG9ydC1mb3JtYXQganNvbiAtLXJlcG9ydC1wYXRoIC90bXAvZ2l0bGVha3MtcmVwb3J0Lmpzb24iCgogICAgICAgICAgaWYgWyAiJHt7IGdpdGh1Yi5ldmVudF9uYW1lIH19IiA9ICJwdWxsX3JlcXVlc3QiIF07IHRoZW4KICAgICAgICAgICAgIyBTY2FuIG9ubHkgUFIgY29tbWl0cwogICAgICAgICAgICBBUkdTPSIkQVJHUyAtLWxvZy1vcHRzPSR7eyBnaXRodWIuZXZlbnQucHVsbF9yZXF1ZXN0LmJhc2Uuc2hhIH19Li4ke3sgZ2l0aHViLmV2ZW50LnB1bGxfcmVxdWVzdC5oZWFkLnNoYSB9fSIKICAgICAgICAgICAgZWNobyAiU2Nhbm5pbmcgUFIgY29tbWl0cyBvbmx5IiA+PiAkR0lUSFVCX1NURVBfU1VNTUFSWQogICAgICAgICAgZWxzZQogICAgICAgICAgICBlY2hvICJGdWxsIHJlcG9zaXRvcnkgc2NhbiIgPj4gJEdJVEhVQl9TVEVQX1NVTU1BUlkKICAgICAgICAgIGZpCgogICAgICAgICAgaWYgZ2l0bGVha3MgZGV0ZWN0ICRBUkdTIDI+JjE7IHRoZW4KICAgICAgICAgICAgZWNobyAicmVzdWx0PWNsZWFuIiA+PiAiJEdJVEhVQl9PVVRQVVQiCiAgICAgICAgICAgIGVjaG8gIioqTm8gc2VjcmV0cyBkZXRlY3RlZC4qKiIgPj4gJEdJVEhVQl9TVEVQX1NVTU1BUlkKICAgICAgICAgIGVsc2UKICAgICAgICAgICAgZWNobyAicmVzdWx0PWZvdW5kIiA+PiAiJEdJVEhVQl9PVVRQVVQiCiAgICAgICAgICAgIEZJTkRJTkdTPSQoanEgbGVuZ3RoIC90bXAvZ2l0bGVha3MtcmVwb3J0Lmpzb24gMj4vZGV2L251bGwgfHwgZWNobyAidW5rbm93biIpCiAgICAgICAgICAgIGVjaG8gIioqJHtGSU5ESU5HU30gcG90ZW50aWFsIHNlY3JldChzKSBkZXRlY3RlZC4qKiIgPj4gJEdJVEhVQl9TVEVQX1NVTU1BUlkKICAgICAgICAgICAgZWNobyAiIiA+PiAkR0lUSFVCX1NURVBfU1VNTUFSWQogICAgICAgICAgICBlY2hvICJSZXZpZXcgdGhlIGZpbmRpbmdzIGFuZCByb3RhdGUgYW55IGV4cG9zZWQgY3JlZGVudGlhbHMgaW1tZWRpYXRlbHkuIiA+PiAkR0lUSFVCX1NURVBfU1VNTUFSWQogICAgICAgICAgICBleGl0IDEKICAgICAgICAgIGZpCgogICAgICAtIG5hbWU6IE5vdGlmeSBvbiBmaW5kaW5ncwogICAgICAgIGlmOiBmYWlsdXJlKCkgJiYgc3RlcHMuc2Nhbi5vdXRwdXRzLnJlc3VsdCA9PSAnZm91bmQnCiAgICAgICAgcnVuOiB8CiAgICAgICAgICBSRVBPPSIke3sgZ2l0aHViLmV2ZW50LnJlcG9zaXRvcnkubmFtZSB9fSIKICAgICAgICAgIGN1cmwgLXNTIFwKICAgICAgICAgICAgLUggIlRpdGxlOiAke1JFUE99IOKAlCBzZWNyZXRzIGRldGVjdGVkIGluIGNvZGUiIFwKICAgICAgICAgICAgLUggIlRhZ3M6IHJvdGF0aW5nX2xpZ2h0LGtleSIgXAogICAgICAgICAgICAtSCAiUHJpb3JpdHk6IHVyZ2VudCIgXAogICAgICAgICAgICAtZCAiR2l0bGVha3MgZm91bmQgcG90ZW50aWFsIHNlY3JldHMuIFJldmlldyBhbmQgcm90YXRlIGNyZWRlbnRpYWxzIGltbWVkaWF0ZWx5LiIgXAogICAgICAgICAgICAiJHtOVEZZX1VSTH0vJHtOVEZZX1RPUElDfSIgfHwgdHJ1ZQo=
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Security
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||
# PATH: /templates/workflows/gitleaks.yml.template
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
||||
#
|
||||
# +========================================================================+
|
||||
# | SECRET SCANNING |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | Scans commits for leaked secrets using Gitleaks. |
|
||||
# | |
|
||||
# | - PR scan: only new commits in the PR |
|
||||
# | - Scheduled: full repo scan weekly |
|
||||
# | - Alerts via ntfy on findings |
|
||||
# | |
|
||||
# +========================================================================+
|
||||
|
||||
name: "Universal: Secret Scanning"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 5 * * 1' # Weekly Monday 05:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
|
||||
|
||||
jobs:
|
||||
gitleaks:
|
||||
name: Gitleaks Secret Scan
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Gitleaks
|
||||
run: |
|
||||
GITLEAKS_VERSION="8.21.2"
|
||||
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
||||
| tar -xz -C /usr/local/bin gitleaks
|
||||
gitleaks version
|
||||
|
||||
- name: Scan for secrets
|
||||
id: scan
|
||||
run: |
|
||||
echo "### Secret Scanning" >> $GITHUB_STEP_SUMMARY
|
||||
ARGS="--source . --verbose --report-format json --report-path /tmp/gitleaks-report.json"
|
||||
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
# Scan only PR commits
|
||||
ARGS="$ARGS --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
|
||||
echo "Scanning PR commits only" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "Full repository scan" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if gitleaks detect $ARGS 2>&1; then
|
||||
echo "result=clean" >> "$GITHUB_OUTPUT"
|
||||
echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "result=found" >> "$GITHUB_OUTPUT"
|
||||
FINDINGS=$(jq length /tmp/gitleaks-report.json 2>/dev/null || echo "unknown")
|
||||
echo "**${FINDINGS} potential secret(s) detected.**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Review the findings and rotate any exposed credentials immediately." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Notify on findings
|
||||
if: failure() && steps.scan.outputs.result == 'found'
|
||||
run: |
|
||||
REPO="${{ github.event.repository.name }}"
|
||||
curl -sS \
|
||||
-H "Title: ${REPO} — secrets detected in code" \
|
||||
-H "Tags: rotating_light,key" \
|
||||
-H "Priority: urgent" \
|
||||
-d "Gitleaks found potential secrets. Review and rotate credentials immediately." \
|
||||
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
||||
|
||||
@@ -1 +1,73 @@
|
||||
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojCiMgU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEdQTC0zLjAtb3ItbGF0ZXIKIwojIEZJTEUgSU5GT1JNQVRJT04KIyBERUZHUk9VUDogR2l0ZWEuV29ya2Zsb3cKIyBJTkdST1VQOiBtb2tvY2xpLkF1dG9tYXRpb24KIyBWRVJTSU9OOiAwMS4wMC4wMAojIEJSSUVGOiBBdXRvLWNyZWF0ZSBmZWF0dXJlIGJyYW5jaCB3aGVuIGFuIGlzc3VlIGlzIG9wZW5lZAoKbmFtZTogIlVuaXZlcnNhbDogSXNzdWUgQnJhbmNoIgoKb246CiAgaXNzdWVzOgogICAgdHlwZXM6IFtvcGVuZWRdCgpwZXJtaXNzaW9uczoKICBjb250ZW50czogd3JpdGUKICBpc3N1ZXM6IHdyaXRlCgplbnY6CiAgTU9LT0dJVEVBX1VSTDogJHt7IHZhcnMuR0lURUFfVVJMIHx8ICdodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoJyB9fQoKam9iczoKICBjcmVhdGUtYnJhbmNoOgogICAgbmFtZTogQ3JlYXRlIGZlYXR1cmUgYnJhbmNoCiAgICBydW5zLW9uOiB1YnVudHUtbGF0ZXN0CiAgICBzdGVwczoKICAgICAgLSBuYW1lOiBDcmVhdGUgYnJhbmNoIGFuZCBjb21tZW50CiAgICAgICAgcnVuOiB8CiAgICAgICAgICBUT0tFTj0iJHt7IHNlY3JldHMuTU9LT0dJVEVBX1RPS0VOIH19IgogICAgICAgICAgQVBJPSIke01PS09HSVRFQV9VUkx9L2FwaS92MS9yZXBvcy8ke3sgZ2l0aHViLnJlcG9zaXRvcnkgfX0iCiAgICAgICAgICBJU1NVRV9OVU09IiR7eyBnaXRodWIuZXZlbnQuaXNzdWUubnVtYmVyIH19IgogICAgICAgICAgSVNTVUVfVElUTEU9IiR7eyBnaXRodWIuZXZlbnQuaXNzdWUudGl0bGUgfX0iCgogICAgICAgICAgIyBCdWlsZCBzbHVnIGZyb20gdGl0bGU6IGxvd2VyY2FzZSwgcmVwbGFjZSBub24tYWxudW0gd2l0aCBkYXNoLCB0cmltCiAgICAgICAgICBTTFVHPSQoZWNobyAiJHtJU1NVRV9USVRMRX0iIHwgdHIgJ1s6dXBwZXI6XScgJ1s6bG93ZXI6XScgfCBzZWQgJ3MvW15hLXowLTldLy0vZycgfCBzZWQgJ3MvLS0qLy0vZycgfCBzZWQgJ3MvXi0vLztzLy0kLy8nIHwgY3V0IC1jMS00MCkKICAgICAgICAgIEJSQU5DSD0iZmVhdHVyZS8ke0lTU1VFX05VTX0tJHtTTFVHfSIKCiAgICAgICAgICAjIENoZWNrIGRldiBicmFuY2ggZXhpc3RzCiAgICAgICAgICBERVZfRVhJU1RTPSQoY3VybCAtc2YgLW8gL2Rldi9udWxsIC13ICcle2h0dHBfY29kZX0nIFwKICAgICAgICAgICAgLUggIkF1dGhvcml6YXRpb246IHRva2VuICR7VE9LRU59IiBcCiAgICAgICAgICAgICIke0FQSX0vYnJhbmNoZXMvZGV2IiAyPi9kZXYvbnVsbCB8fCBlY2hvICIwMDAiKQoKICAgICAgICAgIGlmIFsgIiR7REVWX0VYSVNUU30iICE9ICIyMDAiIF07IHRoZW4KICAgICAgICAgICAgZWNobyAiTm8gZGV2IGJyYW5jaCAtLSBza2lwcGluZyIKICAgICAgICAgICAgZXhpdCAwCiAgICAgICAgICBmaQoKICAgICAgICAgICMgQ3JlYXRlIGJyYW5jaCBmcm9tIGRldgogICAgICAgICAgSFRUUD0kKGN1cmwgLXNmIC1vIC9kZXYvbnVsbCAtdyAnJXtodHRwX2NvZGV9JyAtWCBQT1NUIFwKICAgICAgICAgICAgLUggIkF1dGhvcml6YXRpb246IHRva2VuICR7VE9LRU59IiBcCiAgICAgICAgICAgIC1IICJDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2pzb24iIFwKICAgICAgICAgICAgIiR7QVBJfS9icmFuY2hlcyIgXAogICAgICAgICAgICAtZCAie1wibmV3X2JyYW5jaF9uYW1lXCI6XCIke0JSQU5DSH1cIixcIm9sZF9icmFuY2hfbmFtZVwiOlwiZGV2XCJ9IiAyPi9kZXYvbnVsbCB8fCBlY2hvICIwMDAiKQoKICAgICAgICAgIGlmIFsgIiR7SFRUUH0iID0gIjIwMSIgXTsgdGhlbgogICAgICAgICAgICBlY2hvICJDcmVhdGVkIGJyYW5jaDogJHtCUkFOQ0h9IgoKICAgICAgICAgICAgIyBDb21tZW50IG9uIGlzc3VlIHdpdGggYnJhbmNoIGxpbmsKICAgICAgICAgICAgUkVQT19VUkw9IiR7TU9LT0dJVEVBX1VSTH0vJHt7IGdpdGh1Yi5yZXBvc2l0b3J5IH19IgogICAgICAgICAgICBCT0RZPSJCcmFuY2ggY3JlYXRlZDogW1xgJHtCUkFOQ0h9XGBdKCR7UkVQT19VUkx9L3NyYy9icmFuY2gvJHtCUkFOQ0h9KVxuXG5cYFxgXGBiYXNoXG5naXQgZmV0Y2ggb3JpZ2luXG5naXQgY2hlY2tvdXQgJHtCUkFOQ0h9XG5cYFxgXGAiCgogICAgICAgICAgICBjdXJsIC1zZiAtWCBQT1NUIFwKICAgICAgICAgICAgICAtSCAiQXV0aG9yaXphdGlvbjogdG9rZW4gJHtUT0tFTn0iIFwKICAgICAgICAgICAgICAtSCAiQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9qc29uIiBcCiAgICAgICAgICAgICAgIiR7QVBJfS9pc3N1ZXMvJHtJU1NVRV9OVU19L2NvbW1lbnRzIiBcCiAgICAgICAgICAgICAgLWQgIntcImJvZHlcIjpcIiR7Qk9EWX1cIn0iID4gL2Rldi9udWxsIDI+JjEKCiAgICAgICAgICAgIGVjaG8gIkNvbW1lbnRlZCBvbiBpc3N1ZSAjJHtJU1NVRV9OVU19IgogICAgICAgICAgZWxzZQogICAgICAgICAgICBlY2hvICJGYWlsZWQgdG8gY3JlYXRlIGJyYW5jaCAoSFRUUCAke0hUVFB9KSAtLSBtYXkgYWxyZWFkeSBleGlzdCIKICAgICAgICAgIGZpCg==
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Automation
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
env:
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
jobs:
|
||||
create-branch:
|
||||
name: Create feature branch
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create branch and comment
|
||||
run: |
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||
|
||||
# Build slug from title: lowercase, replace non-alnum with dash, trim
|
||||
SLUG=$(echo "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
|
||||
BRANCH="feature/${ISSUE_NUM}-${SLUG}"
|
||||
|
||||
# Check dev branch exists
|
||||
DEV_EXISTS=$(curl -sf -o /dev/null -w '%{http_code}' \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
"${API}/branches/dev" 2>/dev/null || echo "000")
|
||||
|
||||
if [ "${DEV_EXISTS}" != "200" ]; then
|
||||
echo "No dev branch -- skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create branch from dev
|
||||
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/branches" \
|
||||
-d "{\"new_branch_name\":\"${BRANCH}\",\"old_branch_name\":\"dev\"}" 2>/dev/null || echo "000")
|
||||
|
||||
if [ "${HTTP}" = "201" ]; then
|
||||
echo "Created branch: ${BRANCH}"
|
||||
|
||||
# Comment on issue with branch link
|
||||
REPO_URL="${MOKOGITEA_URL}/${{ github.repository }}"
|
||||
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
||||
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/issues/${ISSUE_NUM}/comments" \
|
||||
-d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1
|
||||
|
||||
echo "Commented on issue #${ISSUE_NUM}"
|
||||
else
|
||||
echo "Failed to create branch (HTTP ${HTTP}) -- may already exist"
|
||||
fi
|
||||
|
||||
@@ -1 +1,70 @@
|
||||
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojCiMgU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEdQTC0zLjAtb3ItbGF0ZXIKIwojIEZJTEUgSU5GT1JNQVRJT04KIyBERUZHUk9VUDogR2l0ZWEuV29ya2Zsb3cKIyBJTkdST1VQOiBNb2tvU3RhbmRhcmRzLk5vdGlmaWNhdGlvbnMKIyBSRVBPOiBodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoL01va29Db25zdWx0aW5nL01va29TdGFuZGFyZHMKIyBQQVRIOiAvLmdpdGVhL3dvcmtmbG93cy9ub3RpZnkueW1sCiMgVkVSU0lPTjogMDEuMDAuMDAKIyBCUklFRjogUHVzaCBub3RpZmljYXRpb25zIHZpYSBudGZ5IG9uIHJlbGVhc2Ugc3VjY2VzcyBvciB3b3JrZmxvdyBmYWlsdXJlCgpuYW1lOiAiVW5pdmVyc2FsOiBOb3RpZmljYXRpb25zIgoKb246CiAgd29ya2Zsb3dfcnVuOgogICAgd29ya2Zsb3dzOgogICAgICAtICJKb29tbGEgQnVpbGQgJiBSZWxlYXNlIgogICAgICAtICJKb29tbGEgRXh0ZW5zaW9uIENJIgogICAgICAtICJEZXBsb3kiCiAgICB0eXBlczoKICAgICAgLSBjb21wbGV0ZWQKCnBlcm1pc3Npb25zOgogIGNvbnRlbnRzOiByZWFkCgplbnY6CiAgTlRGWV9VUkw6ICR7eyB2YXJzLk5URllfVVJMIHx8ICdodHRwczovL250ZnkubW9rb2NvbnN1bHRpbmcudGVjaCcgfX0KICBOVEZZX1RPUElDOiAke3sgdmFycy5OVEZZX1RPUElDIHx8ICdnaXRlYS1yZWxlYXNlcycgfX0KCmpvYnM6CiAgbm90aWZ5OgogICAgbmFtZTogU2VuZCBOb3RpZmljYXRpb24KICAgIHJ1bnMtb246IHVidW50dS1sYXRlc3QKICAgIGlmOiA+LQogICAgICBnaXRodWIuZXZlbnQud29ya2Zsb3dfcnVuLmNvbmNsdXNpb24gPT0gJ3N1Y2Nlc3MnIHx8CiAgICAgIGdpdGh1Yi5ldmVudC53b3JrZmxvd19ydW4uY29uY2x1c2lvbiA9PSAnZmFpbHVyZScKCiAgICBzdGVwczoKICAgICAgLSBuYW1lOiBOb3RpZnkgb24gc3VjY2VzcyAocmVsZWFzZXMgb25seSkKICAgICAgICBpZjogPi0KICAgICAgICAgIGdpdGh1Yi5ldmVudC53b3JrZmxvd19ydW4uY29uY2x1c2lvbiA9PSAnc3VjY2VzcycgJiYKICAgICAgICAgIGNvbnRhaW5zKGdpdGh1Yi5ldmVudC53b3JrZmxvd19ydW4ubmFtZSwgJ1JlbGVhc2UnKQogICAgICAgIHJ1bjogfAogICAgICAgICAgUkVQTz0iJHt7IGdpdGh1Yi5ldmVudC5yZXBvc2l0b3J5Lm5hbWUgfX0iCiAgICAgICAgICBXT1JLRkxPVz0iJHt7IGdpdGh1Yi5ldmVudC53b3JrZmxvd19ydW4ubmFtZSB9fSIKICAgICAgICAgIFVSTD0iJHt7IGdpdGh1Yi5ldmVudC53b3JrZmxvd19ydW4uaHRtbF91cmwgfX0iCgogICAgICAgICAgY3VybCAtc1MgXAogICAgICAgICAgICAtSCAiVGl0bGU6ICR7UkVQT30gcmVsZWFzZWQiIFwKICAgICAgICAgICAgLUggIlRhZ3M6IHdoaXRlX2NoZWNrX21hcmsscGFja2FnZSIgXAogICAgICAgICAgICAtSCAiUHJpb3JpdHk6IGRlZmF1bHQiIFwKICAgICAgICAgICAgLUggIkNsaWNrOiAke1VSTH0iIFwKICAgICAgICAgICAgLWQgIiR7V09SS0ZMT1d9IGNvbXBsZXRlZCBzdWNjZXNzZnVsbHkuIiBcCiAgICAgICAgICAgICIke05URllfVVJMfS8ke05URllfVE9QSUN9IgoKICAgICAgLSBuYW1lOiBOb3RpZnkgb24gZmFpbHVyZQogICAgICAgIGlmOiBnaXRodWIuZXZlbnQud29ya2Zsb3dfcnVuLmNvbmNsdXNpb24gPT0gJ2ZhaWx1cmUnCiAgICAgICAgcnVuOiB8CiAgICAgICAgICBSRVBPPSIke3sgZ2l0aHViLmV2ZW50LnJlcG9zaXRvcnkubmFtZSB9fSIKICAgICAgICAgIFdPUktGTE9XPSIke3sgZ2l0aHViLmV2ZW50LndvcmtmbG93X3J1bi5uYW1lIH19IgogICAgICAgICAgVVJMPSIke3sgZ2l0aHViLmV2ZW50LndvcmtmbG93X3J1bi5odG1sX3VybCB9fSIKCiAgICAgICAgICBjdXJsIC1zUyBcCiAgICAgICAgICAgIC1IICJUaXRsZTogJHtSRVBPfSB3b3JrZmxvdyBmYWlsZWQiIFwKICAgICAgICAgICAgLUggIlRhZ3M6IHgsd2FybmluZyIgXAogICAgICAgICAgICAtSCAiUHJpb3JpdHk6IGhpZ2giIFwKICAgICAgICAgICAgLUggIkNsaWNrOiAke1VSTH0iIFwKICAgICAgICAgICAgLWQgIiR7V09SS0ZMT1d9IGZhaWxlZC4gQ2hlY2sgdGhlIHJ1biBmb3IgZGV0YWlscy4iIFwKICAgICAgICAgICAgIiR7TlRGWV9VUkx9LyR7TlRGWV9UT1BJQ30iCg==
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Notifications
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /.gitea/workflows/notify.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Push notifications via ntfy on release success or workflow failure
|
||||
|
||||
name: "Universal: Notifications"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- "Joomla Build & Release"
|
||||
- "Joomla Extension CI"
|
||||
- "Deploy"
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-releases' }}
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
name: Send Notification
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.workflow_run.conclusion == 'success' ||
|
||||
github.event.workflow_run.conclusion == 'failure'
|
||||
|
||||
steps:
|
||||
- name: Notify on success (releases only)
|
||||
if: >-
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
contains(github.event.workflow_run.name, 'Release')
|
||||
run: |
|
||||
REPO="${{ github.event.repository.name }}"
|
||||
WORKFLOW="${{ github.event.workflow_run.name }}"
|
||||
URL="${{ github.event.workflow_run.html_url }}"
|
||||
|
||||
curl -sS \
|
||||
-H "Title: ${REPO} released" \
|
||||
-H "Tags: white_check_mark,package" \
|
||||
-H "Priority: default" \
|
||||
-H "Click: ${URL}" \
|
||||
-d "${WORKFLOW} completed successfully." \
|
||||
"${NTFY_URL}/${NTFY_TOPIC}"
|
||||
|
||||
- name: Notify on failure
|
||||
if: github.event.workflow_run.conclusion == 'failure'
|
||||
run: |
|
||||
REPO="${{ github.event.repository.name }}"
|
||||
WORKFLOW="${{ github.event.workflow_run.name }}"
|
||||
URL="${{ github.event.workflow_run.html_url }}"
|
||||
|
||||
curl -sS \
|
||||
-H "Title: ${REPO} workflow failed" \
|
||||
-H "Tags: x,warning" \
|
||||
-H "Priority: high" \
|
||||
-H "Click: ${URL}" \
|
||||
-d "${WORKFLOW} failed. Check the run for details." \
|
||||
"${NTFY_URL}/${NTFY_TOPIC}"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1,71 @@
|
||||
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojCiMgU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEdQTC0zLjAtb3ItbGF0ZXIKIwojIEZJTEUgSU5GT1JNQVRJT04KIyBERUZHUk9VUDogR2l0ZWEuV29ya2Zsb3cKIyBJTkdST1VQOiBtb2tvY2xpLlZhbGlkYXRpb24KIyBSRVBPOiBodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoL01va29Db25zdWx0aW5nL21va29jbGkKIyBQQVRIOiAvdGVtcGxhdGVzL3dvcmtmbG93cy9qb29tbGEvcHItbWV0YWRhdGEtY2hlY2sueW1sLnRlbXBsYXRlCiMgVkVSU0lPTjogMDEuMDAuMDAKIyBCUklFRjogVmFsaWRhdGUgTW9rb0dpdGVhIG1ldGFkYXRhIG1hdGNoZXMgSm9vbWxhIGV4dGVuc2lvbiBtYW5pZmVzdCBvbiBQUnMKCm5hbWU6ICJKb29tbGE6IE1ldGFkYXRhIFZhbGlkYXRpb24iCgpvbjoKICBwdWxsX3JlcXVlc3Q6CiAgICB0eXBlczogW29wZW5lZCwgc3luY2hyb25pemUsIHJlb3BlbmVkLCBjb252ZXJ0ZWRfdG9fZHJhZnQsIHJlYWR5X2Zvcl9yZXZpZXddCgpwZXJtaXNzaW9uczoKICBjb250ZW50czogcmVhZAoKZW52OgogIE1PS09HSVRFQV9VUkw6ICR7eyB2YXJzLkdJVEVBX1VSTCB8fCAnaHR0cHM6Ly9naXQubW9rb2NvbnN1bHRpbmcudGVjaCcgfX0KICBHSVRFQV9PUkc6ICR7eyB2YXJzLkdJVEVBX09SRyB8fCBnaXRodWIucmVwb3NpdG9yeV9vd25lciB9fQogIEdJVEVBX1JFUE86ICR7eyB2YXJzLkdJVEVBX1JFUE8gfHwgZ2l0aHViLmV2ZW50LnJlcG9zaXRvcnkubmFtZSB9fQoKam9iczoKICB2YWxpZGF0ZS1tZXRhZGF0YToKICAgIG5hbWU6ICJWYWxpZGF0ZSBKb29tbGEgTWV0YWRhdGEiCiAgICBydW5zLW9uOiB1YnVudHUtbGF0ZXN0CgogICAgc3RlcHM6CiAgICAgIC0gbmFtZTogQ2hlY2tvdXQKICAgICAgICB1c2VzOiBhY3Rpb25zL2NoZWNrb3V0QHY0CgogICAgICAtIG5hbWU6IFNldHVwIG1va29jbGkgdG9vbHMKICAgICAgICBlbnY6CiAgICAgICAgICBNT0tPX0NMT05FX1RPS0VOOiAke3sgc2VjcmV0cy5NT0tPR0lURUFfVE9LRU4gfX0KICAgICAgICAgIE1PS09fQ0xPTkVfSE9TVDogZ2l0Lm1va29jb25zdWx0aW5nLnRlY2gvTW9rb0NvbnN1bHRpbmcKICAgICAgICBydW46IHwKICAgICAgICAgIGlmIFsgLWYgL29wdC9tb2tvY2xpL2NsaS9qb29tbGFfbWV0YWRhdGFfdmFsaWRhdGUucGhwIF0gJiYgWyAtZiAvb3B0L21va29jbGkvdmVuZG9yL2F1dG9sb2FkLnBocCBdOyB0aGVuCiAgICAgICAgICAgIGVjaG8gVXNpbmcgcHJlLWluc3RhbGxlZCAvb3B0L21va29jbGkKICAgICAgICAgICAgZWNobyBNT0tPX0NMST0vb3B0L21va29jbGkvY2xpID4+ICRHSVRIVUJfRU5WCiAgICAgICAgICBlbHNlCiAgICAgICAgICAgIGVjaG8gRmFsbGluZyBiYWNrIHRvIGZyZXNoIGNsb25lCiAgICAgICAgICAgIGlmICEgY29tbWFuZCAtdiBjb21wb3NlciA+IC9kZXYvbnVsbCAyPiYxOyB0aGVuCiAgICAgICAgICAgICAgc3VkbyBhcHQtZ2V0IHVwZGF0ZSAtcXEgJiYgc3VkbyBhcHQtZ2V0IGluc3RhbGwgLXkgLXFxIHBocC1jbGkgcGhwLW1ic3RyaW5nIHBocC14bWwgcGhwLXppcCBwaHAtY3VybCBjb21wb3NlciA+IC9kZXYvbnVsbCAyPiYxCiAgICAgICAgICAgIGZpCiAgICAgICAgICAgIHJtIC1yZiAvdG1wL21va29jbGkKICAgICAgICAgICAgQ0xPTkVfVVJMPWh0dHBzOi8veC1hY2Nlc3MtdG9rZW46JHtNT0tPX0NMT05FX1RPS0VOfUAke01PS09fQ0xPTkVfSE9TVH0vbW9rb2NsaS5naXQKICAgICAgICAgICAgZ2l0IGNsb25lIC0tZGVwdGggMSAtLWJyYW5jaCBtYWluIC0tcXVpZXQgJENMT05FX1VSTCAvdG1wL21va29jbGkKICAgICAgICAgICAgY2QgL3RtcC9tb2tvY2xpICYmIGNvbXBvc2VyIGluc3RhbGwgLS1uby1kZXYgLS1uby1pbnRlcmFjdGlvbiAtLXF1aWV0CiAgICAgICAgICAgIGVjaG8gTU9LT19DTEk9L3RtcC9tb2tvY2xpL2NsaSA+PiAkR0lUSFVCX0VOVgogICAgICAgICAgZmkKCiAgICAgIC0gbmFtZTogVmFsaWRhdGUgbWV0YWRhdGEgYWdhaW5zdCBKb29tbGEgbWFuaWZlc3QKICAgICAgICBlbnY6CiAgICAgICAgICBNT0tPR0lURUFfVE9LRU46ICR7eyBzZWNyZXRzLk1PS09HSVRFQV9UT0tFTiB9fQogICAgICAgIHJ1bjogfAogICAgICAgICAgcGhwICR7TU9LT19DTEl9L2pvb21sYV9tZXRhZGF0YV92YWxpZGF0ZS5waHAgXAogICAgICAgICAgICAtLXBhdGggLiBcCiAgICAgICAgICAgIC0tdG9rZW4gIiR7TU9LT0dJVEVBX1RPS0VOfSIgXAogICAgICAgICAgICAtLW9yZyAiJHtHSVRFQV9PUkd9IiBcCiAgICAgICAgICAgIC0tcmVwbyAiJHtHSVRFQV9SRVBPfSIgXAogICAgICAgICAgICAtLWFwaS1iYXNlICIke01PS09HSVRFQV9VUkx9L2FwaS92MSIgXAogICAgICAgICAgICAtLWNpCgogICAgICAgICAgaWYgWyAkPyAtbmUgMCBdOyB0aGVuCiAgICAgICAgICAgIGVjaG8gIjo6ZXJyb3I6Okpvb21sYSBtZXRhZGF0YSBtaXNtYXRjaCDigJQgdXBkYXRlIGRlbGl2ZXJ5IHdpbGwgZmFpbC4gUnVuICdwaHAgY2xpL2pvb21sYV9tZXRhZGF0YV92YWxpZGF0ZS5waHAnIGxvY2FsbHkgdG8gc2VlIGRldGFpbHMuIgogICAgICAgICAgICBleGl0IDEKICAgICAgICAgIGZpCg==
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Validation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /templates/workflows/joomla/pr-metadata-check.yml.template
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Validate MokoGitea metadata matches Joomla extension manifest on PRs
|
||||
|
||||
name: "Joomla: Metadata Validation"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, converted_to_draft, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
jobs:
|
||||
validate-metadata:
|
||||
name: "Validate Joomla Metadata"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup mokocli tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
if [ -f /opt/mokocli/cli/joomla_metadata_validate.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/mokocli
|
||||
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||
else
|
||||
echo Falling back to fresh clone
|
||||
if ! command -v composer > /dev/null 2>&1; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
|
||||
fi
|
||||
rm -rf /tmp/mokocli
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Validate metadata against Joomla manifest
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
php ${MOKO_CLI}/joomla_metadata_validate.php \
|
||||
--path . \
|
||||
--token "${MOKOGITEA_TOKEN}" \
|
||||
--org "${GITEA_ORG}" \
|
||||
--repo "${GITEA_REPO}" \
|
||||
--api-base "${MOKOGITEA_URL}/api/v1" \
|
||||
--ci
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "::error::Joomla metadata mismatch — update delivery will fail. Run 'php cli/joomla_metadata_validate.php' locally to see details."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1,71 @@
|
||||
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojCiMgU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEdQTC0zLjAtb3ItbGF0ZXIKIwojIEZJTEUgSU5GT1JNQVRJT04KIyBERUZHUk9VUDogR2l0ZWEuV29ya2Zsb3cKIyBJTkdST1VQOiBtb2tvY2xpLlVuaXZlcnNhbAojIFJFUE86IGh0dHBzOi8vZ2l0Lm1va29jb25zdWx0aW5nLnRlY2gvTW9rb0NvbnN1bHRpbmcvbW9rb2NsaQojIFBBVEg6IC8ubW9rb2dpdGVhL3dvcmtmbG93cy9yYy1yZXZlcnQueW1sCiMgVkVSU0lPTjogMDkuMjMuMDAKIyBCUklFRjogUmVuYW1lIHJjLyBicmFuY2ggYmFjayB0byBkZXYvIHdoZW4gUFIgaXMgY2xvc2VkIHdpdGhvdXQgbWVyZ2UKCm5hbWU6ICJSQyBSZXZlcnQiCgpvbjoKICBwdWxsX3JlcXVlc3Q6CiAgICB0eXBlczogW2Nsb3NlZF0KCmVudjoKICBGT1JDRV9KQVZBU0NSSVBUX0FDVElPTlNfVE9fTk9ERTI0OiB0cnVlCgpqb2JzOgogIHJldmVydDoKICAgIG5hbWU6IFJlbmFtZSByYy8gYmFjayB0byBkZXYvCiAgICBydW5zLW9uOiB1YnVudHUtbGF0ZXN0CiAgICBpZjogPi0KICAgICAgZ2l0aHViLmV2ZW50LnB1bGxfcmVxdWVzdC5tZXJnZWQgPT0gZmFsc2UgJiYKICAgICAgc3RhcnRzV2l0aChnaXRodWIuZXZlbnQucHVsbF9yZXF1ZXN0LmhlYWQucmVmLCAncmMvJykKCiAgICBzdGVwczoKICAgICAgLSBuYW1lOiBSZW5hbWUgYnJhbmNoCiAgICAgICAgZW52OgogICAgICAgICAgQlJBTkNIOiAke3sgZ2l0aHViLmV2ZW50LnB1bGxfcmVxdWVzdC5oZWFkLnJlZiB9fQogICAgICAgICAgUkVQTzogJHt7IGdpdGh1Yi5yZXBvc2l0b3J5IH19CiAgICAgICAgICBHSVRFQV9VUkw6ICR7eyB2YXJzLkdJVEVBX1VSTCB8fCAnaHR0cHM6Ly9naXQubW9rb2NvbnN1bHRpbmcudGVjaCcgfX0KICAgICAgICAgIFRPS0VOOiAke3sgc2VjcmV0cy5NT0tPR0lURUFfVE9LRU4gfX0KICAgICAgICBydW46IHwKICAgICAgICAgIHNldCAtZXVvIHBpcGVmYWlsCiAgICAgICAgICAjIEJSQU5DSCBpcyBhdHRhY2tlci1jb250cm9sbGVkIChQUiBoZWFkIHJlZikuIFN0cmljdCBhbGxvd2xpc3QgYmVmb3JlIEFOWSB1c2UuCiAgICAgICAgICBpZiAhIHByaW50ZiAnJXMnICIkQlJBTkNIIiB8IGdyZXAgLUVxICdecmMvW0EtWmEtejAtOS5fLy1dKyQnOyB0aGVuCiAgICAgICAgICAgIGVjaG8gIjo6ZXJyb3I6OlJlZnVzaW5nIHVuc2FmZSBicmFuY2ggbmFtZTogJEJSQU5DSCI7IGV4aXQgMQogICAgICAgICAgZmkKICAgICAgICAgIFNVRkZJWD0iJHtCUkFOQ0gjcmMvfSIKICAgICAgICAgIERFVl9CUkFOQ0g9ImRldi8ke1NVRkZJWH0iCiAgICAgICAgICBBUEk9IiR7R0lURUFfVVJMfS9hcGkvdjEvcmVwb3MvJHtSRVBPfS9icmFuY2hlcyIKCiAgICAgICAgICAjIENyZWF0ZSBkZXYvIGJyYW5jaCBmcm9tIHJjLyBicmFuY2gKICAgICAgICAgIFNUQVRVUz0kKGN1cmwgLXNmIC1vIC9kZXYvbnVsbCAtdyAiJXtodHRwX2NvZGV9IiAtWCBQT1NUIFwKICAgICAgICAgICAgLUggIkF1dGhvcml6YXRpb246IHRva2VuICR7VE9LRU59IiBcCiAgICAgICAgICAgIC1IICJDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2pzb24iIFwKICAgICAgICAgICAgLWQgIntcIm5ld19icmFuY2hfbmFtZVwiOiBcIiR7REVWX0JSQU5DSH1cIiwgXCJvbGRfYnJhbmNoX25hbWVcIjogXCIke0JSQU5DSH1cIn0iIFwKICAgICAgICAgICAgIiR7QVBJfSIgMj4vZGV2L251bGwgfHwgdHJ1ZSkKICAgICAgICAgIGlmIFsgIiRTVEFUVVMiID0gIjIwMSIgXTsgdGhlbgogICAgICAgICAgICBlY2hvICJDcmVhdGVkIGJyYW5jaDogJHtERVZfQlJBTkNIfSIgPj4gIiRHSVRIVUJfU1RFUF9TVU1NQVJZIgogICAgICAgICAgZWxzZQogICAgICAgICAgICBlY2hvICI6OmVycm9yOjpGYWlsZWQgdG8gY3JlYXRlICR7REVWX0JSQU5DSH0gZnJvbSAke0JSQU5DSH0gKEhUVFAgJHtTVEFUVVN9KSI7IGV4aXQgMQogICAgICAgICAgZmkKCiAgICAgICAgICAjIFJlYWQgQlJBTkNIIGZyb20gdGhlIGVudmlyb25tZW50IGluc2lkZSBQSFAgKGdldGVudiwgbm8gc3RyaW5nIGludGVycG9sYXRpb24gLT4gbm8gUEhQIGluamVjdGlvbikKICAgICAgICAgIEVOQ09ERUQ9JChwaHAgLXIgJ2VjaG8gcmF3dXJsZW5jb2RlKGdldGVudigiQlJBTkNIIikpOycpCiAgICAgICAgICBTVEFUVVM9JChjdXJsIC1zZiAtbyAvZGV2L251bGwgLXcgIiV7aHR0cF9jb2RlfSIgLVggREVMRVRFIFwKICAgICAgICAgICAgLUggIkF1dGhvcml6YXRpb246IHRva2VuICR7VE9LRU59IiBcCiAgICAgICAgICAgICIke0FQSX0vJHtFTkNPREVEfSIgMj4vZGV2L251bGwgfHwgdHJ1ZSkKICAgICAgICAgIGlmIFsgIiRTVEFUVVMiID0gIjIwNCIgXTsgdGhlbgogICAgICAgICAgICBlY2hvICJEZWxldGVkIGJyYW5jaDogJHtCUkFOQ0h9IiA+PiAiJEdJVEhVQl9TVEVQX1NVTU1BUlkiCiAgICAgICAgICBlbHNlCiAgICAgICAgICAgIGVjaG8gIjo6d2FybmluZzo6RmFpbGVkIHRvIGRlbGV0ZSAke0JSQU5DSH0gKEhUVFAgJHtTVEFUVVN9KSIKICAgICAgICAgIGZpCgogICAgICAgICAgZWNobyAiIyMjIFJDIFJldmVydGVkIiA+PiAiJEdJVEhVQl9TVEVQX1NVTU1BUlkiCiAgICAgICAgICBlY2hvICIke0JSQU5DSH0g4oaSICR7REVWX0JSQU5DSH0iID4+ICIkR0lUSFVCX1NURVBfU1VNTUFSWSIK
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /.mokogitea/workflows/rc-revert.yml
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
|
||||
|
||||
name: "RC Revert"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
revert:
|
||||
name: Rename rc/ back to dev/
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request.merged == false &&
|
||||
startsWith(github.event.pull_request.head.ref, 'rc/')
|
||||
|
||||
steps:
|
||||
- name: Rename branch
|
||||
env:
|
||||
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||
REPO: ${{ github.repository }}
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
|
||||
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
|
||||
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
|
||||
fi
|
||||
SUFFIX="${BRANCH#rc/}"
|
||||
DEV_BRANCH="dev/${SUFFIX}"
|
||||
API="${GITEA_URL}/api/v1/repos/${REPO}/branches"
|
||||
|
||||
# Create dev/ branch from rc/ branch
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
|
||||
"${API}" 2>/dev/null || true)
|
||||
if [ "$STATUS" = "201" ]; then
|
||||
echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
|
||||
fi
|
||||
|
||||
# Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
|
||||
ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||
if [ "$STATUS" = "204" ]; then
|
||||
echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
||||
fi
|
||||
|
||||
echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1,81 @@
|
||||
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojCiMgU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEdQTC0zLjAtb3ItbGF0ZXIKIwojIEZJTEUgSU5GT1JNQVRJT04KIyBERUZHUk9VUDogR2l0ZWEuV29ya2Zsb3cKIyBJTkdST1VQOiBtb2tvY2xpLlVuaXZlcnNhbAojIFJFUE86IGh0dHBzOi8vZ2l0Lm1va29jb25zdWx0aW5nLnRlY2gvTW9rb0NvbnN1bHRpbmcvbW9rb2NsaQojIFBBVEg6IC8ubW9rb2dpdGVhL3dvcmtmbG93cy93b3JrZmxvdy1zeW5jLXRyaWdnZXIueW1sCiMgVkVSU0lPTjogMDEuMDEuMDAKIyBCUklFRjogVHJpZ2dlciB3b3JrZmxvdyBzeW5jIHRvIGxpdmUgcmVwb3Mgd2hlbiBhIFBSIGlzIG1lcmdlZCB0byBtYWluCgpuYW1lOiAiVW5pdmVyc2FsOiBXb3JrZmxvdyBTeW5jIFRyaWdnZXIiCgpvbjoKICB3b3JrZmxvd19kaXNwYXRjaDoKICBwdWxsX3JlcXVlc3Q6CiAgICB0eXBlczogW2Nsb3NlZF0KICAgIGJyYW5jaGVzOgogICAgICAtIG1haW4KCmVudjoKICBGT1JDRV9KQVZBU0NSSVBUX0FDVElPTlNfVE9fTk9ERTI0OiB0cnVlCgpqb2JzOgogIHN5bmM6CiAgICBuYW1lOiBTeW5jIHdvcmtmbG93cyB0byBsaXZlIHJlcG9zCiAgICBydW5zLW9uOiB1YnVudHUtbGF0ZXN0CiAgICBpZjogPi0KICAgICAgZ2l0aHViLmV2ZW50X25hbWUgPT0gJ3dvcmtmbG93X2Rpc3BhdGNoJyB8fAogICAgICAoZ2l0aHViLmV2ZW50LnB1bGxfcmVxdWVzdC5tZXJnZWQgPT0gdHJ1ZSAmJgogICAgICAhY29udGFpbnMoZ2l0aHViLmV2ZW50LnB1bGxfcmVxdWVzdC50aXRsZSwgJ1tza2lwIHN5bmNdJykpCgogICAgc3RlcHM6CiAgICAgIC0gbmFtZTogRGV0ZXJtaW5lIHBsYXRmb3JtIGZyb20gcmVwbyBuYW1lCiAgICAgICAgaWQ6IHBsYXRmb3JtCiAgICAgICAgcnVuOiB8CiAgICAgICAgICBSRVBPPSIke3sgZ2l0aHViLmV2ZW50LnJlcG9zaXRvcnkubmFtZSB9fSIKICAgICAgICAgIGNhc2UgIiRSRVBPIiBpbgogICAgICAgICAgICBUZW1wbGF0ZS1Kb29tbGEpICAgUExBVEZPUk09Impvb21sYSIgOzsKICAgICAgICAgICAgVGVtcGxhdGUtRG9saWJhcnIpIFBMQVRGT1JNPSJkb2xpYmFyciIgOzsKICAgICAgICAgICAgVGVtcGxhdGUtR28pICAgICAgIFBMQVRGT1JNPSJnbyIgOzsKICAgICAgICAgICAgVGVtcGxhdGUtTUNQKSAgICAgIFBMQVRGT1JNPSJtY3AiIDs7CiAgICAgICAgICAgIFRlbXBsYXRlLUdlbmVyaWMpICBQTEFURk9STT0iIiA7OwogICAgICAgICAgICAqKSAgICAgICAgICAgICAgICAgUExBVEZPUk09IiIgOzsKICAgICAgICAgIGVzYWMKICAgICAgICAgIGVjaG8gInBsYXRmb3JtPSRQTEFURk9STSIgPj4gIiRHSVRIVUJfT1VUUFVUIgogICAgICAgICAgZWNobyAiUGxhdGZvcm06ICR7UExBVEZPUk06LWFsbH0iCgogICAgICAtIG5hbWU6IENsb25lIG1va29jbGkKICAgICAgICBlbnY6CiAgICAgICAgICBNT0tPR0lURUFfVE9LRU46ICR7eyBzZWNyZXRzLk1PS09HSVRFQV9UT0tFTiB9fQogICAgICAgIHJ1bjogfAogICAgICAgICAgTU9LT0dJVEVBX1VSTD0iJHt7IHZhcnMuR0lURUFfVVJMIHx8ICdodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoJyB9fSIKICAgICAgICAgIGdpdCBjbG9uZSAtLWRlcHRoIDEgIiR7TU9LT0dJVEVBX1VSTH0vTW9rb0NvbnN1bHRpbmcvbW9rb2NsaS5naXQiIC90bXAvbW9rb2NsaQoKICAgICAgLSBuYW1lOiBJbnN0YWxsIFBIUAogICAgICAgIHJ1bjogfAogICAgICAgICAgaWYgISBjb21tYW5kIC12IHBocCAmPiAvZGV2L251bGw7IHRoZW4KICAgICAgICAgICAgYXB0LWdldCB1cGRhdGUgLXFxICYmIGFwdC1nZXQgaW5zdGFsbCAteSAtcXEgcGhwLWNsaSBwaHAtanNvbiBwaHAtY3VybCA+IC9kZXYvbnVsbCAyPiYxCiAgICAgICAgICBmaQoKICAgICAgLSBuYW1lOiBJbnN0YWxsIGRlcGVuZGVuY2llcwogICAgICAgIHJ1bjogfAogICAgICAgICAgY2QgL3RtcC9tb2tvY2xpCiAgICAgICAgICBjb21wb3NlciBpbnN0YWxsIC0tbm8tZGV2IC0tbm8taW50ZXJhY3Rpb24gLS1xdWlldCAyPi9kZXYvbnVsbCB8fCB0cnVlCgogICAgICAtIG5hbWU6IFJ1biB3b3JrZmxvdyBzeW5jCiAgICAgICAgZW52OgogICAgICAgICAgTU9LT0dJVEVBX1RPS0VOOiAke3sgc2VjcmV0cy5NT0tPR0lURUFfVE9LRU4gfX0KICAgICAgICBydW46IHwKICAgICAgICAgIEFSR1M9Ii0tdG9rZW4gJHtNT0tPR0lURUFfVE9LRU59IgogICAgICAgICAgQVJHUz0iJHtBUkdTfSAtLW9yZyAke3sgdmFycy5HSVRFQV9PUkcgfHwgZ2l0aHViLnJlcG9zaXRvcnlfb3duZXIgfX0iCiAgICAgICAgICBBUkdTPSIke0FSR1N9IC0tcGhhc2UgcmVwb3MiCgogICAgICAgICAgUExBVEZPUk09IiR7eyBzdGVwcy5wbGF0Zm9ybS5vdXRwdXRzLnBsYXRmb3JtIH19IgogICAgICAgICAgaWYgWyAtbiAiJFBMQVRGT1JNIiBdOyB0aGVuCiAgICAgICAgICAgIEFSR1M9IiR7QVJHU30gLS1wbGF0Zm9ybS1maWx0ZXIgJHtQTEFURk9STX0iCiAgICAgICAgICBmaQoKICAgICAgICAgIHBocCAvdG1wL21va29jbGkvY2xpL3dvcmtmbG93X3N5bmMucGhwICR7QVJHU30K
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
|
||||
# VERSION: 01.01.00
|
||||
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
|
||||
|
||||
name: "Universal: Workflow Sync Trigger"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: Sync workflows to live repos
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.pull_request.merged == true &&
|
||||
!contains(github.event.pull_request.title, '[skip sync]'))
|
||||
|
||||
steps:
|
||||
- name: Determine platform from repo name
|
||||
id: platform
|
||||
run: |
|
||||
REPO="${{ github.event.repository.name }}"
|
||||
case "$REPO" in
|
||||
Template-Joomla) PLATFORM="joomla" ;;
|
||||
Template-Dolibarr) PLATFORM="dolibarr" ;;
|
||||
Template-Go) PLATFORM="go" ;;
|
||||
Template-MCP) PLATFORM="mcp" ;;
|
||||
Template-Generic) PLATFORM="" ;;
|
||||
*) PLATFORM="" ;;
|
||||
esac
|
||||
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
||||
echo "Platform: ${PLATFORM:-all}"
|
||||
|
||||
- name: Clone mokocli
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||
git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
||||
|
||||
- name: Install PHP
|
||||
run: |
|
||||
if ! command -v php &> /dev/null; then
|
||||
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd /tmp/mokocli
|
||||
composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||
|
||||
- name: Run workflow sync
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
ARGS="--token ${MOKOGITEA_TOKEN}"
|
||||
ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}"
|
||||
ARGS="${ARGS} --phase repos"
|
||||
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
if [ -n "$PLATFORM" ]; then
|
||||
ARGS="${ARGS} --platform-filter ${PLATFORM}"
|
||||
fi
|
||||
|
||||
php /tmp/mokocli/cli/workflow_sync.php ${ARGS}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
INGROUP: MokoSuiteTaxi.Documentation
|
||||
BRIEF: Version history using Keep a Changelog
|
||||
-->
|
||||
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **Package Script** -- license key warning on install/update when no DLID is configured
|
||||
- **Repository** -- initial repo creation with scaffolding
|
||||
- **System Plugin** -- Extension class, service provider, 6 helpers
|
||||
- **SQL Schema** -- 8 tables: vehicles, drivers, zones, fares, rides, ratings, dispatch, shifts
|
||||
- **Admin Component** -- 6 views with ListModels: dashboard, rides, vehicles, drivers, zones, fares
|
||||
- **Admin Templates** -- functional Joomla 6 list views with pagination and status badges
|
||||
- **Webservices Plugin** -- 7 API routes: rides, vehicles, drivers, zones, fares, dispatch, ratings
|
||||
- **Configuration** -- 16 settings across general, dispatch, fares, drivers, booking
|
||||
- **Access Control** -- 21 permissions for granular role-based access
|
||||
- **Component Language Files** -- en-GB translations for menu, submenu, and extension manager
|
||||
- **Wiki Documentation** -- 13 pages covering installation, configuration, database, ride management, ride lifecycle, dispatch engine, fare system, fare zones, fleet management, driver management, API reference, and permissions
|
||||
|
||||
### Fixed
|
||||
- **Package Manifest** -- added `<scriptfile>` for package install script
|
||||
- **Package Manifest** -- added component and webservices plugin to package files list (only system plugin was listed)
|
||||
- **FareHelper** -- timezone-aware night surcharge using `Factory::getDate()` instead of `date('H')`
|
||||
- **FareHelper** -- peak multiplier now gated by `peak_hours_start`/`peak_hours_end` instead of always applied
|
||||
- **FareHelper** -- surcharges return value now conditionally includes night surcharge
|
||||
- **FareHelper** -- replaced deprecated `bootPlugin()` with `PluginHelper::getPlugin()` + `Registry`
|
||||
- **ZoneHelper** -- surge supply count now filtered to drivers within zone bounding box instead of global count
|
||||
- **ZoneHelper** -- replaced deprecated `bootPlugin()` with `PluginHelper::getPlugin()` + `Registry`
|
||||
- **ZoneHelper** -- radius fallback uses null coalesce (`??`) instead of falsy coalesce (`?:`)
|
||||
- **RideHelper** -- `requestRide()` now detects pickup/dropoff zones via `ZoneHelper::detectZone()`
|
||||
- **RideHelper** -- surge multiplier stored on ride at request time for use at completion
|
||||
- **RideHelper** -- `completeRide()` now passes ride's pickup zone to `calculateFare()`
|
||||
- **DispatchHelper** -- SQL bounding-box pre-filter before haversine distance calculation
|
||||
- **DispatchHelper** -- `LIMIT` applied after distance filtering instead of before (was silently missing nearby drivers)
|
||||
- **Component Manifest** -- added `access.xml`, `config.xml`, language folder, submenu entries
|
||||
- **Component Provider** -- added `RouterFactoryInterface` registration for SEF URL support
|
||||
- **Webservices Manifest** -- added description, author metadata
|
||||
@@ -1,32 +0,0 @@
|
||||
# MokoSuiteTaxi
|
||||
|
||||
Ride-hailing, dispatch, fleet management, fare zones, and driver scheduling for Joomla 6.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **Package** | `pkg_mokosuitetaxi` |
|
||||
| **Layer** | 2 (requires: Client, CRM) |
|
||||
| **Language** | PHP 8.3+ |
|
||||
| **Branch** | develop on `dev`, merge to `main` (protected) |
|
||||
| **Wiki** | [MokoSuiteTaxi Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteTaxi/wiki) |
|
||||
|
||||
## Architecture
|
||||
|
||||
Joomla **package** -- Layer 2 add-on. CRM contacts as riders/drivers, zone-based dispatch with surge pricing.
|
||||
|
||||
## Rules
|
||||
|
||||
- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js`
|
||||
- **Attribution**: `Authored-by: Moko Consulting`
|
||||
- **Workflow directory**: `.mokogitea/`
|
||||
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoCLI/wiki)
|
||||
- **Changelog**: `[Unreleased]` only -- release system assigns versions
|
||||
|
||||
## Coding Standards
|
||||
|
||||
- PHP 8.3+ / Joomla 6 patterns
|
||||
- `$this->getDatabase()` in models, `Factory::getContainer()->get(DatabaseInterface::class)` in helpers
|
||||
- `Factory::getApplication()->getIdentity()` for user
|
||||
- FOR UPDATE locking on ride acceptance/dispatch to prevent race conditions
|
||||
+46
-1
@@ -1 +1,46 @@
|
||||
PCEtLQlDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgoKCVRoaXMgZmlsZSBpcyBwYXJ0IG9mIGEgTW9rbyBDb25zdWx0aW5nIHByb2plY3QuCgoJU1BEWC1MSUNFTlNFLUlERU5USUZJRVI6IEdQTC0zLjAtb3ItbGF0ZXIKCglUaGlzIHByb2dyYW0gaXMgZnJlZSBzb2Z0d2FyZTsgeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uOyBlaXRoZXIgdmVyc2lvbiAzIG9mIHRoZSBMaWNlbnNlLCBvciAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLgoKCVRoaXMgcHJvZ3JhbSBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLCBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZiBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuIFNlZSB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgZm9yIG1vcmUgZGV0YWlscy4KCglZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSAoLi9MSUNFTlNFKS4KCgkjIEZJTEUgSU5GT1JNQVRJT04KCURFRkdST1VQOiBUZW1wbGF0ZS1Kb29tbGEKCUlOR1JPVVA6IFRlbXBsYXRlLUpvb21sYS5Eb2N1bWVudGF0aW9uCglSRVBPOiBodHRwczovL2dpdGh1Yi5jb20vbW9rb2NvbnN1bHRpbmctdGVjaC9UZW1wbGF0ZS1Kb29tbGEvCiAgVkVSU0lPTjogMDEuMDEuMDAKICBQQVRIOiAuL0NPREVfT0ZfQ09ORFVDVC5tZAogIEJSSUVGOiBDb21tdW5pdHkgZXhwZWN0YXRpb25zIGFuZCBlbmZvcmNlbWVudCBndWlkZWxpbmVzCiAgTk9URTogQWRhcHRlZCB3aXRoIGF0dHJpYnV0aW9uIGZyb20gdGhlIENvbnRyaWJ1dG9yIENvdmVuYW50IHYyLjEKLS0+CgojIENvbnRyaWJ1dG9yIENvdmVuYW50IENvZGUgb2YgQ29uZHVjdAoKIyMgT3VyIFBsZWRnZQpXZSBhcyBtZW1iZXJzLCBjb250cmlidXRvcnMsIGFuZCBsZWFkZXJzIHBsZWRnZSB0byBtYWtlIHBhcnRpY2lwYXRpb24gaW4gb3VyIGNvbW11bml0eSBhIGhhcmFzc21lbnQtZnJlZSBleHBlcmllbmNlIGZvciBldmVyeW9uZS4KCiMjIE91ciBTdGFuZGFyZHMKLSBCZSBlbXBhdGhldGljIGFuZCBraW5kCi0gQmUgcmVzcGVjdGZ1bCBvZiBkaWZmZXJpbmcgb3BpbmlvbnMKLSBBY2NlcHQgY29uc3RydWN0aXZlIGZlZWRiYWNrCi0gT3duIG1pc3Rha2VzIGFuZCBsZWFybiBmcm9tIHRoZW0KClVuYWNjZXB0YWJsZSBiZWhhdmlvciBpbmNsdWRlcyBzZXh1YWxpemVkIGxhbmd1YWdlL2ltYWdlcnksIHRyb2xsaW5nLCBoYXJhc3NtZW50LCBkb3hpbmcsIGFuZCBvdGhlciBpbmFwcHJvcHJpYXRlIGNvbmR1Y3QuCgojIyBFbmZvcmNlbWVudApSZXBvcnQgaW5jaWRlbnRzIHRvICoqaGVsbG9AbW9rb2NvbnN1bHRpbmcudGVjaCoqIG9yIHRocm91Z2ggR2l0SHViIERpc2N1c3Npb25zIGlmIHlvdSBwcmVmZXIgYSBjb21tdW5pdHktdmlzaWJsZSBhcHByb2FjaC4gUHJpdmF0ZSBjb21wbGFpbnRzIHdpbGwgYmUgcmV2aWV3ZWQgcHJvbXB0bHkgYW5kIGZhaXJseS4KCiMjIEVuZm9yY2VtZW50IEd1aWRlbGluZXMKMS4gKipDb3JyZWN0aW9uKiog4oCUIFByaXZhdGUgd2FybmluZyAgCjIuICoqV2FybmluZyoqIOKAlCBGb3JtYWwgd2FybmluZyBhbmQgbGltaXRlZCBpbnRlcmFjdGlvbiAgCjMuICoqVGVtcG9yYXJ5IEJhbioqIOKAlCBUaW1lLWJveGVkIGV4Y2x1c2lvbiAgCjQuICoqUGVybWFuZW50IEJhbioqIOKAlCBSZW1vdmFsIGZyb20gdGhlIGNvbW11bml0eQoKIyMgQXR0cmlidXRpb24KQWRhcHRlZCBmcm9tIHRoZSBDb250cmlidXRvciBDb3ZlbmFudCB2Mi4xLgo=
|
||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License (./LICENSE).
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Template-Joomla
|
||||
INGROUP: Template-Joomla.Documentation
|
||||
REPO: https://github.com/mokoconsulting-tech/Template-Joomla/
|
||||
VERSION: 01.01.00
|
||||
PATH: ./CODE_OF_CONDUCT.md
|
||||
BRIEF: Community expectations and enforcement guidelines
|
||||
NOTE: Adapted with attribution from the Contributor Covenant v2.1
|
||||
-->
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone.
|
||||
|
||||
## Our Standards
|
||||
- Be empathetic and kind
|
||||
- Be respectful of differing opinions
|
||||
- Accept constructive feedback
|
||||
- Own mistakes and learn from them
|
||||
|
||||
Unacceptable behavior includes sexualized language/imagery, trolling, harassment, doxing, and other inappropriate conduct.
|
||||
|
||||
## Enforcement
|
||||
Report incidents to **hello@mokoconsulting.tech** or through GitHub Discussions if you prefer a community-visible approach. Private complaints will be reviewed promptly and fairly.
|
||||
|
||||
## Enforcement Guidelines
|
||||
1. **Correction** — Private warning
|
||||
2. **Warning** — Formal warning and limited interaction
|
||||
3. **Temporary Ban** — Time-boxed exclusion
|
||||
4. **Permanent Ban** — Removal from the community
|
||||
|
||||
## Attribution
|
||||
Adapted from the Contributor Covenant v2.1.
|
||||
|
||||
+161
-1
File diff suppressed because one or more lines are too long
+119
-1
File diff suppressed because one or more lines are too long
@@ -1,44 +1,3 @@
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
# MokoSuiteTaxi
|
||||
|
||||
# MokoSuite Taxi
|
||||
|
||||
Ride-hailing, dispatch, fleet management, fare zones, and driver scheduling module for MokoSuite on Joomla 6.
|
||||
|
||||
## Overview
|
||||
|
||||
MokoSuiteTaxi is a Layer 2 module in the MokoSuite platform, building on MokoSuiteClient (Layer 0) and MokoSuiteCRM (Layer 1) to provide complete taxi and ride-hailing operations management.
|
||||
|
||||
## Features
|
||||
|
||||
- **Ride Management** -- on-demand, scheduled, airport, and corporate ride types
|
||||
- **Dispatch Engine** -- auto-dispatch with zone-aware driver matching and FOR UPDATE locking
|
||||
- **Fleet Management** -- vehicle tracking, insurance/inspection expiry alerts, maintenance status
|
||||
- **Driver Management** -- profiles linked to CRM contacts, approval workflow, background checks
|
||||
- **Fare Zones** -- GeoJSON polygon boundaries with radius fallback, zone-based pricing
|
||||
- **Dynamic Pricing** -- surge multiplier based on demand/supply ratio, night surcharges, peak hours
|
||||
- **Ratings** -- bidirectional rider/driver ratings with feedback tags
|
||||
- **Shift Scheduling** -- driver shift management with earnings tracking
|
||||
|
||||
## Requirements
|
||||
|
||||
- Joomla 6.x
|
||||
- PHP 8.3+
|
||||
- MokoSuiteClient (Layer 0)
|
||||
- MokoSuiteCRM (Layer 1)
|
||||
|
||||
## Installation
|
||||
|
||||
Install via Joomla Extension Manager using the package file `pkg_mokosuitetaxi.zip`.
|
||||
|
||||
## License
|
||||
|
||||
GNU General Public License v3.0 or later -- see [LICENSE](LICENSE).
|
||||
|
||||
## Links
|
||||
|
||||
- [Documentation](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteTaxi/wiki)
|
||||
- [Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteTaxi/issues)
|
||||
- [MokoSuite Platform](https://mokoconsulting.tech)
|
||||
MokoSuite Taxi — ride-hailing, dispatch, fleet management, fare zones, driver scheduling for Joomla 6
|
||||
+241
-1
File diff suppressed because one or more lines are too long
+27
-1
@@ -1 +1,27 @@
|
||||
ewogICAgIm5hbWUiOiAibW9rb0NvbnN1bHRpbmcvbW9rb3N1aXRldGF4aSIsCiAgICAiZGVzY3JpcHRpb24iOiAiUmlkZS1oYWlsaW5nLCBkaXNwYXRjaCwgZmxlZXQgbWFuYWdlbWVudCwgZmFyZSB6b25lcywgYW5kIGRyaXZlciBzY2hlZHVsaW5nIGZvciBKb29tbGEgNiIsCiAgICAidHlwZSI6ICJqb29tbGEtcGFja2FnZSIsCiAgICAidmVyc2lvbiI6ICIwNi4wMC4wMCIsCiAgICAibGljZW5zZSI6ICJHUEwtMy4wLW9yLWxhdGVyIiwKICAgICJhdXRob3JzIjogWwogICAgICAgIHsKICAgICAgICAgICAgIm5hbWUiOiAiTW9rbyBDb25zdWx0aW5nIiwKICAgICAgICAgICAgImVtYWlsIjogImhlbGxvQG1va29jb25zdWx0aW5nLnRlY2giLAogICAgICAgICAgICAiaG9tZXBhZ2UiOiAiaHR0cHM6Ly9tb2tvY29uc3VsdGluZy50ZWNoIgogICAgICAgIH0KICAgIF0sCiAgICAicmVxdWlyZSI6IHsKICAgICAgICAicGhwIjogIj49OC4zIgogICAgfSwKICAgICJyZXF1aXJlLWRldiI6IHsKICAgICAgICAic3F1aXpsYWJzL3BocF9jb2Rlc25pZmZlciI6ICJeMy43IiwKICAgICAgICAicGhwc3Rhbi9waHBzdGFuIjogIl4xLjEwIiwKICAgICAgICAiam9vbWxhL2NvZGluZy1zdGFuZGFyZHMiOiAiMy4wLngtZGV2IgogICAgfSwKICAgICJtaW5pbXVtLXN0YWJpbGl0eSI6ICJkZXYiLAogICAgInByZWZlci1zdGFibGUiOiB0cnVlLAogICAgImNvbmZpZyI6IHsKICAgICAgICAic29ydC1wYWNrYWdlcyI6IHRydWUKICAgIH0KfQ==
|
||||
{
|
||||
"name": "mokoconsulting/mokojoomgallery",
|
||||
"description": "Photo gallery management for Joomla — galleries, images, thumbnails, lightbox, and frontend display",
|
||||
"type": "joomla-package",
|
||||
"version": "01.00.00",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Moko Consulting",
|
||||
"email": "hello@mokoconsulting.tech",
|
||||
"homepage": "https://mokoconsulting.tech"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"joomla/coding-standards": "3.0.x-dev"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
||||
|
||||
+32
-1
@@ -1 +1,32 @@
|
||||
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBHUEwtMy4wLW9yLWxhdGVyCiMKIyBQSFBTdGFuIGNvbmZpZ3VyYXRpb24gZm9yIEpvb21sYSBleHRlbnNpb24gcmVwb3NpdG9yaWVzLgojIEV4dGVuZHMgdGhlIGJhc2UgTW9rb1N0YW5kYXJkcyBjb25maWcgYW5kIGFkZHMgSm9vbWxhIGZyYW1ld29yayBjbGFzcyBzdHVicwojIHNvIFBIUFN0YW4gY2FuIHJlc29sdmUgRmFjdG9yeSwgQ01TQXBwbGljYXRpb24sIFVzZXIsIFRhYmxlLCBldGMuCiMgd2l0aG91dCByZXF1aXJpbmcgYSBmdWxsIEpvb21sYSBpbnN0YWxsYXRpb24uCgpwYXJhbWV0ZXJzOgogICAgbGV2ZWw6IDUKCiAgICBwYXRoczoKICAgICAgICAtIHNyYwoKICAgIGV4Y2x1ZGVQYXRoczoKICAgICAgICAtIHZlbmRvcgogICAgICAgIC0gbm9kZV9tb2R1bGVzCgogICAgIyBKb29tbGEgZnJhbWV3b3JrIHN0dWJzIOKAlCByZXNvbHZlZCB2aWEgdGhlIGVudGVycHJpc2UgcGFja2FnZSBmcm9tIHZlbmRvci8KICAgIHN0dWJGaWxlczoKICAgICAgICAtIHZlbmRvci9tb2tvY29uc3VsdGluZy10ZWNoL2VudGVycHJpc2UvdGVtcGxhdGVzL3N0dWJzL2pvb21sYS5waHAKCiAgICAjIFN1cHByZXNzIGVycm9ycyB0aGF0IGFyZSBzdHJ1Y3R1cmFsIGluIEpvb21sYSdzIHNlcnZpY2UtY29udGFpbmVyIGFyY2hpdGVjdHVyZQogICAgaWdub3JlRXJyb3JzOgogICAgICAgICMgSm9vbWxhJ3Mgc2VydmljZS1iYXNlZCBkZXBlbmRlbmN5IGluamVjdGlvbiByZXR1cm5zIG1peGVkIGZyb20gZ2V0QXBwbGljYXRpb24oKQogICAgICAgIC0gJyNDYW5ub3QgY2FsbCBtZXRob2QgLisgb24gSm9vbWxhXFxDTVNcXEFwcGxpY2F0aW9uXFxDTVNBcHBsaWNhdGlvblx8bnVsbCMnCiAgICAgICAgIyBGYWN0b3J5OjpnZXRYKCkgcGF0dGVybnMgYXJlIHNhZmUgYXQgcnVudGltZSBldmVuIHdoZW4gbnVsbGFibGUgaW4gc3R1YnMKICAgICAgICAtICcjQ2FsbCB0byBzdGF0aWMgbWV0aG9kIFthLXpBLVpdK1woXCkgb24gYW4gaW50ZXJmYWNlIycKCiAgICByZXBvcnRVbm1hdGNoZWRJZ25vcmVkRXJyb3JzOiBmYWxzZQogICAgY2hlY2tNaXNzaW5nSXRlcmFibGVWYWx1ZVR5cGU6IGZhbHNlCiAgICBjaGVja0dlbmVyaWNDbGFzc0luTm9uR2VuZXJpY09iamVjdFR5cGU6IGZhbHNlCg==
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# PHPStan configuration for Joomla extension repositories.
|
||||
# Extends the base MokoStandards config and adds Joomla framework class stubs
|
||||
# so PHPStan can resolve Factory, CMSApplication, User, Table, etc.
|
||||
# without requiring a full Joomla installation.
|
||||
|
||||
parameters:
|
||||
level: 5
|
||||
|
||||
paths:
|
||||
- src
|
||||
|
||||
excludePaths:
|
||||
- vendor
|
||||
- node_modules
|
||||
|
||||
# Joomla framework stubs — resolved via the enterprise package from vendor/
|
||||
stubFiles:
|
||||
- vendor/mokoconsulting-tech/enterprise/templates/stubs/joomla.php
|
||||
|
||||
# Suppress errors that are structural in Joomla's service-container architecture
|
||||
ignoreErrors:
|
||||
# Joomla's service-based dependency injection returns mixed from getApplication()
|
||||
- '#Cannot call method .+ on Joomla\\CMS\\Application\\CMSApplication\|null#'
|
||||
# Factory::getX() patterns are safe at runtime even when nullable in stubs
|
||||
- '#Call to static method [a-zA-Z]+\(\) on an interface#'
|
||||
|
||||
reportUnmatchedIgnoredErrors: false
|
||||
checkMissingIterableValueType: false
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<access component="com_mokosuitetaxi">
|
||||
<section name="component">
|
||||
<action name="core.admin" title="JACTION_ADMIN" />
|
||||
<action name="core.options" title="JACTION_OPTIONS" />
|
||||
<action name="core.manage" title="JACTION_MANAGE" />
|
||||
<action name="core.create" title="JACTION_CREATE" />
|
||||
<action name="core.delete" title="JACTION_DELETE" />
|
||||
<action name="core.edit" title="JACTION_EDIT" />
|
||||
<action name="taxi.rides.manage" title="Manage Rides" />
|
||||
<action name="taxi.rides.dispatch" title="Dispatch Rides" />
|
||||
<action name="taxi.rides.cancel" title="Cancel Rides" />
|
||||
<action name="taxi.rides.complete" title="Complete Rides" />
|
||||
<action name="taxi.drivers.manage" title="Manage Drivers" />
|
||||
<action name="taxi.drivers.approve" title="Approve Drivers" />
|
||||
<action name="taxi.drivers.suspend" title="Suspend Drivers" />
|
||||
<action name="taxi.vehicles.manage" title="Manage Vehicles" />
|
||||
<action name="taxi.zones.manage" title="Manage Zones" />
|
||||
<action name="taxi.fares.manage" title="Manage Fare Rules" />
|
||||
<action name="taxi.dispatch.view" title="View Dispatch Queue" />
|
||||
<action name="taxi.ratings.view" title="View Ratings" />
|
||||
<action name="taxi.shifts.manage" title="Manage Shifts" />
|
||||
<action name="taxi.reports" title="View Reports" />
|
||||
<action name="taxi.settings" title="Manage Settings" />
|
||||
</section>
|
||||
</access>
|
||||
@@ -1,51 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<config>
|
||||
<fieldset name="general" label="General">
|
||||
<field name="default_currency" type="text" default="USD" label="Default Currency" />
|
||||
<field name="distance_unit" type="list" default="mi" label="Distance Unit">
|
||||
<option value="mi">Miles</option>
|
||||
<option value="km">Kilometres</option>
|
||||
</field>
|
||||
<field name="timezone" type="text" default="UTC" label="Operating Timezone" />
|
||||
</fieldset>
|
||||
<fieldset name="dispatch" label="Dispatch">
|
||||
<field name="auto_dispatch" type="radio" default="1" label="Auto-Dispatch Rides" class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field name="driver_search_radius" type="number" default="10" label="Driver Search Radius" hint="In distance units" />
|
||||
<field name="ride_timeout_minutes" type="number" default="5" label="Ride Accept Timeout (min)" />
|
||||
<field name="max_dispatch_attempts" type="number" default="5" label="Max Dispatch Attempts" />
|
||||
<field name="max_active_rides" type="number" default="1" label="Max Concurrent Rides per Driver" />
|
||||
</fieldset>
|
||||
<fieldset name="fares" label="Fares">
|
||||
<field name="base_fare" type="number" default="2.50" step="0.01" label="Default Base Fare" />
|
||||
<field name="per_distance" type="number" default="1.50" step="0.01" label="Per Distance Unit Rate" />
|
||||
<field name="per_minute" type="number" default="0.25" step="0.01" label="Per Minute Rate" />
|
||||
<field name="surge_enabled" type="radio" default="1" label="Enable Surge Pricing" class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field name="max_surge_multiplier" type="number" default="3.0" step="0.1" label="Max Surge Multiplier" />
|
||||
<field name="cancellation_fee" type="number" default="5.00" step="0.01" label="Cancellation Fee" />
|
||||
</fieldset>
|
||||
<fieldset name="drivers" label="Drivers">
|
||||
<field name="min_driver_rating" type="number" default="4.0" step="0.1" label="Minimum Driver Rating" />
|
||||
<field name="commission_rate" type="number" default="20" step="1" label="Platform Commission (%)" />
|
||||
<field name="require_background_check" type="radio" default="1" label="Require Background Check" class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
<fieldset name="booking" label="Booking">
|
||||
<field name="enable_scheduled_rides" type="radio" default="1" label="Allow Scheduled Rides" class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field name="advance_booking_days" type="number" default="7" label="Max Advance Booking (days)" />
|
||||
<field name="enable_ride_sharing" type="radio" default="0" label="Enable Ride Sharing" class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</config>
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\ComponentInterface;
|
||||
use Joomla\CMS\Extension\MVCComponent;
|
||||
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
|
||||
return new class implements ServiceProviderInterface {
|
||||
public function register(Container $container): void {
|
||||
$container->set(ComponentInterface::class, function (Container $container) {
|
||||
$c = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
|
||||
$c->setMVCFactory($container->get(MVCFactoryInterface::class));
|
||||
return $c;
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteTaxi\Administrator\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
|
||||
class DisplayController extends BaseController
|
||||
{
|
||||
protected $default_view = 'taxidashboard';
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteTaxi\Administrator\Model;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
||||
|
||||
class TaxiDashboardModel extends BaseDatabaseModel
|
||||
{
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteTaxi\Administrator\View\TaxiDashboard;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
ToolbarHelper::title('MokoSuite Taxi - Dashboard');
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteTaxi\Administrator\View\TaxiDrivers;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
ToolbarHelper::title('MokoSuite Taxi - Drivers');
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteTaxi\Administrator\View\TaxiFares;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
ToolbarHelper::title('MokoSuite Taxi - Fare Rules');
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteTaxi\Administrator\View\TaxiRides;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
ToolbarHelper::title('MokoSuite Taxi - Rides');
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteTaxi\Administrator\View\TaxiVehicles;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
ToolbarHelper::title('MokoSuite Taxi - Vehicles');
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteTaxi\Administrator\View\TaxiZones;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
ToolbarHelper::title('MokoSuite Taxi - Zones');
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?php defined('_JEXEC') or die; ?><div><h2>Dashboard</h2><p>Coming soon.</p></div>
|
||||
@@ -1 +0,0 @@
|
||||
<?php defined('_JEXEC') or die; ?><div><h2>Drivers</h2><p>Coming soon.</p></div>
|
||||
@@ -1 +0,0 @@
|
||||
<?php defined('_JEXEC') or die; ?><div><h2>Fares</h2><p>Coming soon.</p></div>
|
||||
@@ -1 +0,0 @@
|
||||
<?php defined('_JEXEC') or die; ?><div><h2>Rides</h2><p>Coming soon.</p></div>
|
||||
@@ -1 +0,0 @@
|
||||
<?php defined('_JEXEC') or die; ?><div><h2>Vehicles</h2><p>Coming soon.</p></div>
|
||||
@@ -1 +0,0 @@
|
||||
<?php defined('_JEXEC') or die; ?><div><h2>Zones</h2><p>Coming soon.</p></div>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="component" method="upgrade">
|
||||
<name>MokoSuite Taxi</name>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-06-27</creationDate>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<version>06.00.00</version>
|
||||
<namespace path="src">Moko\Component\MokoSuiteTaxi</namespace>
|
||||
<administration>
|
||||
<files folder="admin"><folder>services</folder><folder>src</folder><folder>tmpl</folder></files>
|
||||
<menu>COM_MOKOSUITETAXI</menu>
|
||||
</administration>
|
||||
</extension>
|
||||
@@ -1,2 +0,0 @@
|
||||
PLG_SYSTEM_MOKOSUITETAXI="System - MokoSuite Taxi"
|
||||
PLG_SYSTEM_MOKOSUITETAXI_DESC="Ride-hailing, dispatch, fleet management, fare zones, and driver scheduling"
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
PLG_SYSTEM_MOKOSUITETAXI="System - MokoSuite Taxi"
|
||||
PLG_SYSTEM_MOKOSUITETAXI_DESC="Ride-hailing, dispatch, fleet management, fare zones, and driver scheduling"
|
||||
@@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="plugin" group="system" method="upgrade">
|
||||
<name>System - MokoSuite Taxi</name>
|
||||
<element>mokosuitetaxi</element>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-06-27</creationDate>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>06.00.00</version>
|
||||
<php_minimum>8.3</php_minimum>
|
||||
<description>PLG_SYSTEM_MOKOSUITETAXI_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\System\MokoSuiteTaxi</namespace>
|
||||
<files>
|
||||
<folder>src</folder>
|
||||
<folder>services</folder>
|
||||
<folder>language</folder>
|
||||
<folder>sql</folder>
|
||||
</files>
|
||||
<languages folder="language">
|
||||
<language tag="en-GB">en-GB/plg_system_mokosuitetaxi.ini</language>
|
||||
<language tag="en-GB">en-GB/plg_system_mokosuitetaxi.sys.ini</language>
|
||||
</languages>
|
||||
<install><sql><file driver="mysql" charset="utf8">sql/install.mysql.sql</file></sql></install>
|
||||
<uninstall><sql><file driver="mysql" charset="utf8">sql/uninstall.mysql.sql</file></sql></uninstall>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic" label="Taxi Defaults">
|
||||
<field name="default_currency" type="text" default="USD" label="Default Currency" />
|
||||
<field name="distance_unit" type="list" default="mi" label="Distance Unit">
|
||||
<option value="mi">Miles</option>
|
||||
<option value="km">Kilometres</option>
|
||||
</field>
|
||||
<field name="auto_dispatch" type="radio" default="1" label="Auto-Dispatch Rides" class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field name="driver_search_radius" type="number" default="10" label="Driver Search Radius" hint="In distance units" />
|
||||
</fieldset>
|
||||
<fieldset name="fares" label="Fare Settings">
|
||||
<field name="base_fare" type="number" default="2.50" step="0.01" label="Default Base Fare" />
|
||||
<field name="per_distance" type="number" default="1.50" step="0.01" label="Per Distance Unit Rate" />
|
||||
<field name="per_minute" type="number" default="0.25" step="0.01" label="Per Minute Rate" />
|
||||
<field name="surge_enabled" type="radio" default="1" label="Enable Surge Pricing" class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field name="max_surge_multiplier" type="number" default="3.0" step="0.1" label="Max Surge Multiplier" />
|
||||
<field name="cancellation_fee" type="number" default="5.00" step="0.01" label="Cancellation Fee" />
|
||||
</fieldset>
|
||||
<fieldset name="drivers" label="Driver Settings">
|
||||
<field name="min_driver_rating" type="number" default="4.0" step="0.1" label="Minimum Driver Rating" />
|
||||
<field name="commission_rate" type="number" default="20" step="1" label="Platform Commission (%)" />
|
||||
<field name="ride_timeout_minutes" type="number" default="5" label="Ride Accept Timeout (min)" />
|
||||
<field name="max_active_rides" type="number" default="1" label="Max Concurrent Rides per Driver" />
|
||||
</fieldset>
|
||||
<fieldset name="booking" label="Booking">
|
||||
<field name="advance_booking_days" type="number" default="7" label="Max Advance Booking (days)" />
|
||||
<field name="enable_scheduled_rides" type="radio" default="1" label="Allow Scheduled Rides" class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Moko\Plugin\System\MokoSuiteTaxi\Extension\Taxi;
|
||||
|
||||
return new class implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new Taxi($dispatcher, (array) PluginHelper::getPlugin('system', 'mokosuitetaxi'));
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,216 +0,0 @@
|
||||
--
|
||||
-- MokoSuite Taxi Tables
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuitetaxi_vehicles` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`plate_number` VARCHAR(20) NOT NULL,
|
||||
`vin` VARCHAR(17) NOT NULL DEFAULT '',
|
||||
`make` VARCHAR(100) NOT NULL,
|
||||
`model` VARCHAR(100) NOT NULL,
|
||||
`year` SMALLINT UNSIGNED NOT NULL,
|
||||
`color` VARCHAR(50) NOT NULL DEFAULT '',
|
||||
`vehicle_type` ENUM('sedan','suv','van','luxury','minibus','motorcycle') NOT NULL DEFAULT 'sedan',
|
||||
`capacity` TINYINT UNSIGNED NOT NULL DEFAULT 4,
|
||||
`fuel_type` ENUM('gasoline','diesel','electric','hybrid') NOT NULL DEFAULT 'gasoline',
|
||||
`status` ENUM('available','in_service','maintenance','retired') NOT NULL DEFAULT 'available',
|
||||
`insurance_expiry` DATE DEFAULT NULL,
|
||||
`inspection_expiry` DATE DEFAULT NULL,
|
||||
`odometer_km` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`photo` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`notes` TEXT,
|
||||
`published` TINYINT NOT NULL DEFAULT 1,
|
||||
`created` DATETIME NOT NULL,
|
||||
`modified` DATETIME DEFAULT NULL,
|
||||
`created_by` INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_plate` (`plate_number`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_type` (`vehicle_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuitetaxi_drivers` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`contact_id` INT DEFAULT NULL COMMENT 'FK to #__contact_details',
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`phone` VARCHAR(50) NOT NULL DEFAULT '',
|
||||
`email` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
`license_number` VARCHAR(50) NOT NULL,
|
||||
`license_expiry` DATE DEFAULT NULL,
|
||||
`license_class` VARCHAR(10) NOT NULL DEFAULT '',
|
||||
`vehicle_id` INT UNSIGNED DEFAULT NULL,
|
||||
`status` ENUM('active','inactive','suspended','pending_approval') NOT NULL DEFAULT 'pending_approval',
|
||||
`rating` DECIMAL(3,2) NOT NULL DEFAULT 5.00,
|
||||
`total_rides` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`total_earnings` DECIMAL(12,2) NOT NULL DEFAULT 0.00,
|
||||
`commission_rate` DECIMAL(5,2) DEFAULT NULL COMMENT 'Override platform default',
|
||||
`photo` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`background_check_date` DATE DEFAULT NULL,
|
||||
`published` TINYINT NOT NULL DEFAULT 1,
|
||||
`created` DATETIME NOT NULL,
|
||||
`modified` DATETIME DEFAULT NULL,
|
||||
`created_by` INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_license` (`license_number`),
|
||||
KEY `idx_contact` (`contact_id`),
|
||||
KEY `idx_vehicle` (`vehicle_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_rating` (`rating`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuitetaxi_zones` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`description` TEXT,
|
||||
`zone_type` ENUM('pickup','dropoff','service','surge','restricted') NOT NULL DEFAULT 'service',
|
||||
`boundary_geojson` JSON COMMENT 'GeoJSON polygon defining zone boundary',
|
||||
`center_lat` DECIMAL(10,7) DEFAULT NULL,
|
||||
`center_lng` DECIMAL(10,7) DEFAULT NULL,
|
||||
`radius_km` DECIMAL(8,2) DEFAULT NULL COMMENT 'Circular zone fallback if no polygon',
|
||||
`fare_multiplier` DECIMAL(4,2) NOT NULL DEFAULT 1.00,
|
||||
`active_hours_start` TIME DEFAULT NULL,
|
||||
`active_hours_end` TIME DEFAULT NULL,
|
||||
`published` TINYINT NOT NULL DEFAULT 1,
|
||||
`ordering` INT NOT NULL DEFAULT 0,
|
||||
`created` DATETIME NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_type` (`zone_type`),
|
||||
KEY `idx_center` (`center_lat`, `center_lng`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuitetaxi_fares` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`title` VARCHAR(255) NOT NULL,
|
||||
`vehicle_type` ENUM('sedan','suv','van','luxury','minibus','motorcycle','all') NOT NULL DEFAULT 'all',
|
||||
`zone_id` INT UNSIGNED DEFAULT NULL COMMENT 'Zone-specific pricing, NULL = default',
|
||||
`base_fare` DECIMAL(10,2) NOT NULL DEFAULT 2.50,
|
||||
`per_km` DECIMAL(10,2) NOT NULL DEFAULT 1.50,
|
||||
`per_minute` DECIMAL(10,2) NOT NULL DEFAULT 0.25,
|
||||
`minimum_fare` DECIMAL(10,2) NOT NULL DEFAULT 5.00,
|
||||
`booking_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`peak_multiplier` DECIMAL(4,2) NOT NULL DEFAULT 1.00,
|
||||
`peak_hours_start` TIME DEFAULT NULL,
|
||||
`peak_hours_end` TIME DEFAULT NULL,
|
||||
`night_surcharge` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`airport_surcharge` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`effective_from` DATE DEFAULT NULL,
|
||||
`effective_to` DATE DEFAULT NULL,
|
||||
`published` TINYINT NOT NULL DEFAULT 1,
|
||||
`created` DATETIME NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_vehicle_type` (`vehicle_type`),
|
||||
KEY `idx_zone` (`zone_id`),
|
||||
KEY `idx_effective` (`effective_from`, `effective_to`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuitetaxi_rides` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`ride_ref` VARCHAR(20) NOT NULL,
|
||||
`rider_contact_id` INT DEFAULT NULL COMMENT 'FK to #__contact_details',
|
||||
`rider_name` VARCHAR(255) NOT NULL,
|
||||
`rider_phone` VARCHAR(50) NOT NULL DEFAULT '',
|
||||
`driver_id` INT UNSIGNED DEFAULT NULL,
|
||||
`vehicle_id` INT UNSIGNED DEFAULT NULL,
|
||||
`status` ENUM('requested','dispatched','accepted','arriving','in_progress','completed','cancelled','no_driver') NOT NULL DEFAULT 'requested',
|
||||
`ride_type` ENUM('on_demand','scheduled','airport','corporate') NOT NULL DEFAULT 'on_demand',
|
||||
`vehicle_type_requested` ENUM('sedan','suv','van','luxury','minibus','motorcycle','any') NOT NULL DEFAULT 'any',
|
||||
`pickup_address` VARCHAR(500) NOT NULL,
|
||||
`pickup_lat` DECIMAL(10,7) DEFAULT NULL,
|
||||
`pickup_lng` DECIMAL(10,7) DEFAULT NULL,
|
||||
`dropoff_address` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`dropoff_lat` DECIMAL(10,7) DEFAULT NULL,
|
||||
`dropoff_lng` DECIMAL(10,7) DEFAULT NULL,
|
||||
`pickup_zone_id` INT UNSIGNED DEFAULT NULL,
|
||||
`dropoff_zone_id` INT UNSIGNED DEFAULT NULL,
|
||||
`scheduled_at` DATETIME DEFAULT NULL,
|
||||
`dispatched_at` DATETIME DEFAULT NULL,
|
||||
`accepted_at` DATETIME DEFAULT NULL,
|
||||
`arrived_at` DATETIME DEFAULT NULL,
|
||||
`started_at` DATETIME DEFAULT NULL,
|
||||
`completed_at` DATETIME DEFAULT NULL,
|
||||
`cancelled_at` DATETIME DEFAULT NULL,
|
||||
`cancelled_by` ENUM('rider','driver','system') DEFAULT NULL,
|
||||
`cancel_reason` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`distance_km` DECIMAL(10,2) DEFAULT NULL,
|
||||
`duration_minutes` DECIMAL(10,2) DEFAULT NULL,
|
||||
`fare_id` INT UNSIGNED DEFAULT NULL,
|
||||
`base_fare` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`distance_charge` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`time_charge` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`surge_multiplier` DECIMAL(4,2) NOT NULL DEFAULT 1.00,
|
||||
`surcharges` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`discount` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`total_fare` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`driver_payout` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`platform_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`payment_method` ENUM('cash','card','wallet','corporate') NOT NULL DEFAULT 'cash',
|
||||
`payment_status` ENUM('pending','paid','refunded') NOT NULL DEFAULT 'pending',
|
||||
`passenger_count` TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
`notes` TEXT,
|
||||
`route_polyline` TEXT COMMENT 'Encoded polyline of actual route',
|
||||
`created` DATETIME NOT NULL,
|
||||
`created_by` INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_ref` (`ride_ref`),
|
||||
KEY `idx_rider` (`rider_contact_id`),
|
||||
KEY `idx_driver` (`driver_id`),
|
||||
KEY `idx_vehicle` (`vehicle_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_type` (`ride_type`),
|
||||
KEY `idx_created` (`created`),
|
||||
KEY `idx_scheduled` (`scheduled_at`),
|
||||
KEY `idx_pickup_zone` (`pickup_zone_id`),
|
||||
KEY `idx_dropoff_zone` (`dropoff_zone_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuitetaxi_ratings` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`ride_id` INT UNSIGNED NOT NULL,
|
||||
`rated_by` ENUM('rider','driver') NOT NULL,
|
||||
`rating` TINYINT UNSIGNED NOT NULL COMMENT '1-5 stars',
|
||||
`comment` TEXT,
|
||||
`tags` VARCHAR(500) NOT NULL DEFAULT '' COMMENT 'Comma-separated feedback tags',
|
||||
`created` DATETIME NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_ride_rater` (`ride_id`, `rated_by`),
|
||||
KEY `idx_rating` (`rating`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuitetaxi_dispatch` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`ride_id` INT UNSIGNED NOT NULL,
|
||||
`driver_id` INT UNSIGNED NOT NULL,
|
||||
`status` ENUM('offered','accepted','rejected','expired') NOT NULL DEFAULT 'offered',
|
||||
`offered_at` DATETIME NOT NULL,
|
||||
`responded_at` DATETIME DEFAULT NULL,
|
||||
`distance_to_pickup_km` DECIMAL(10,2) DEFAULT NULL,
|
||||
`eta_minutes` DECIMAL(10,2) DEFAULT NULL,
|
||||
`attempt_number` TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_ride` (`ride_id`),
|
||||
KEY `idx_driver` (`driver_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_offered` (`offered_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuitetaxi_shifts` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`driver_id` INT UNSIGNED NOT NULL,
|
||||
`vehicle_id` INT UNSIGNED DEFAULT NULL,
|
||||
`start_time` DATETIME NOT NULL,
|
||||
`end_time` DATETIME DEFAULT NULL,
|
||||
`status` ENUM('scheduled','active','completed','cancelled') NOT NULL DEFAULT 'scheduled',
|
||||
`start_lat` DECIMAL(10,7) DEFAULT NULL,
|
||||
`start_lng` DECIMAL(10,7) DEFAULT NULL,
|
||||
`end_lat` DECIMAL(10,7) DEFAULT NULL,
|
||||
`end_lng` DECIMAL(10,7) DEFAULT NULL,
|
||||
`total_rides` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`total_distance_km` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
`total_earnings` DECIMAL(12,2) NOT NULL DEFAULT 0.00,
|
||||
`created` DATETIME NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_driver` (`driver_id`),
|
||||
KEY `idx_vehicle` (`vehicle_id`),
|
||||
KEY `idx_times` (`start_time`, `end_time`),
|
||||
KEY `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@@ -1,12 +0,0 @@
|
||||
--
|
||||
-- MokoSuite Taxi — Uninstall
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `#__mokosuitetaxi_shifts`;
|
||||
DROP TABLE IF EXISTS `#__mokosuitetaxi_dispatch`;
|
||||
DROP TABLE IF EXISTS `#__mokosuitetaxi_ratings`;
|
||||
DROP TABLE IF EXISTS `#__mokosuitetaxi_rides`;
|
||||
DROP TABLE IF EXISTS `#__mokosuitetaxi_fares`;
|
||||
DROP TABLE IF EXISTS `#__mokosuitetaxi_zones`;
|
||||
DROP TABLE IF EXISTS `#__mokosuitetaxi_drivers`;
|
||||
DROP TABLE IF EXISTS `#__mokosuitetaxi_vehicles`;
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\System\MokoSuiteTaxi\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
class Taxi extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
protected $autoloadLanguage = true;
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Moko\Plugin\System\MokoSuiteTaxi\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
class DispatchHelper
|
||||
{
|
||||
public static function findNearbyDrivers(
|
||||
float $lat,
|
||||
float $lng,
|
||||
float $radiusKm = 10.0,
|
||||
string $vehicleType = 'any',
|
||||
int $limit = 10
|
||||
): array {
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select([
|
||||
'd.id', 'd.name', 'd.rating', 'd.vehicle_id',
|
||||
'v.plate_number', 'v.vehicle_type', 'v.capacity',
|
||||
's.start_lat AS lat', 's.start_lng AS lng',
|
||||
])
|
||||
->from($db->quoteName('#__mokosuitetaxi_drivers', 'd'))
|
||||
->join('INNER', $db->quoteName('#__mokosuitetaxi_shifts', 's')
|
||||
. ' ON s.driver_id = d.id AND s.status = ' . $db->quote('active'))
|
||||
->join('LEFT', $db->quoteName('#__mokosuitetaxi_vehicles', 'v')
|
||||
. ' ON v.id = d.vehicle_id')
|
||||
->where($db->quoteName('d.status') . ' = ' . $db->quote('active'))
|
||||
->where($db->quoteName('d.published') . ' = 1')
|
||||
->where('s.start_lat IS NOT NULL')
|
||||
->where('s.start_lng IS NOT NULL')
|
||||
->order('d.rating DESC')
|
||||
->setLimit($limit);
|
||||
|
||||
if ($vehicleType !== 'any') {
|
||||
$query->where($db->quoteName('v.vehicle_type') . ' = ' . $db->quote($vehicleType));
|
||||
}
|
||||
|
||||
$db->setQuery($query);
|
||||
$drivers = $db->loadObjectList() ?: [];
|
||||
|
||||
$nearby = [];
|
||||
|
||||
foreach ($drivers as $driver) {
|
||||
$distance = TaxiHelper::haversineDistance($lat, $lng, (float) $driver->lat, (float) $driver->lng);
|
||||
|
||||
if ($distance <= $radiusKm) {
|
||||
$driver->distance_km = round($distance, 2);
|
||||
$driver->eta_minutes = round($distance * 2.5, 1);
|
||||
$nearby[] = $driver;
|
||||
}
|
||||
}
|
||||
|
||||
usort($nearby, fn($a, $b) => $a->distance_km <=> $b->distance_km);
|
||||
|
||||
return $nearby;
|
||||
}
|
||||
|
||||
public static function dispatchRide(int $rideId, int $driverId): object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$now = Factory::getDate()->toSql();
|
||||
|
||||
// FOR UPDATE lock to prevent race condition on ride acceptance
|
||||
$db->transactionStart();
|
||||
|
||||
try {
|
||||
$db->setQuery('SELECT id, status FROM ' . $db->quoteName('#__mokosuitetaxi_rides')
|
||||
. ' WHERE id = ' . (int) $rideId . ' FOR UPDATE');
|
||||
$ride = $db->loadObject();
|
||||
|
||||
if (!$ride || !in_array($ride->status, ['requested', 'dispatched'], true)) {
|
||||
$db->transactionRollback();
|
||||
throw new \RuntimeException('Ride is no longer available for dispatch');
|
||||
}
|
||||
|
||||
$attempt = 1;
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COALESCE(MAX(attempt_number), 0)')
|
||||
->from('#__mokosuitetaxi_dispatch')
|
||||
->where('ride_id = ' . (int) $rideId));
|
||||
$attempt = (int) $db->loadResult() + 1;
|
||||
|
||||
$dispatch = (object) [
|
||||
'ride_id' => $rideId,
|
||||
'driver_id' => $driverId,
|
||||
'status' => 'offered',
|
||||
'offered_at' => $now,
|
||||
'attempt_number' => $attempt,
|
||||
];
|
||||
|
||||
$db->insertObject('#__mokosuitetaxi_dispatch', $dispatch, 'id');
|
||||
|
||||
$update = (object) [
|
||||
'id' => $rideId,
|
||||
'status' => 'dispatched',
|
||||
'driver_id' => $driverId,
|
||||
'dispatched_at' => $now,
|
||||
];
|
||||
$db->updateObject('#__mokosuitetaxi_rides', $update, 'id');
|
||||
|
||||
$db->transactionCommit();
|
||||
|
||||
return $dispatch;
|
||||
} catch (\Throwable $e) {
|
||||
$db->transactionRollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public static function acceptRide(int $rideId, int $driverId): bool
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$now = Factory::getDate()->toSql();
|
||||
|
||||
$db->transactionStart();
|
||||
|
||||
try {
|
||||
$db->setQuery('SELECT id, status, driver_id FROM ' . $db->quoteName('#__mokosuitetaxi_rides')
|
||||
. ' WHERE id = ' . (int) $rideId . ' FOR UPDATE');
|
||||
$ride = $db->loadObject();
|
||||
|
||||
if (!$ride || $ride->status !== 'dispatched' || (int) $ride->driver_id !== $driverId) {
|
||||
$db->transactionRollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
$db->updateObject('#__mokosuitetaxi_rides', (object) [
|
||||
'id' => $rideId,
|
||||
'status' => 'accepted',
|
||||
'accepted_at' => $now,
|
||||
], 'id');
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->update('#__mokosuitetaxi_dispatch')
|
||||
->set($db->quoteName('status') . ' = ' . $db->quote('accepted'))
|
||||
->set($db->quoteName('responded_at') . ' = ' . $db->quote($now))
|
||||
->where('ride_id = ' . (int) $rideId)
|
||||
->where('driver_id = ' . (int) $driverId)
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('offered'));
|
||||
$db->setQuery($query)->execute();
|
||||
|
||||
$db->transactionCommit();
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
$db->transactionRollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getActiveRidesForDriver(int $driverId): array
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('*')
|
||||
->from('#__mokosuitetaxi_rides')
|
||||
->where('driver_id = ' . (int) $driverId)
|
||||
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['dispatched', 'accepted', 'arriving', 'in_progress'])) . ')')
|
||||
->order('created DESC'));
|
||||
|
||||
return $db->loadObjectList() ?: [];
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Moko\Plugin\System\MokoSuiteTaxi\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
class FareHelper
|
||||
{
|
||||
public static function calculateFare(
|
||||
float $distanceKm,
|
||||
float $durationMinutes,
|
||||
string $vehicleType = 'sedan',
|
||||
?int $zoneId = null,
|
||||
float $surgeMultiplier = 1.0
|
||||
): object {
|
||||
$rule = self::getApplicableRule($vehicleType, $zoneId);
|
||||
|
||||
$baseFare = (float) $rule->base_fare;
|
||||
$distanceCharge = $distanceKm * (float) $rule->per_km;
|
||||
$timeCharge = $durationMinutes * (float) $rule->per_minute;
|
||||
|
||||
$subtotal = ($baseFare + $distanceCharge + $timeCharge) * $surgeMultiplier;
|
||||
$subtotal += (float) $rule->booking_fee;
|
||||
|
||||
$hour = (int) date('H');
|
||||
if ($hour >= 22 || $hour < 6) {
|
||||
$subtotal += (float) $rule->night_surcharge;
|
||||
}
|
||||
|
||||
$subtotal *= (float) ($rule->peak_multiplier ?? 1.0);
|
||||
$total = max($subtotal, (float) $rule->minimum_fare);
|
||||
|
||||
return (object) [
|
||||
'fare_id' => (int) $rule->id,
|
||||
'base_fare' => $baseFare,
|
||||
'distance_charge' => round($distanceCharge, 2),
|
||||
'time_charge' => round($timeCharge, 2),
|
||||
'surge_multiplier' => $surgeMultiplier,
|
||||
'surcharges' => round((float) $rule->night_surcharge + (float) $rule->booking_fee, 2),
|
||||
'total_fare' => round($total, 2),
|
||||
];
|
||||
}
|
||||
|
||||
public static function estimateFare(
|
||||
float $pickupLat,
|
||||
float $pickupLng,
|
||||
float $dropoffLat,
|
||||
float $dropoffLng,
|
||||
string $vehicleType = 'sedan'
|
||||
): object {
|
||||
$distanceKm = TaxiHelper::haversineDistance($pickupLat, $pickupLng, $dropoffLat, $dropoffLng);
|
||||
$distanceKm *= 1.3; // road distance approximation
|
||||
$durationMinutes = $distanceKm * 2.0;
|
||||
|
||||
$estimate = self::calculateFare($distanceKm, $durationMinutes, $vehicleType);
|
||||
$estimate->estimated_distance_km = round($distanceKm, 2);
|
||||
$estimate->estimated_duration_minutes = round($durationMinutes, 1);
|
||||
|
||||
return $estimate;
|
||||
}
|
||||
|
||||
public static function getApplicableRule(string $vehicleType = 'sedan', ?int $zoneId = null): object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$now = Factory::getDate()->format('Y-m-d');
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from('#__mokosuitetaxi_fares')
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->where('(' . $db->quoteName('effective_from') . ' IS NULL OR ' . $db->quoteName('effective_from') . ' <= ' . $db->quote($now) . ')')
|
||||
->where('(' . $db->quoteName('effective_to') . ' IS NULL OR ' . $db->quoteName('effective_to') . ' >= ' . $db->quote($now) . ')')
|
||||
->order('zone_id DESC, vehicle_type DESC')
|
||||
->setLimit(1);
|
||||
|
||||
if ($zoneId) {
|
||||
$query->where('(' . $db->quoteName('zone_id') . ' = ' . (int) $zoneId . ' OR ' . $db->quoteName('zone_id') . ' IS NULL)');
|
||||
} else {
|
||||
$query->where($db->quoteName('zone_id') . ' IS NULL');
|
||||
}
|
||||
|
||||
$query->where('(' . $db->quoteName('vehicle_type') . ' = ' . $db->quote($vehicleType) . ' OR ' . $db->quoteName('vehicle_type') . ' = ' . $db->quote('all') . ')');
|
||||
|
||||
$db->setQuery($query);
|
||||
$rule = $db->loadObject();
|
||||
|
||||
if (!$rule) {
|
||||
return (object) [
|
||||
'id' => 0, 'base_fare' => 2.50, 'per_km' => 1.50, 'per_minute' => 0.25,
|
||||
'minimum_fare' => 5.00, 'booking_fee' => 0.00, 'peak_multiplier' => 1.00,
|
||||
'night_surcharge' => 0.00, 'airport_surcharge' => 0.00,
|
||||
];
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
public static function calculateDriverPayout(float $totalFare, ?float $driverCommissionRate = null): object
|
||||
{
|
||||
if ($driverCommissionRate === null) {
|
||||
$params = Factory::getApplication()->bootPlugin('mokosuitetaxi', 'system')->params;
|
||||
$driverCommissionRate = (float) ($params->get('commission_rate', 20));
|
||||
}
|
||||
|
||||
$platformFee = round($totalFare * ($driverCommissionRate / 100), 2);
|
||||
$driverPayout = round($totalFare - $platformFee, 2);
|
||||
|
||||
return (object) [
|
||||
'total_fare' => $totalFare,
|
||||
'commission_rate' => $driverCommissionRate,
|
||||
'platform_fee' => $platformFee,
|
||||
'driver_payout' => $driverPayout,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Moko\Plugin\System\MokoSuiteTaxi\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
class RideHelper
|
||||
{
|
||||
private const STATUS_FLOW = [
|
||||
'requested' => ['dispatched', 'cancelled', 'no_driver'],
|
||||
'dispatched' => ['accepted', 'cancelled', 'no_driver'],
|
||||
'accepted' => ['arriving', 'cancelled'],
|
||||
'arriving' => ['in_progress', 'cancelled'],
|
||||
'in_progress' => ['completed', 'cancelled'],
|
||||
'completed' => [],
|
||||
'cancelled' => [],
|
||||
'no_driver' => ['requested'],
|
||||
];
|
||||
|
||||
public static function requestRide(array $data): object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$now = Factory::getDate()->toSql();
|
||||
$user = Factory::getApplication()->getIdentity();
|
||||
|
||||
$ride = (object) [
|
||||
'ride_ref' => TaxiHelper::generateRideRef(),
|
||||
'rider_contact_id' => isset($data['rider_contact_id']) ? (int) $data['rider_contact_id'] : null,
|
||||
'rider_name' => $data['rider_name'],
|
||||
'rider_phone' => $data['rider_phone'] ?? '',
|
||||
'status' => 'requested',
|
||||
'ride_type' => $data['ride_type'] ?? 'on_demand',
|
||||
'vehicle_type_requested' => $data['vehicle_type'] ?? 'any',
|
||||
'pickup_address' => $data['pickup_address'],
|
||||
'pickup_lat' => $data['pickup_lat'] ?? null,
|
||||
'pickup_lng' => $data['pickup_lng'] ?? null,
|
||||
'dropoff_address' => $data['dropoff_address'] ?? '',
|
||||
'dropoff_lat' => $data['dropoff_lat'] ?? null,
|
||||
'dropoff_lng' => $data['dropoff_lng'] ?? null,
|
||||
'scheduled_at' => $data['scheduled_at'] ?? null,
|
||||
'passenger_count' => (int) ($data['passenger_count'] ?? 1),
|
||||
'payment_method' => $data['payment_method'] ?? 'cash',
|
||||
'notes' => $data['notes'] ?? '',
|
||||
'created' => $now,
|
||||
'created_by' => $user ? (int) $user->id : 0,
|
||||
];
|
||||
|
||||
if ($ride->pickup_lat && $ride->pickup_lng && $ride->dropoff_lat && $ride->dropoff_lng) {
|
||||
$estimate = FareHelper::estimateFare(
|
||||
(float) $ride->pickup_lat, (float) $ride->pickup_lng,
|
||||
(float) $ride->dropoff_lat, (float) $ride->dropoff_lng,
|
||||
$ride->vehicle_type_requested === 'any' ? 'sedan' : $ride->vehicle_type_requested
|
||||
);
|
||||
$ride->fare_id = $estimate->fare_id;
|
||||
$ride->base_fare = $estimate->base_fare;
|
||||
$ride->distance_charge = $estimate->distance_charge;
|
||||
$ride->time_charge = $estimate->time_charge;
|
||||
$ride->surcharges = $estimate->surcharges;
|
||||
$ride->total_fare = $estimate->total_fare;
|
||||
}
|
||||
|
||||
$db->insertObject('#__mokosuitetaxi_rides', $ride, 'id');
|
||||
|
||||
return $ride;
|
||||
}
|
||||
|
||||
public static function updateStatus(int $rideId, string $newStatus): bool
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$now = Factory::getDate()->toSql();
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('id, status, driver_id')
|
||||
->from('#__mokosuitetaxi_rides')
|
||||
->where('id = ' . (int) $rideId));
|
||||
$ride = $db->loadObject();
|
||||
|
||||
if (!$ride) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowed = self::STATUS_FLOW[$ride->status] ?? [];
|
||||
|
||||
if (!in_array($newStatus, $allowed, true)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Cannot transition from '{$ride->status}' to '{$newStatus}'"
|
||||
);
|
||||
}
|
||||
|
||||
$update = (object) ['id' => $rideId, 'status' => $newStatus];
|
||||
|
||||
$timestampMap = [
|
||||
'arriving' => 'arrived_at',
|
||||
'in_progress' => 'started_at',
|
||||
'completed' => 'completed_at',
|
||||
'cancelled' => 'cancelled_at',
|
||||
];
|
||||
|
||||
if (isset($timestampMap[$newStatus])) {
|
||||
$field = $timestampMap[$newStatus];
|
||||
$update->$field = $now;
|
||||
}
|
||||
|
||||
$db->updateObject('#__mokosuitetaxi_rides', $update, 'id');
|
||||
|
||||
if ($newStatus === 'completed' && $ride->driver_id) {
|
||||
self::updateDriverStats((int) $ride->driver_id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function completeRide(int $rideId, float $distanceKm, float $durationMinutes): object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$now = Factory::getDate()->toSql();
|
||||
|
||||
$db->setQuery($db->getQuery(true)->select('*')->from('#__mokosuitetaxi_rides')->where('id = ' . (int) $rideId));
|
||||
$ride = $db->loadObject();
|
||||
|
||||
if (!$ride || $ride->status !== 'in_progress') {
|
||||
throw new \RuntimeException('Ride is not in progress');
|
||||
}
|
||||
|
||||
$vehicleType = $ride->vehicle_type_requested === 'any' ? 'sedan' : $ride->vehicle_type_requested;
|
||||
$fare = FareHelper::calculateFare($distanceKm, $durationMinutes, $vehicleType, null, (float) $ride->surge_multiplier);
|
||||
$payout = FareHelper::calculateDriverPayout($fare->total_fare);
|
||||
|
||||
$update = (object) [
|
||||
'id' => $rideId,
|
||||
'status' => 'completed',
|
||||
'completed_at' => $now,
|
||||
'distance_km' => $distanceKm,
|
||||
'duration_minutes' => $durationMinutes,
|
||||
'base_fare' => $fare->base_fare,
|
||||
'distance_charge' => $fare->distance_charge,
|
||||
'time_charge' => $fare->time_charge,
|
||||
'surcharges' => $fare->surcharges,
|
||||
'total_fare' => $fare->total_fare,
|
||||
'driver_payout' => $payout->driver_payout,
|
||||
'platform_fee' => $payout->platform_fee,
|
||||
];
|
||||
|
||||
$db->updateObject('#__mokosuitetaxi_rides', $update, 'id');
|
||||
|
||||
if ($ride->driver_id) {
|
||||
self::updateDriverStats((int) $ride->driver_id);
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
public static function getById(int $rideId): ?object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select([
|
||||
'r.*',
|
||||
'd.name AS driver_name', 'd.phone AS driver_phone', 'd.rating AS driver_rating',
|
||||
'v.plate_number', 'v.make AS vehicle_make', 'v.model AS vehicle_model', 'v.color AS vehicle_color',
|
||||
])
|
||||
->from($db->quoteName('#__mokosuitetaxi_rides', 'r'))
|
||||
->join('LEFT', $db->quoteName('#__mokosuitetaxi_drivers', 'd') . ' ON d.id = r.driver_id')
|
||||
->join('LEFT', $db->quoteName('#__mokosuitetaxi_vehicles', 'v') . ' ON v.id = r.vehicle_id')
|
||||
->where('r.id = ' . (int) $rideId);
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
return $db->loadObject() ?: null;
|
||||
}
|
||||
|
||||
private static function updateDriverStats(int $driverId): void
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select(['COUNT(*) AS total_rides', 'COALESCE(SUM(driver_payout), 0) AS total_earnings'])
|
||||
->from('#__mokosuitetaxi_rides')
|
||||
->where('driver_id = ' . (int) $driverId)
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('completed')));
|
||||
$stats = $db->loadObject();
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COALESCE(AVG(rating), 5.00)')
|
||||
->from('#__mokosuitetaxi_ratings')
|
||||
->join('INNER', '#__mokosuitetaxi_rides AS r ON r.id = ride_id')
|
||||
->where('r.driver_id = ' . (int) $driverId)
|
||||
->where($db->quoteName('rated_by') . ' = ' . $db->quote('rider')));
|
||||
$avgRating = (float) $db->loadResult();
|
||||
|
||||
$db->updateObject('#__mokosuitetaxi_drivers', (object) [
|
||||
'id' => $driverId,
|
||||
'total_rides' => (int) $stats->total_rides,
|
||||
'total_earnings' => (float) $stats->total_earnings,
|
||||
'rating' => round($avgRating, 2),
|
||||
'modified' => Factory::getDate()->toSql(),
|
||||
], 'id');
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Moko\Plugin\System\MokoSuiteTaxi\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
class TaxiHelper
|
||||
{
|
||||
public static function getDashboard(): object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$now = Factory::getDate()->toSql();
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from('#__mokosuitetaxi_drivers')
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('active'))
|
||||
->where($db->quoteName('published') . ' = 1'));
|
||||
$activeDrivers = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from('#__mokosuitetaxi_rides')
|
||||
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['requested', 'dispatched', 'accepted', 'arriving', 'in_progress'])) . ')'));
|
||||
$activeRides = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from('#__mokosuitetaxi_vehicles')
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('available'))
|
||||
->where($db->quoteName('published') . ' = 1'));
|
||||
$availableVehicles = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COALESCE(SUM(' . $db->quoteName('total_fare') . '), 0)')
|
||||
->from('#__mokosuitetaxi_rides')
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('completed'))
|
||||
->where($db->quoteName('payment_status') . ' = ' . $db->quote('paid'))
|
||||
->where('DATE(' . $db->quoteName('completed_at') . ') = CURDATE()'));
|
||||
$todayRevenue = (float) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from('#__mokosuitetaxi_rides')
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('completed'))
|
||||
->where('DATE(' . $db->quoteName('completed_at') . ') = CURDATE()'));
|
||||
$todayCompleted = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COALESCE(AVG(' . $db->quoteName('r.rating') . '), 0)')
|
||||
->from($db->quoteName('#__mokosuitetaxi_ratings', 'r'))
|
||||
->where($db->quoteName('r.rated_by') . ' = ' . $db->quote('rider'))
|
||||
->where('DATE(' . $db->quoteName('r.created') . ') >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)'));
|
||||
$avgRating = round((float) $db->loadResult(), 2);
|
||||
|
||||
return (object) [
|
||||
'active_drivers' => $activeDrivers,
|
||||
'active_rides' => $activeRides,
|
||||
'available_vehicles' => $availableVehicles,
|
||||
'today_revenue' => $todayRevenue,
|
||||
'today_completed' => $todayCompleted,
|
||||
'avg_rating_30d' => $avgRating,
|
||||
];
|
||||
}
|
||||
|
||||
public static function generateRideRef(): string
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$prefix = 'TX';
|
||||
|
||||
do {
|
||||
$ref = $prefix . strtoupper(bin2hex(random_bytes(4)));
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from('#__mokosuitetaxi_rides')
|
||||
->where($db->quoteName('ride_ref') . ' = ' . $db->quote($ref)));
|
||||
} while ((int) $db->loadResult() > 0);
|
||||
|
||||
return $ref;
|
||||
}
|
||||
|
||||
public static function haversineDistance(float $lat1, float $lng1, float $lat2, float $lng2): float
|
||||
{
|
||||
$earthRadius = 6371.0;
|
||||
$dLat = deg2rad($lat2 - $lat1);
|
||||
$dLng = deg2rad($lng2 - $lng1);
|
||||
$a = sin($dLat / 2) * sin($dLat / 2) +
|
||||
cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
|
||||
sin($dLng / 2) * sin($dLng / 2);
|
||||
|
||||
return $earthRadius * 2 * atan2(sqrt($a), sqrt(1 - $a));
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Moko\Plugin\System\MokoSuiteTaxi\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
class VehicleHelper
|
||||
{
|
||||
public static function getById(int $vehicleId): ?object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select([
|
||||
'v.*',
|
||||
'd.name AS assigned_driver',
|
||||
'd.id AS assigned_driver_id',
|
||||
])
|
||||
->from($db->quoteName('#__mokosuitetaxi_vehicles', 'v'))
|
||||
->join('LEFT', $db->quoteName('#__mokosuitetaxi_drivers', 'd')
|
||||
. ' ON d.vehicle_id = v.id AND d.status = ' . $db->quote('active'))
|
||||
->where('v.id = ' . (int) $vehicleId);
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
return $db->loadObject() ?: null;
|
||||
}
|
||||
|
||||
public static function getFleetSummary(): object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select([
|
||||
$db->quoteName('status'),
|
||||
'COUNT(*) AS count',
|
||||
])
|
||||
->from('#__mokosuitetaxi_vehicles')
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->group('status'));
|
||||
|
||||
$rows = $db->loadObjectList('status') ?: [];
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select([
|
||||
$db->quoteName('vehicle_type'),
|
||||
'COUNT(*) AS count',
|
||||
])
|
||||
->from('#__mokosuitetaxi_vehicles')
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->group('vehicle_type'));
|
||||
|
||||
$byType = $db->loadObjectList('vehicle_type') ?: [];
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from('#__mokosuitetaxi_vehicles')
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->where('(' . $db->quoteName('insurance_expiry') . ' <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)'
|
||||
. ' OR ' . $db->quoteName('inspection_expiry') . ' <= DATE_ADD(CURDATE(), INTERVAL 30 DAY))'));
|
||||
$expiringCount = (int) $db->loadResult();
|
||||
|
||||
return (object) [
|
||||
'by_status' => $rows,
|
||||
'by_type' => $byType,
|
||||
'expiring_soon' => $expiringCount,
|
||||
'total' => array_sum(array_map(fn($r) => (int) $r->count, (array) $rows)),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getExpiringDocuments(int $days = 30): array
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$deadline = Factory::getDate('+' . $days . ' days')->format('Y-m-d');
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('id, plate_number, make, model, insurance_expiry, inspection_expiry')
|
||||
->from('#__mokosuitetaxi_vehicles')
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->where('(' . $db->quoteName('insurance_expiry') . ' <= ' . $db->quote($deadline)
|
||||
. ' OR ' . $db->quoteName('inspection_expiry') . ' <= ' . $db->quote($deadline) . ')')
|
||||
->order('LEAST(COALESCE(insurance_expiry, ' . $db->quote('9999-12-31') . '), COALESCE(inspection_expiry, ' . $db->quote('9999-12-31') . ')) ASC'));
|
||||
|
||||
return $db->loadObjectList() ?: [];
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Moko\Plugin\System\MokoSuiteTaxi\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
class ZoneHelper
|
||||
{
|
||||
public static function detectZone(float $lat, float $lng): ?object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('*')
|
||||
->from('#__mokosuitetaxi_zones')
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->where('center_lat IS NOT NULL')
|
||||
->where('center_lng IS NOT NULL')
|
||||
->order('ordering ASC'));
|
||||
|
||||
$zones = $db->loadObjectList() ?: [];
|
||||
|
||||
foreach ($zones as $zone) {
|
||||
$radius = (float) ($zone->radius_km ?: 50.0);
|
||||
$distance = TaxiHelper::haversineDistance($lat, $lng, (float) $zone->center_lat, (float) $zone->center_lng);
|
||||
|
||||
if ($distance <= $radius) {
|
||||
$zone->distance_from_center = round($distance, 2);
|
||||
return $zone;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getSurgeMultiplier(int $zoneId): float
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from('#__mokosuitetaxi_rides')
|
||||
->where($db->quoteName('pickup_zone_id') . ' = ' . (int) $zoneId)
|
||||
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['requested', 'dispatched'])) . ')')
|
||||
->where($db->quoteName('created') . ' >= DATE_SUB(NOW(), INTERVAL 15 MINUTE)'));
|
||||
$demandCount = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(DISTINCT d.id)')
|
||||
->from($db->quoteName('#__mokosuitetaxi_drivers', 'd'))
|
||||
->join('INNER', $db->quoteName('#__mokosuitetaxi_shifts', 's') . ' ON s.driver_id = d.id AND s.status = ' . $db->quote('active'))
|
||||
->where($db->quoteName('d.status') . ' = ' . $db->quote('active')));
|
||||
$supplyCount = max((int) $db->loadResult(), 1);
|
||||
|
||||
$ratio = $demandCount / $supplyCount;
|
||||
|
||||
if ($ratio <= 0.5) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
if ($ratio <= 1.0) {
|
||||
return 1.25;
|
||||
}
|
||||
|
||||
if ($ratio <= 2.0) {
|
||||
return 1.5;
|
||||
}
|
||||
|
||||
$params = Factory::getApplication()->bootPlugin('mokosuitetaxi', 'system')->params;
|
||||
$maxSurge = (float) $params->get('max_surge_multiplier', 3.0);
|
||||
|
||||
return min(1.5 + ($ratio - 2.0) * 0.5, $maxSurge);
|
||||
}
|
||||
|
||||
public static function listZones(string $type = ''): array
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from('#__mokosuitetaxi_zones')
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->order('ordering ASC');
|
||||
|
||||
if ($type) {
|
||||
$query->where($db->quoteName('zone_type') . ' = ' . $db->quote($type));
|
||||
}
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
return $db->loadObjectList() ?: [];
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<extension type="plugin" group="webservices" method="upgrade">
|
||||
<name>Web Services - MokoSuite Taxi</name>
|
||||
<element>mokosuitetaxi</element>
|
||||
<author>Moko Consulting</author>
|
||||
<version>06.00.00</version>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<namespace path="src">Moko\Plugin\WebServices\MokoSuiteTaxi</namespace>
|
||||
<files><folder>src</folder><folder>services</folder></files>
|
||||
</extension>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Moko\Plugin\WebServices\MokoSuiteTaxi\Extension\MokoSuiteTaxi;
|
||||
|
||||
return new class implements ServiceProviderInterface {
|
||||
public function register(Container $container): void {
|
||||
$container->set(PluginInterface::class, function (Container $container) {
|
||||
return new MokoSuiteTaxi($container->get(DispatcherInterface::class), (array) PluginHelper::getPlugin('webservices', 'mokosuitetaxi'));
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Authored-by: Moko Consulting
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\WebServices\MokoSuiteTaxi\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
class MokoSuiteTaxi extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return ['onBeforeApiRoute' => 'onBeforeApiRoute'];
|
||||
}
|
||||
|
||||
public function onBeforeApiRoute(&$event): void
|
||||
{
|
||||
$router = $event->getArgument('router');
|
||||
$opts = ['component' => 'com_mokosuitetaxi'];
|
||||
|
||||
$router->createCRUDRoutes('v1/mokosuitetaxi/rides', 'rides', $opts);
|
||||
$router->createCRUDRoutes('v1/mokosuitetaxi/vehicles', 'vehicles', $opts);
|
||||
$router->createCRUDRoutes('v1/mokosuitetaxi/drivers', 'drivers', $opts);
|
||||
$router->createCRUDRoutes('v1/mokosuitetaxi/zones', 'zones', $opts);
|
||||
$router->createCRUDRoutes('v1/mokosuitetaxi/fares', 'fares', $opts);
|
||||
$router->createCRUDRoutes('v1/mokosuitetaxi/dispatch', 'dispatch', $opts);
|
||||
$router->createCRUDRoutes('v1/mokosuitetaxi/ratings', 'ratings', $opts);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="package" method="upgrade">
|
||||
<name>Package - MokoSuite Taxi</name>
|
||||
<packagename>mokosuitetaxi</packagename>
|
||||
<version>06.00.00</version>
|
||||
<creationDate>2026-06-27</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||
<description><![CDATA[
|
||||
<h3>MokoSuite Taxi</h3>
|
||||
<p>Layer 2 ride-hailing extension for the MokoSuite platform on Joomla 6.</p>
|
||||
<p><strong>Requires:</strong> MokoSuiteClient (Layer 0), MokoSuiteCRM (Layer 1)</p>
|
||||
<h4>Included Extensions</h4>
|
||||
<ul>
|
||||
<li><strong>System - MokoSuite Taxi</strong> — Core helpers, SQL schema (8 tables), dispatch engine, fare calculator</li>
|
||||
<li><strong>MokoSuite Taxi Component</strong> — Admin dashboard, rides, vehicles, drivers, zones, and fares management</li>
|
||||
<li><strong>Web Services - MokoSuite Taxi</strong> — REST API with 7 endpoints for rides, vehicles, drivers, zones, fares, dispatch, and ratings</li>
|
||||
</ul>
|
||||
<h4>Features</h4>
|
||||
<ul>
|
||||
<li>On-demand, scheduled, airport, and corporate ride types</li>
|
||||
<li>Zone-aware dispatch with bounding-box pre-filter and haversine distance matching</li>
|
||||
<li>Dynamic surge pricing based on zone-level demand/supply ratio</li>
|
||||
<li>Fleet management with vehicle tracking and document expiry alerts</li>
|
||||
<li>Driver profiles linked to CRM contacts with commission tracking</li>
|
||||
<li>GeoJSON fare zones with radius fallback and zone-specific pricing rules</li>
|
||||
<li>Bidirectional rider/driver ratings with feedback tags</li>
|
||||
<li>Shift scheduling with location tracking and earnings aggregation</li>
|
||||
</ul>
|
||||
<p><a href="https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteTaxi/wiki" target="_blank">Documentation</a> | <a href="https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteTaxi/issues" target="_blank">Support</a></p>
|
||||
]]></description>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
<php_minimum>8.3</php_minimum>
|
||||
<dlid prefix="dlid=" suffix=""/>
|
||||
<blockChildUninstall>true</blockChildUninstall>
|
||||
<files folder="packages">
|
||||
<file type="plugin" id="plg_system_mokosuitetaxi" group="system">plg_system_mokosuitetaxi.zip</file>
|
||||
<file type="component" id="com_mokosuitetaxi">com_mokosuitetaxi.zip</file>
|
||||
<file type="plugin" id="plg_webservices_mokosuitetaxi" group="webservices">plg_webservices_mokosuitetaxi.zip</file>
|
||||
</files>
|
||||
<updateservers>
|
||||
<server type="extension" priority="1" name="Package - MokoSuite Taxi">https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteTaxi/updates.xml</server>
|
||||
</updateservers>
|
||||
</extension>
|
||||
@@ -1,77 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteTaxi
|
||||
* @subpackage pkg_mokosuitetaxi
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Installer\InstallerAdapter;
|
||||
use Joomla\CMS\Log\Log;
|
||||
|
||||
/**
|
||||
* Package installation script for MokoSuiteTaxi.
|
||||
*/
|
||||
class Pkg_MokoSuiteTaxiInstallerScript
|
||||
{
|
||||
public function postflight(string $type, InstallerAdapter $adapter): void
|
||||
{
|
||||
$this->warnMissingLicenseKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn after install/update if no license key (dlid) is configured on the update site.
|
||||
*/
|
||||
private function warnMissingLicenseKey(): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$app = Factory::getApplication();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select([$db->quoteName('update_site_id'), $db->quoteName('extra_query')])
|
||||
->from($db->quoteName('#__update_sites'))
|
||||
->where('(' . $db->quoteName('name') . ' LIKE ' . $db->quote('%MokoSuiteTaxi%')
|
||||
. ' OR ' . $db->quoteName('location') . ' LIKE ' . $db->quote('%MokoSuiteTaxi%') . ')')
|
||||
->setLimit(1);
|
||||
$db->setQuery($query);
|
||||
$site = $db->loadObject();
|
||||
|
||||
if ($site)
|
||||
{
|
||||
$extraQuery = (string) ($site->extra_query ?? '');
|
||||
|
||||
if (!empty($extraQuery) && strpos($extraQuery, 'dlid=') !== false)
|
||||
{
|
||||
parse_str($extraQuery, $parsed);
|
||||
|
||||
if (!empty($parsed['dlid']))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$editUrl = 'index.php?option=com_installer&task=updatesite.edit&update_site_id=' . (int) $site->update_site_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
$editUrl = 'index.php?option=com_installer&view=updatesites';
|
||||
}
|
||||
|
||||
$app->enqueueMessage(
|
||||
'<strong>Moko Consulting License Key Required</strong> — '
|
||||
. 'No download key is configured. Updates will not be available until a valid license key is entered. '
|
||||
. '<a href="' . $editUrl . '" class="btn btn-sm btn-warning ms-2">Enter License Key</a>',
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
// Silent — avoid breaking install if update_sites query fails
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user