! Published.
All checks were successful
Build docker image / build_docker_image (push) Successful in 3m50s
All checks were successful
Build docker image / build_docker_image (push) Successful in 3m50s
This commit is contained in:
39
src/.env.example
Normal file
39
src/.env.example
Normal file
@@ -0,0 +1,39 @@
|
||||
APP_NAME=Backend
|
||||
APP_ENV=production
|
||||
APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_TIMEZONE=UTC
|
||||
APP_URL=http://localhost:9000
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
APP_MAINTENANCE_STORE=database
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
DB_DATABASE=/app/Backend/database/sqlite/database.sqlite
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=file
|
||||
CACHE_PREFIX=
|
||||
|
||||
ADMIN_EMAIL=admin@example.com
|
||||
ADMIN_PASSWORD=PlEaSe_ChAnGe_Me
|
||||
11
src/.gitattributes
vendored
Normal file
11
src/.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
||||
22
src/.gitignore
vendored
Normal file
22
src/.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpunit.result.cache
|
||||
.editorconfig
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
auth.json
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/.fleet
|
||||
/.idea
|
||||
/.vscode
|
||||
/.php-cs-fixer.cache
|
||||
tests/Coverage
|
||||
35
src/.php-cs-fixer.dist.php
Normal file
35
src/.php-cs-fixer.dist.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
|
||||
use PhpCsFixer\Config;
|
||||
use PhpCsFixer\Finder;
|
||||
|
||||
return (new Config())
|
||||
->setRules([
|
||||
'@PER-CS' => true,
|
||||
'@PHP82Migration' => true,
|
||||
'new_with_parentheses' => [
|
||||
'anonymous_class' => false,
|
||||
],
|
||||
'braces_position' => [
|
||||
'anonymous_classes_opening_brace' => 'next_line_unless_newline_at_signature_end',
|
||||
],
|
||||
'function_declaration' => [
|
||||
'closure_fn_spacing' => 'one',
|
||||
'closure_function_spacing' => 'one',
|
||||
],
|
||||
'concat_space' => [
|
||||
'spacing' => 'none',
|
||||
],
|
||||
'single_trait_insert_per_statement' => false,
|
||||
'no_blank_lines_after_class_opening' => false,
|
||||
])
|
||||
->setFinder(
|
||||
(new Finder())
|
||||
->in(__DIR__)
|
||||
->exclude([
|
||||
'storage/framework/views',
|
||||
'bootstrap/cache',
|
||||
])
|
||||
)
|
||||
;
|
||||
4
src/.scribe/.filehashes
Normal file
4
src/.scribe/.filehashes
Normal file
@@ -0,0 +1,4 @@
|
||||
# GENERATED. YOU SHOULDN'T MODIFY OR DELETE THIS FILE.
|
||||
# Scribe uses this file to know when you change something manually in your docs.
|
||||
.scribe/intro.md=02e48ceeff338f664f57072da0297805
|
||||
.scribe/auth.md=5ea0b82b0e0252887e6b1c8bc7382937
|
||||
156
src/.scribe/append.md
Normal file
156
src/.scribe/append.md
Normal file
@@ -0,0 +1,156 @@
|
||||
|
||||
# Classes
|
||||
|
||||
## DiscordUser
|
||||
|
||||
This record stores the general informations of a discord user.
|
||||
|
||||
### Fields
|
||||
|
||||
- **id** - *The unique ID of the DiscordUser record.*
|
||||
- **snowflake** - *The unique snowflake of the discord user.*
|
||||
- **user_name** - *The name of the discord user.*
|
||||
- **global_name** - *The global name of the discord user.*
|
||||
- **locale** - *The locale of the discord user.*
|
||||
- **timezone** - *The timezone of the discord user.*
|
||||
|
||||
> Exaple:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 42,
|
||||
"snowflake": "481398158916845568",
|
||||
"user_name": "bigfootmcfly",
|
||||
"global_name": "BigFoot McFly",
|
||||
"locale": "hu_HU",
|
||||
"timezone": "Europe/Budapest"
|
||||
}
|
||||
```
|
||||
|
||||
## Remainder
|
||||
|
||||
### Fields
|
||||
|
||||
- **id** - *The unique ID of the Remainder record.*
|
||||
- **discord_user_id** - *The internal ID of the [DiscordUser](#discorduser).*
|
||||
- **channel_id** - *The snowflake of the channel the remainder should be sent to.*
|
||||
- **due_at** - *The "Due at" time ([timestamp](#timestamp)) of the remainder.*
|
||||
- **message** - *The message to send to the discord user.*
|
||||
- **status** - *The [status](#remainderstatus) of the remainder.*
|
||||
- **error** - *The error (if any) that caused the remainder to fail.*
|
||||
|
||||
> Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 18568,
|
||||
"discord_user_id": 42,
|
||||
"channel_id": null,
|
||||
"due_at": 1732950700,
|
||||
"message": "Maintance completed.",
|
||||
"status": "new",
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
# Types
|
||||
|
||||
## Timestamp
|
||||
|
||||
It measures time by the number of non-leap seconds that have elapsed since 00:00:00 UTC on 1 January 1970, the Unix epoch.
|
||||
|
||||
See: <a href="https://en.wikipedia.org/wiki/Unix_time" target="_blank">Unix time on Wikipedia</a>
|
||||
|
||||
See: <a href="https://www.unixtimestamp.com/" target="_blank">Timestamp converter</a>
|
||||
|
||||
## Snowflake
|
||||
|
||||
A unique identifier within the discord namespace.
|
||||
|
||||
See: <a href="https://discord.com/developers/docs/reference#snowflakes" target="_blank">Discord reference #snowflakes</a>
|
||||
|
||||
## Locale
|
||||
|
||||
A valid local identifier.
|
||||
|
||||
<a href="https://github.com/Nerdtrix/language-list/blob/main/language-list-json.json" target="_blank">Locale list (json)</a>
|
||||
|
||||
## Timezone
|
||||
|
||||
A valid timezone.
|
||||
|
||||
<a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones" target="_blank">Timezone list</a>
|
||||
|
||||
## RemainderStatus
|
||||
|
||||
The status of a remainder.
|
||||
|
||||
Available values:
|
||||
|
||||
- **new** - *New remainder.*
|
||||
- **failed** - *Something went wrong, will not try to resend it.*
|
||||
- **pending** - *The remainder is currently being processed.*
|
||||
- **deleted** - *The remainder was deleted.*
|
||||
- **finished** - *The remainder finished succesfully.*
|
||||
- **cancelled** - *The remainder was cancelled.*
|
||||
|
||||
|
||||
|
||||
# Error responses
|
||||
|
||||
## Unauthorized (401)
|
||||
|
||||
In case no authentication is provided or the authentication fails, a **`401, Unauthorized`** response is returned.
|
||||
|
||||
> Exa
|
||||
mple response (401, Unauthorized):
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Unauthenticated."
|
||||
}
|
||||
```
|
||||
|
||||
## Not Found (404)
|
||||
|
||||
If the requested record cannot be found, a **`404, Not Found`** response is returned.
|
||||
|
||||
> Example response (404, Not Found):
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Not Found."
|
||||
}
|
||||
```
|
||||
|
||||
## Unprocessable Content (422)
|
||||
|
||||
If the request cannot be fulfilled, a **`422, Unprocessable Content`** response is returned.
|
||||
|
||||
See the returned message for details about the failure.
|
||||
|
||||
This is mostly due to **`validation errors`**.
|
||||
|
||||
> Example response (422, Unprocessable Content):
|
||||
|
||||
```json
|
||||
{
|
||||
"errors": {
|
||||
"snowflake": [
|
||||
"Invalid snowflake"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Internal Server Error (500)
|
||||
|
||||
An internal server error occured. Please try again later or contact the operator.
|
||||
|
||||
> Example response (500, Internal Server Error):
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Server Error"
|
||||
}
|
||||
```
|
||||
7
src/.scribe/auth.md
Normal file
7
src/.scribe/auth.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Authenticating requests
|
||||
|
||||
To authenticate requests, include an **`Authorization`** header with the value **`"Bearer {YOUR_AUTH_KEY}"`**.
|
||||
|
||||
All authenticated endpoints are marked with a `requires authentication` badge in the documentation below.
|
||||
|
||||
You can manage your tokens at the **`profile`** page in the **`API Tokens`** section.
|
||||
435
src/.scribe/endpoints.cache/00.yaml
Normal file
435
src/.scribe/endpoints.cache/00.yaml
Normal file
@@ -0,0 +1,435 @@
|
||||
## Autogenerated by Scribe. DO NOT MODIFY.
|
||||
|
||||
name: 'Discord User Managment'
|
||||
description: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
endpoints:
|
||||
-
|
||||
httpMethods:
|
||||
- GET
|
||||
uri: api/v1/discord-users
|
||||
metadata:
|
||||
groupName: 'Discord User Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'List DiscorUsers.'
|
||||
description: 'Paginated list of [DiscordUser](#discorduser) records.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters: []
|
||||
cleanUrlParameters: []
|
||||
queryParameters:
|
||||
page_size:
|
||||
name: page_size
|
||||
description: 'Items per page. Defaults to 100.'
|
||||
required: false
|
||||
example: 25
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
page:
|
||||
name: page
|
||||
description: 'Page to query. Defaults to 1.'
|
||||
required: false
|
||||
example: 1
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanQueryParameters:
|
||||
page_size: 25
|
||||
page: 1
|
||||
bodyParameters: []
|
||||
cleanBodyParameters: []
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":[{"id":1,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe/Budapest"},{"id":6,"snowflake":"860046989130727450","user_name":"Teszt Elek","global_name":"holnap_is_teszt_elek","locale":"hu","timezone":"Europe/Budapest"},{"id":12,"snowflake":"112233445566778899","user_name":"Igaz Álmos","global_name":"almos#1244","locale":null,"timezone":null}],"meta":{"current_page":1,"from":1,"last_page":1,"per_page":10,"to":3,"total":3}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- POST
|
||||
uri: api/v1/discord-users
|
||||
metadata:
|
||||
groupName: 'Discord User Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Create a new DiscordUser record.'
|
||||
description: 'Creates a new [DiscordUser](#discorduser) record with the provided parameters.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters: []
|
||||
cleanUrlParameters: []
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'A valid [snowflake](#snowflake).'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
user_name:
|
||||
name: user_name
|
||||
description: 'The user_name registered in Discord.'
|
||||
required: false
|
||||
example: bigfootmcfly
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
global_name:
|
||||
name: global_name
|
||||
description: 'The global_name registered in Discord.'
|
||||
required: false
|
||||
example: 'BigFoot McFly'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
avatar:
|
||||
name: avatar
|
||||
description: 'The avatar url registered in Discord.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
locale:
|
||||
name: locale
|
||||
description: 'A valid [locale](#locale).'
|
||||
required: false
|
||||
example: hu_HU
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
timezone:
|
||||
name: timezone
|
||||
description: 'A valid [time zone](#timezone).'
|
||||
required: false
|
||||
example: Europe/Budapest
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
snowflake: '481398158916845568'
|
||||
user_name: bigfootmcfly
|
||||
global_name: 'BigFoot McFly'
|
||||
locale: hu_HU
|
||||
timezone: Europe/Budapest
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/Budapest"}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
-
|
||||
status: 422
|
||||
content: '{"errors":{"snowflake":["The snowflake has already been taken."]}}'
|
||||
headers: []
|
||||
description: 'Unprocessable Content'
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- GET
|
||||
uri: 'api/v1/discord-users/{id}'
|
||||
metadata:
|
||||
groupName: 'Discord User Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Get the specified DiscordUser record.'
|
||||
description: 'Returns the specified [DiscordUser](#discorduser) record.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
id:
|
||||
name: id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
id: 42
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters: []
|
||||
cleanBodyParameters: []
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/Budapest"}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- PUT
|
||||
- PATCH
|
||||
uri: 'api/v1/discord-users/{id}'
|
||||
metadata:
|
||||
groupName: 'Discord User Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Update the specified DiscordUser record.'
|
||||
description: 'Updates the specified [DiscordUser](#discorduser) with the supplied values.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
id:
|
||||
name: id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
id: 42
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'The snowflake of the [DiscordUser](#discorduser) to update.'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
user_name:
|
||||
name: user_name
|
||||
description: 'The user_name registered in Discord.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
global_name:
|
||||
name: global_name
|
||||
description: 'The global_name registered in Discord.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
avatar:
|
||||
name: avatar
|
||||
description: 'The avatar url registered in Discord.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
locale:
|
||||
name: locale
|
||||
description: 'A valid locale. <a href="https://github.com/Nerdtrix/language-list/blob/main/language-list-json.json" target="_blank">Locale list (json)</a>'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
timezone:
|
||||
name: timezone
|
||||
description: 'A valid [time zone](#timezone).'
|
||||
required: false
|
||||
example: Europe/London
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
snowflake: '481398158916845568'
|
||||
timezone: Europe/London
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/London"}}'
|
||||
headers: []
|
||||
description: ''
|
||||
custom: []
|
||||
-
|
||||
status: 422
|
||||
content: '{"errors":{"snowflake":["Invalid snowflake"]}}'
|
||||
headers: []
|
||||
description: 'Unprocessable Content'
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- DELETE
|
||||
uri: 'api/v1/discord-users/{id}'
|
||||
metadata:
|
||||
groupName: 'Discord User Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Remove the specified DiscordUser record.'
|
||||
description: 'Removes the specified [DiscordUser](#discorduser) record **with** all the [Remainder](#remainder) records belonging to it.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
id:
|
||||
name: id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
id: 42
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'The snowflake of the [DiscordUser](#discorduser) to delete.'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
snowflake: '481398158916845568'
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 204
|
||||
content: '{}'
|
||||
headers: []
|
||||
description: ''
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
371
src/.scribe/endpoints.cache/01.yaml
Normal file
371
src/.scribe/endpoints.cache/01.yaml
Normal file
@@ -0,0 +1,371 @@
|
||||
## Autogenerated by Scribe. DO NOT MODIFY.
|
||||
|
||||
name: 'Remainder Managment'
|
||||
description: |-
|
||||
|
||||
APIs to manage Remainders records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [Remainder](#remainder) records.
|
||||
endpoints:
|
||||
-
|
||||
httpMethods:
|
||||
- GET
|
||||
uri: 'api/v1/discord-users/{discord_user_id}/remainders'
|
||||
metadata:
|
||||
groupName: 'Remainder Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage Remainders records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [Remainder](#remainder) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'List of Remainder records.'
|
||||
description: 'Paginated list of [Remainder](#remainder) records belonging to the specified [DiscordUser](#discorduser).'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
discord_user_id:
|
||||
name: discord_user_id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
discord_user_id: 42
|
||||
queryParameters:
|
||||
page_size:
|
||||
name: page_size
|
||||
description: 'Items per page. Defaults to 100.'
|
||||
required: false
|
||||
example: 25
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
page:
|
||||
name: page
|
||||
description: 'Page to query. Defaults to 1.'
|
||||
required: false
|
||||
example: 1
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanQueryParameters:
|
||||
page_size: 25
|
||||
page: 1
|
||||
bodyParameters: []
|
||||
cleanBodyParameters: []
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":[{"id":38,"discord_user_id":42,"channel_id":null,"due_at":1736259300,"message":"Update todo list","status":"new","error":null},{"id":121,"discord_user_id":42,"channel_id":null,"due_at":1736259480,"message":"Water plants","status":"new","error":null}],"meta":{"current_page":1,"from":1,"last_page":1,"per_page":25,"to":2,"total":2}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- POST
|
||||
uri: 'api/v1/discord-users/{discord_user_id}/remainders'
|
||||
metadata:
|
||||
groupName: 'Remainder Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage Remainders records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [Remainder](#remainder) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Create a new Remainder record.'
|
||||
description: 'Creates a new [Remainder](#remainder) record with the provided parameters.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
discord_user_id:
|
||||
name: discord_user_id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
discord_user_id: 42
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
due_at:
|
||||
name: due_at
|
||||
description: 'The "Due at" time ([timestamp](#timestamp)) of the remainder'
|
||||
required: true
|
||||
example: '1732550400'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
message:
|
||||
name: message
|
||||
description: 'The message to send to the discord user.'
|
||||
required: true
|
||||
example: 'Check maintance results!'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
channel_id:
|
||||
name: channel_id
|
||||
description: 'The [snowflake](#snowflake) of the channel to send the remainder to.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
due_at: '1732550400'
|
||||
message: 'Check maintance results!'
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":18568,"discord_user_id":42,"channel_id":null,"due_at":1732550400,"message":"Check maintance results!","status":"new","error":null}}'
|
||||
headers: []
|
||||
description: ''
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- PUT
|
||||
uri: 'api/v1/discord-users/{discord_user_id}/remainders/{id}'
|
||||
metadata:
|
||||
groupName: 'Remainder Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage Remainders records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [Remainder](#remainder) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Update the specified Remainder record.'
|
||||
description: 'Updates the specified [Remainder](#remainder) record with the provided parameters.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
discord_user_id:
|
||||
name: discord_user_id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
id:
|
||||
name: id
|
||||
description: '[Remainder](#remainder) ID.'
|
||||
required: true
|
||||
example: 18568
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
discord_user_id: 42
|
||||
id: 18568
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
due_at:
|
||||
name: due_at
|
||||
description: 'The "Due at" time ([timestamp](#timestamp)) of the remainder.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
message:
|
||||
name: message
|
||||
description: 'The message to send to the discord user.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: false
|
||||
custom: []
|
||||
channel_id:
|
||||
name: channel_id
|
||||
description: 'The [snowflake](#snowflake) of the channel to send the remainder to.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
status:
|
||||
name: status
|
||||
description: |-
|
||||
Status of the [Remainder](#remainder).
|
||||
|
||||
For possible values see: [RemainderStatus](#remainderstatus)
|
||||
required: false
|
||||
example: failed
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
error:
|
||||
name: error
|
||||
description: 'Error description in case of failure.'
|
||||
required: false
|
||||
example: 'Unknow user'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
status: failed
|
||||
error: 'Unknow user'
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":18568,"discord_user_id":42,"channel_id":null,"due_at":1732550400,"message":"Check maintance results!","status":"failed","error":"Unknow user"},"changes":{"status":{"old":"new","new":"failed"},"error":{"old":null,"new":"Unknow user"}}}'
|
||||
headers: []
|
||||
description: ''
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- DELETE
|
||||
uri: 'api/v1/discord-users/{discord_user_id}/remainders/{id}'
|
||||
metadata:
|
||||
groupName: 'Remainder Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage Remainders records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [Remainder](#remainder) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Remove the specified Remainder record.'
|
||||
description: 'Removes the specified [Remainder](#remainder) record with the provided parameters.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
discord_user_id:
|
||||
name: discord_user_id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
id:
|
||||
name: id
|
||||
description: '[Remainder](#remainder) ID.'
|
||||
required: true
|
||||
example: 18568
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
discord_user_id: 42
|
||||
id: 18568
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'The [snowflake](#snowflake) of the DiscordUser of the Remainder to delete.'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
snowflake: '481398158916845568'
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 204
|
||||
content: '{}'
|
||||
headers: []
|
||||
description: ''
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
206
src/.scribe/endpoints.cache/02.yaml
Normal file
206
src/.scribe/endpoints.cache/02.yaml
Normal file
@@ -0,0 +1,206 @@
|
||||
## Autogenerated by Scribe. DO NOT MODIFY.
|
||||
|
||||
name: 'Discord User By snowflake Managment'
|
||||
description: |-
|
||||
|
||||
APIs to manage DiscordUser records.
|
||||
|
||||
These endpoints can be used to identify/create DiscordUser records identified by the [snowflake](#snowflake) that already exists in the discord app.
|
||||
endpoints:
|
||||
-
|
||||
httpMethods:
|
||||
- GET
|
||||
uri: 'api/v1/discord-user-by-snowflake/{discord_user_snowflake}'
|
||||
metadata:
|
||||
groupName: 'Discord User By snowflake Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage DiscordUser records.
|
||||
|
||||
These endpoints can be used to identify/create DiscordUser records identified by the [snowflake](#snowflake) that already exists in the discord app.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Get the DiscordUser identified by the specified snowflake.'
|
||||
description: |-
|
||||
Returns the [DiscordUser](#discorduser) record for the specified [snowflake](#snowflake), given in the url __discord_user_snowflake__ parameter.
|
||||
|
||||
If it cannot be found, a [**404, Not Found**](#not-found-404) error is returned.
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
discord_user_snowflake:
|
||||
name: discord_user_snowflake
|
||||
description: 'A valid [snowflake](#snowflake).'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
discord_user_snowflake: '481398158916845568'
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters: []
|
||||
cleanBodyParameters: []
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/Budapest"}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
-
|
||||
status: 404
|
||||
content: '{"message":"Not Found."}'
|
||||
headers: []
|
||||
description: 'not found'
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- PUT
|
||||
uri: 'api/v1/discord-user-by-snowflake/{snowflake}'
|
||||
metadata:
|
||||
groupName: 'Discord User By snowflake Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage DiscordUser records.
|
||||
|
||||
These endpoints can be used to identify/create DiscordUser records identified by the [snowflake](#snowflake) that already exists in the discord app.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Get _OR_ Update/Create the DiscordUser identified by the specified snowflake.'
|
||||
description: |-
|
||||
If the record specified by the url __discord_user_snowflake__ parameter exists, it will be updated with the data provided in the body of the request.
|
||||
|
||||
If it does not exists, it will be created using the given data.
|
||||
|
||||
Returns the **updated/created** [DiscordUser](#discorduser) record.
|
||||
|
||||
If anything goes wrong, a [**422, Unprocessable Content**](#unprocessable-content-422) error with more details will be returned.
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'A valid [snowflake](#snowflake).'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
snowflake: '481398158916845568'
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'A valid [snowflake](#snowflake).'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
user_name:
|
||||
name: user_name
|
||||
description: 'The user_name registered in Discord.'
|
||||
required: false
|
||||
example: bigfootmcfly
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
global_name:
|
||||
name: global_name
|
||||
description: 'The global_name registered in Discord.'
|
||||
required: false
|
||||
example: 'BigFoot McFly'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
avatar:
|
||||
name: avatar
|
||||
description: 'The avatar url registered in Discord.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
locale:
|
||||
name: locale
|
||||
description: 'A valid [locale](#locale).'
|
||||
required: false
|
||||
example: en_GB
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
timezone:
|
||||
name: timezone
|
||||
description: 'A valid [time zone](#timezone).'
|
||||
required: false
|
||||
example: Europe/London
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
snowflake: '481398158916845568'
|
||||
user_name: bigfootmcfly
|
||||
global_name: 'BigFoot McFly'
|
||||
locale: en_GB
|
||||
timezone: Europe/London
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"en_GB","timezone":"Europe\/London"},"changes":{"locale":{"old":"hu_HU","new":"en_GB"},"timezone":{"old":"Europe\/Budapest","new":"Europe\/London"}}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
-
|
||||
status: 422
|
||||
content: '{"errors":{"snowflake":["The snowflake field is required."]}}'
|
||||
headers: []
|
||||
description: 'Unprocessable Content'
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
86
src/.scribe/endpoints.cache/03.yaml
Normal file
86
src/.scribe/endpoints.cache/03.yaml
Normal file
@@ -0,0 +1,86 @@
|
||||
## Autogenerated by Scribe. DO NOT MODIFY.
|
||||
|
||||
name: 'Remainder by DueAt Managment'
|
||||
description: |-
|
||||
|
||||
API to get Remainder records.
|
||||
|
||||
This endpoint can be used to Query the actual [Remainder](#remainder) records.
|
||||
endpoints:
|
||||
-
|
||||
httpMethods:
|
||||
- GET
|
||||
uri: 'api/v1/remainder-by-due-at/{due_at}'
|
||||
metadata:
|
||||
groupName: 'Remainder by DueAt Managment'
|
||||
groupDescription: |-
|
||||
|
||||
API to get Remainder records.
|
||||
|
||||
This endpoint can be used to Query the actual [Remainder](#remainder) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Returns all the "actual" reaminders for the given second.'
|
||||
description: ''
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
due_at:
|
||||
name: due_at
|
||||
description: 'The time ([timestamp](#timestamp)) of the requested remainders.'
|
||||
required: true
|
||||
example: 1735685999
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
due_at: 1735685999
|
||||
queryParameters:
|
||||
page_size:
|
||||
name: page_size
|
||||
description: 'Items per page. Defaults to 100.'
|
||||
required: false
|
||||
example: 25
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
page:
|
||||
name: page
|
||||
description: 'Page to query. Defaults to 1.'
|
||||
required: false
|
||||
example: 1
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanQueryParameters:
|
||||
page_size: 25
|
||||
page: 1
|
||||
bodyParameters: []
|
||||
cleanBodyParameters: []
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":[{"id":56,"discord_user_id":42,"channel_id":null,"due_at":1735685999,"message":"Update conatiner registry!","status":"new","error":null},{"id":192,"discord_user_id":47,"channel_id":null,"due_at":1735685999,"message":"Get some milk","status":"new","error":null}],"meta":{"current_page":1,"from":1,"last_page":1,"per_page":100,"to":2,"total":2}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
433
src/.scribe/endpoints/00.yaml
Normal file
433
src/.scribe/endpoints/00.yaml
Normal file
@@ -0,0 +1,433 @@
|
||||
name: 'Discord User Managment'
|
||||
description: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
endpoints:
|
||||
-
|
||||
httpMethods:
|
||||
- GET
|
||||
uri: api/v1/discord-users
|
||||
metadata:
|
||||
groupName: 'Discord User Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'List DiscorUsers.'
|
||||
description: 'Paginated list of [DiscordUser](#discorduser) records.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters: []
|
||||
cleanUrlParameters: []
|
||||
queryParameters:
|
||||
page_size:
|
||||
name: page_size
|
||||
description: 'Items per page. Defaults to 100.'
|
||||
required: false
|
||||
example: 25
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
page:
|
||||
name: page
|
||||
description: 'Page to query. Defaults to 1.'
|
||||
required: false
|
||||
example: 1
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanQueryParameters:
|
||||
page_size: 25
|
||||
page: 1
|
||||
bodyParameters: []
|
||||
cleanBodyParameters: []
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":[{"id":1,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe/Budapest"},{"id":6,"snowflake":"860046989130727450","user_name":"Teszt Elek","global_name":"holnap_is_teszt_elek","locale":"hu","timezone":"Europe/Budapest"},{"id":12,"snowflake":"112233445566778899","user_name":"Igaz Álmos","global_name":"almos#1244","locale":null,"timezone":null}],"meta":{"current_page":1,"from":1,"last_page":1,"per_page":10,"to":3,"total":3}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- POST
|
||||
uri: api/v1/discord-users
|
||||
metadata:
|
||||
groupName: 'Discord User Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Create a new DiscordUser record.'
|
||||
description: 'Creates a new [DiscordUser](#discorduser) record with the provided parameters.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters: []
|
||||
cleanUrlParameters: []
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'A valid [snowflake](#snowflake).'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
user_name:
|
||||
name: user_name
|
||||
description: 'The user_name registered in Discord.'
|
||||
required: false
|
||||
example: bigfootmcfly
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
global_name:
|
||||
name: global_name
|
||||
description: 'The global_name registered in Discord.'
|
||||
required: false
|
||||
example: 'BigFoot McFly'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
avatar:
|
||||
name: avatar
|
||||
description: 'The avatar url registered in Discord.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
locale:
|
||||
name: locale
|
||||
description: 'A valid [locale](#locale).'
|
||||
required: false
|
||||
example: hu_HU
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
timezone:
|
||||
name: timezone
|
||||
description: 'A valid [time zone](#timezone).'
|
||||
required: false
|
||||
example: Europe/Budapest
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
snowflake: '481398158916845568'
|
||||
user_name: bigfootmcfly
|
||||
global_name: 'BigFoot McFly'
|
||||
locale: hu_HU
|
||||
timezone: Europe/Budapest
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/Budapest"}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
-
|
||||
status: 422
|
||||
content: '{"errors":{"snowflake":["The snowflake has already been taken."]}}'
|
||||
headers: []
|
||||
description: 'Unprocessable Content'
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- GET
|
||||
uri: 'api/v1/discord-users/{id}'
|
||||
metadata:
|
||||
groupName: 'Discord User Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Get the specified DiscordUser record.'
|
||||
description: 'Returns the specified [DiscordUser](#discorduser) record.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
id:
|
||||
name: id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
id: 42
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters: []
|
||||
cleanBodyParameters: []
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/Budapest"}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- PUT
|
||||
- PATCH
|
||||
uri: 'api/v1/discord-users/{id}'
|
||||
metadata:
|
||||
groupName: 'Discord User Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Update the specified DiscordUser record.'
|
||||
description: 'Updates the specified [DiscordUser](#discorduser) with the supplied values.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
id:
|
||||
name: id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
id: 42
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'The snowflake of the [DiscordUser](#discorduser) to update.'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
user_name:
|
||||
name: user_name
|
||||
description: 'The user_name registered in Discord.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
global_name:
|
||||
name: global_name
|
||||
description: 'The global_name registered in Discord.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
avatar:
|
||||
name: avatar
|
||||
description: 'The avatar url registered in Discord.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
locale:
|
||||
name: locale
|
||||
description: 'A valid locale. <a href="https://github.com/Nerdtrix/language-list/blob/main/language-list-json.json" target="_blank">Locale list (json)</a>'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
timezone:
|
||||
name: timezone
|
||||
description: 'A valid [time zone](#timezone).'
|
||||
required: false
|
||||
example: Europe/London
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
snowflake: '481398158916845568'
|
||||
timezone: Europe/London
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/London"}}'
|
||||
headers: []
|
||||
description: ''
|
||||
custom: []
|
||||
-
|
||||
status: 422
|
||||
content: '{"errors":{"snowflake":["Invalid snowflake"]}}'
|
||||
headers: []
|
||||
description: 'Unprocessable Content'
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- DELETE
|
||||
uri: 'api/v1/discord-users/{id}'
|
||||
metadata:
|
||||
groupName: 'Discord User Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage [DiscordUser](#discorduser) records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Remove the specified DiscordUser record.'
|
||||
description: 'Removes the specified [DiscordUser](#discorduser) record **with** all the [Remainder](#remainder) records belonging to it.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
id:
|
||||
name: id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
id: 42
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'The snowflake of the [DiscordUser](#discorduser) to delete.'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
snowflake: '481398158916845568'
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 204
|
||||
content: '{}'
|
||||
headers: []
|
||||
description: ''
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
369
src/.scribe/endpoints/01.yaml
Normal file
369
src/.scribe/endpoints/01.yaml
Normal file
@@ -0,0 +1,369 @@
|
||||
name: 'Remainder Managment'
|
||||
description: |-
|
||||
|
||||
APIs to manage Remainders records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [Remainder](#remainder) records.
|
||||
endpoints:
|
||||
-
|
||||
httpMethods:
|
||||
- GET
|
||||
uri: 'api/v1/discord-users/{discord_user_id}/remainders'
|
||||
metadata:
|
||||
groupName: 'Remainder Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage Remainders records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [Remainder](#remainder) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'List of Remainder records.'
|
||||
description: 'Paginated list of [Remainder](#remainder) records belonging to the specified [DiscordUser](#discorduser).'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
discord_user_id:
|
||||
name: discord_user_id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
discord_user_id: 42
|
||||
queryParameters:
|
||||
page_size:
|
||||
name: page_size
|
||||
description: 'Items per page. Defaults to 100.'
|
||||
required: false
|
||||
example: 25
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
page:
|
||||
name: page
|
||||
description: 'Page to query. Defaults to 1.'
|
||||
required: false
|
||||
example: 1
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanQueryParameters:
|
||||
page_size: 25
|
||||
page: 1
|
||||
bodyParameters: []
|
||||
cleanBodyParameters: []
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":[{"id":38,"discord_user_id":42,"channel_id":null,"due_at":1736259300,"message":"Update todo list","status":"new","error":null},{"id":121,"discord_user_id":42,"channel_id":null,"due_at":1736259480,"message":"Water plants","status":"new","error":null}],"meta":{"current_page":1,"from":1,"last_page":1,"per_page":25,"to":2,"total":2}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- POST
|
||||
uri: 'api/v1/discord-users/{discord_user_id}/remainders'
|
||||
metadata:
|
||||
groupName: 'Remainder Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage Remainders records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [Remainder](#remainder) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Create a new Remainder record.'
|
||||
description: 'Creates a new [Remainder](#remainder) record with the provided parameters.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
discord_user_id:
|
||||
name: discord_user_id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
discord_user_id: 42
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
due_at:
|
||||
name: due_at
|
||||
description: 'The "Due at" time ([timestamp](#timestamp)) of the remainder'
|
||||
required: true
|
||||
example: '1732550400'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
message:
|
||||
name: message
|
||||
description: 'The message to send to the discord user.'
|
||||
required: true
|
||||
example: 'Check maintance results!'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
channel_id:
|
||||
name: channel_id
|
||||
description: 'The [snowflake](#snowflake) of the channel to send the remainder to.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
due_at: '1732550400'
|
||||
message: 'Check maintance results!'
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":18568,"discord_user_id":42,"channel_id":null,"due_at":1732550400,"message":"Check maintance results!","status":"new","error":null}}'
|
||||
headers: []
|
||||
description: ''
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- PUT
|
||||
uri: 'api/v1/discord-users/{discord_user_id}/remainders/{id}'
|
||||
metadata:
|
||||
groupName: 'Remainder Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage Remainders records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [Remainder](#remainder) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Update the specified Remainder record.'
|
||||
description: 'Updates the specified [Remainder](#remainder) record with the provided parameters.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
discord_user_id:
|
||||
name: discord_user_id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
id:
|
||||
name: id
|
||||
description: '[Remainder](#remainder) ID.'
|
||||
required: true
|
||||
example: 18568
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
discord_user_id: 42
|
||||
id: 18568
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
due_at:
|
||||
name: due_at
|
||||
description: 'The "Due at" time ([timestamp](#timestamp)) of the remainder.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
message:
|
||||
name: message
|
||||
description: 'The message to send to the discord user.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: false
|
||||
custom: []
|
||||
channel_id:
|
||||
name: channel_id
|
||||
description: 'The [snowflake](#snowflake) of the channel to send the remainder to.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
status:
|
||||
name: status
|
||||
description: |-
|
||||
Status of the [Remainder](#remainder).
|
||||
|
||||
For possible values see: [RemainderStatus](#remainderstatus)
|
||||
required: false
|
||||
example: failed
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
error:
|
||||
name: error
|
||||
description: 'Error description in case of failure.'
|
||||
required: false
|
||||
example: 'Unknow user'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
status: failed
|
||||
error: 'Unknow user'
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":18568,"discord_user_id":42,"channel_id":null,"due_at":1732550400,"message":"Check maintance results!","status":"failed","error":"Unknow user"},"changes":{"status":{"old":"new","new":"failed"},"error":{"old":null,"new":"Unknow user"}}}'
|
||||
headers: []
|
||||
description: ''
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- DELETE
|
||||
uri: 'api/v1/discord-users/{discord_user_id}/remainders/{id}'
|
||||
metadata:
|
||||
groupName: 'Remainder Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage Remainders records.
|
||||
|
||||
These endpoints can be used to Query/Update/Delete [Remainder](#remainder) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Remove the specified Remainder record.'
|
||||
description: 'Removes the specified [Remainder](#remainder) record with the provided parameters.'
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
discord_user_id:
|
||||
name: discord_user_id
|
||||
description: '[DiscordUser](#discorduser) ID.'
|
||||
required: true
|
||||
example: 42
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
id:
|
||||
name: id
|
||||
description: '[Remainder](#remainder) ID.'
|
||||
required: true
|
||||
example: 18568
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
discord_user_id: 42
|
||||
id: 18568
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'The [snowflake](#snowflake) of the DiscordUser of the Remainder to delete.'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
snowflake: '481398158916845568'
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 204
|
||||
content: '{}'
|
||||
headers: []
|
||||
description: ''
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
204
src/.scribe/endpoints/02.yaml
Normal file
204
src/.scribe/endpoints/02.yaml
Normal file
@@ -0,0 +1,204 @@
|
||||
name: 'Discord User By snowflake Managment'
|
||||
description: |-
|
||||
|
||||
APIs to manage DiscordUser records.
|
||||
|
||||
These endpoints can be used to identify/create DiscordUser records identified by the [snowflake](#snowflake) that already exists in the discord app.
|
||||
endpoints:
|
||||
-
|
||||
httpMethods:
|
||||
- GET
|
||||
uri: 'api/v1/discord-user-by-snowflake/{discord_user_snowflake}'
|
||||
metadata:
|
||||
groupName: 'Discord User By snowflake Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage DiscordUser records.
|
||||
|
||||
These endpoints can be used to identify/create DiscordUser records identified by the [snowflake](#snowflake) that already exists in the discord app.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Get the DiscordUser identified by the specified snowflake.'
|
||||
description: |-
|
||||
Returns the [DiscordUser](#discorduser) record for the specified [snowflake](#snowflake), given in the url __discord_user_snowflake__ parameter.
|
||||
|
||||
If it cannot be found, a [**404, Not Found**](#not-found-404) error is returned.
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
discord_user_snowflake:
|
||||
name: discord_user_snowflake
|
||||
description: 'A valid [snowflake](#snowflake).'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
discord_user_snowflake: '481398158916845568'
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters: []
|
||||
cleanBodyParameters: []
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/Budapest"}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
-
|
||||
status: 404
|
||||
content: '{"message":"Not Found."}'
|
||||
headers: []
|
||||
description: 'not found'
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
-
|
||||
httpMethods:
|
||||
- PUT
|
||||
uri: 'api/v1/discord-user-by-snowflake/{snowflake}'
|
||||
metadata:
|
||||
groupName: 'Discord User By snowflake Managment'
|
||||
groupDescription: |-
|
||||
|
||||
APIs to manage DiscordUser records.
|
||||
|
||||
These endpoints can be used to identify/create DiscordUser records identified by the [snowflake](#snowflake) that already exists in the discord app.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Get _OR_ Update/Create the DiscordUser identified by the specified snowflake.'
|
||||
description: |-
|
||||
If the record specified by the url __discord_user_snowflake__ parameter exists, it will be updated with the data provided in the body of the request.
|
||||
|
||||
If it does not exists, it will be created using the given data.
|
||||
|
||||
Returns the **updated/created** [DiscordUser](#discorduser) record.
|
||||
|
||||
If anything goes wrong, a [**422, Unprocessable Content**](#unprocessable-content-422) error with more details will be returned.
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'A valid [snowflake](#snowflake).'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
snowflake: '481398158916845568'
|
||||
queryParameters: []
|
||||
cleanQueryParameters: []
|
||||
bodyParameters:
|
||||
snowflake:
|
||||
name: snowflake
|
||||
description: 'A valid [snowflake](#snowflake).'
|
||||
required: true
|
||||
example: '481398158916845568'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
user_name:
|
||||
name: user_name
|
||||
description: 'The user_name registered in Discord.'
|
||||
required: false
|
||||
example: bigfootmcfly
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
global_name:
|
||||
name: global_name
|
||||
description: 'The global_name registered in Discord.'
|
||||
required: false
|
||||
example: 'BigFoot McFly'
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
avatar:
|
||||
name: avatar
|
||||
description: 'The avatar url registered in Discord.'
|
||||
required: false
|
||||
example: null
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: false
|
||||
nullable: true
|
||||
custom: []
|
||||
locale:
|
||||
name: locale
|
||||
description: 'A valid [locale](#locale).'
|
||||
required: false
|
||||
example: en_GB
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
timezone:
|
||||
name: timezone
|
||||
description: 'A valid [time zone](#timezone).'
|
||||
required: false
|
||||
example: Europe/London
|
||||
type: string
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: true
|
||||
custom: []
|
||||
cleanBodyParameters:
|
||||
snowflake: '481398158916845568'
|
||||
user_name: bigfootmcfly
|
||||
global_name: 'BigFoot McFly'
|
||||
locale: en_GB
|
||||
timezone: Europe/London
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"en_GB","timezone":"Europe\/London"},"changes":{"locale":{"old":"hu_HU","new":"en_GB"},"timezone":{"old":"Europe\/Budapest","new":"Europe\/London"}}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
-
|
||||
status: 422
|
||||
content: '{"errors":{"snowflake":["The snowflake field is required."]}}'
|
||||
headers: []
|
||||
description: 'Unprocessable Content'
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
84
src/.scribe/endpoints/03.yaml
Normal file
84
src/.scribe/endpoints/03.yaml
Normal file
@@ -0,0 +1,84 @@
|
||||
name: 'Remainder by DueAt Managment'
|
||||
description: |-
|
||||
|
||||
API to get Remainder records.
|
||||
|
||||
This endpoint can be used to Query the actual [Remainder](#remainder) records.
|
||||
endpoints:
|
||||
-
|
||||
httpMethods:
|
||||
- GET
|
||||
uri: 'api/v1/remainder-by-due-at/{due_at}'
|
||||
metadata:
|
||||
groupName: 'Remainder by DueAt Managment'
|
||||
groupDescription: |-
|
||||
|
||||
API to get Remainder records.
|
||||
|
||||
This endpoint can be used to Query the actual [Remainder](#remainder) records.
|
||||
subgroup: ''
|
||||
subgroupDescription: ''
|
||||
title: 'Returns all the "actual" reaminders for the given second.'
|
||||
description: ''
|
||||
authenticated: true
|
||||
custom: []
|
||||
headers:
|
||||
Authorization: 'Bearer {YOUR_AUTH_KEY}'
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
urlParameters:
|
||||
due_at:
|
||||
name: due_at
|
||||
description: 'The time ([timestamp](#timestamp)) of the requested remainders.'
|
||||
required: true
|
||||
example: 1735685999
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanUrlParameters:
|
||||
due_at: 1735685999
|
||||
queryParameters:
|
||||
page_size:
|
||||
name: page_size
|
||||
description: 'Items per page. Defaults to 100.'
|
||||
required: false
|
||||
example: 25
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
page:
|
||||
name: page
|
||||
description: 'Page to query. Defaults to 1.'
|
||||
required: false
|
||||
example: 1
|
||||
type: integer
|
||||
enumValues: []
|
||||
exampleWasSpecified: true
|
||||
nullable: false
|
||||
custom: []
|
||||
cleanQueryParameters:
|
||||
page_size: 25
|
||||
page: 1
|
||||
bodyParameters: []
|
||||
cleanBodyParameters: []
|
||||
fileParameters: []
|
||||
responses:
|
||||
-
|
||||
status: 200
|
||||
content: '{"data":[{"id":56,"discord_user_id":42,"channel_id":null,"due_at":1735685999,"message":"Update conatiner registry!","status":"new","error":null},{"id":192,"discord_user_id":47,"channel_id":null,"due_at":1735685999,"message":"Get some milk","status":"new","error":null}],"meta":{"current_page":1,"from":1,"last_page":1,"per_page":100,"to":2,"total":2}}'
|
||||
headers: []
|
||||
description: success
|
||||
custom: []
|
||||
responseFields: []
|
||||
auth:
|
||||
- headers
|
||||
- Authorization
|
||||
- 'Bearer DWg0pwbKhoC45vSEKiY7o2fqyuawN4F1yCC6bbiYee795197'
|
||||
controller: null
|
||||
method: null
|
||||
route: null
|
||||
custom: []
|
||||
53
src/.scribe/endpoints/custom.0.yaml
Normal file
53
src/.scribe/endpoints/custom.0.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
# To include an endpoint that isn't a part of your Laravel app (or belongs to a vendor package),
|
||||
# you can define it in a custom.*.yaml file, like this one.
|
||||
# Each custom file should contain an array of endpoints. Here's an example:
|
||||
# See https://scribe.knuckles.wtf/laravel/documenting/custom-endpoints#extra-sorting-groups-in-custom-endpoint-files for more options
|
||||
|
||||
#- httpMethods:
|
||||
# - POST
|
||||
# uri: api/doSomething/{param}
|
||||
# metadata:
|
||||
# groupName: The group the endpoint belongs to. Can be a new group or an existing group.
|
||||
# groupDescription: A description for the group. You don't need to set this for every endpoint; once is enough.
|
||||
# subgroup: You can add a subgroup, too.
|
||||
# title: Do something
|
||||
# description: 'This endpoint allows you to do something.'
|
||||
# authenticated: false
|
||||
# headers:
|
||||
# Content-Type: application/json
|
||||
# Accept: application/json
|
||||
# urlParameters:
|
||||
# param:
|
||||
# name: param
|
||||
# description: A URL param for no reason.
|
||||
# required: true
|
||||
# example: 2
|
||||
# type: integer
|
||||
# queryParameters:
|
||||
# speed:
|
||||
# name: speed
|
||||
# description: How fast the thing should be done. Can be `slow` or `fast`.
|
||||
# required: false
|
||||
# example: fast
|
||||
# type: string
|
||||
# bodyParameters:
|
||||
# something:
|
||||
# name: something
|
||||
# description: The things we should do.
|
||||
# required: true
|
||||
# example:
|
||||
# - string 1
|
||||
# - string 2
|
||||
# type: 'string[]'
|
||||
# responses:
|
||||
# - status: 200
|
||||
# description: 'When the thing was done smoothly.'
|
||||
# content: # Your response content can be an object, an array, a string or empty.
|
||||
# {
|
||||
# "hey": "ho ho ho"
|
||||
# }
|
||||
# responseFields:
|
||||
# hey:
|
||||
# name: hey
|
||||
# description: Who knows?
|
||||
# type: string # This is optional
|
||||
13
src/.scribe/intro.md
Normal file
13
src/.scribe/intro.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Introduction
|
||||
|
||||
Documentation for the Backend API.
|
||||
|
||||
<aside>
|
||||
<strong>Base URL</strong>: <code>localhost:9000</code>
|
||||
</aside>
|
||||
|
||||
This documentation aims to provide all the information you need to work with our API.
|
||||
|
||||
<aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
|
||||
You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>
|
||||
|
||||
87
src/README.md
Normal file
87
src/README.md
Normal file
@@ -0,0 +1,87 @@
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
## The source code of the backend.
|
||||
|
||||
This is just a brief list of the structure and functionality for the source tree, the default, mianly unchanged laravel filles will not be covered by this list.
|
||||
|
||||
The source files are well documented, fell free to read trough them.
|
||||
|
||||
- [app](app)<br>
|
||||
The laravel application.
|
||||
|
||||
- [Actions](app/Actions)<br>
|
||||
Custom actions used by the application.
|
||||
|
||||
- [Attributes](app/Attributes)<br>
|
||||
Custom attributes used by the application.
|
||||
|
||||
- [Enums](app/Enums)<br>
|
||||
Enums to ensure data integrity.
|
||||
|
||||
- [Filament](app/Filament)<br>
|
||||
The definitions for the admi UI.
|
||||
|
||||
- [Filament](app/Filament)<br>
|
||||
The definitions for the admi UI.
|
||||
|
||||
- [Helpers](app/Helpers)<br>
|
||||
Some helper functions for convenience.
|
||||
|
||||
- [Http/Controllers](app/Http/Controllers)<br>
|
||||
The controllers to handle all web/api requests
|
||||
|
||||
- [Http/Controllers](app/Http/Controllers)<br>
|
||||
The controllers to handle all web/api requests
|
||||
|
||||
- [Http/Controllers/Api/v1](app/Http/Controllers/Api/v1)<br>
|
||||
The controllers to handle the API requests
|
||||
|
||||
- [Http/Middleware](app/Http/Middleware)<br>
|
||||
The custom middleware to handle all web/api requests
|
||||
|
||||
- [HardenHeaders.php](app/Http/Middleware/HardenHeaders.php)<br>
|
||||
Hides some unnecessary informations from the bots.
|
||||
|
||||
- [StripPaginationInfo.php](app/Http/Middleware/StripPaginationInfo.php)<br>
|
||||
Removes web links from API paginated responses.
|
||||
|
||||
- [Http/Requests](app/Http/Requests)<br>
|
||||
Custom request classes to validate input data for all requests.
|
||||
|
||||
- [Http/Resources/Api/v1](app/Http/Resources/Api/v1)<br>
|
||||
Custom resource classes to return only the needed data for the API calls.
|
||||
|
||||
- [Http/Livewire](app/Http/Livewire)<br>
|
||||
One lonly custom Livewire component, that was missing from filament.
|
||||
|
||||
- [Http/Models](app/Http/Models)<br>
|
||||
The models used by the application.
|
||||
|
||||
- [Http/Providers](app/Http/Providers)<br>
|
||||
The service providers for the application.
|
||||
|
||||
- [Http/Traits](app/Http/Traits)<br>
|
||||
Common code used by multiple classes/enums.
|
||||
|
||||
- [Http/Validators/SnowflakeValidator.php](app/Http/ValidatorsSnowflakeValidator.php)<br>
|
||||
Minimalistic validator for discord snaowflake.
|
||||
|
||||
- [database](database)<br>
|
||||
Database migrations, factories, seeders.
|
||||
|
||||
- [public/docs](public/docs)<br>
|
||||
The documentation for the API, created by <a href="https://scribe.knuckles.wtf/laravel" target="_blank">Scribe</a><br>
|
||||
The full API documentation is available <a href="proxima.goliath.hu/docs" target="_blank">here</a>.
|
||||
|
||||
- [routes/api.php](routes/api.php)<br>
|
||||
The routes the API access.
|
||||
|
||||
- [routes/console.php](routes/console.php)<br>
|
||||
The custom inline commands for the application.
|
||||
|
||||
- [tests](tests)<br>
|
||||
Feature tests for the application.
|
||||
45
src/app/Actions/CreateRemainderAction.php
Normal file
45
src/app/Actions/CreateRemainderAction.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Enums\RemainderStatus;
|
||||
use App\Models\DiscordUser;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Http\Requests\Api\v1\StoreRemainderRequest;
|
||||
use App\Http\Resources\Api\v1\RemainderResource;
|
||||
use App\Models\Remainder;
|
||||
|
||||
/**
|
||||
* Creates a remainder
|
||||
*/
|
||||
class CreateRemainderAction
|
||||
{
|
||||
|
||||
/**
|
||||
* Creates a remainder with validated data
|
||||
*
|
||||
* @param StoreRemainderRequest $request The validated request
|
||||
* @param DiscordUser $discordUser The DiscordUser owner of the Remainder
|
||||
*
|
||||
* @return Response [201] Returns the created Remainder
|
||||
*
|
||||
*/
|
||||
public static function run(StoreRemainderRequest $request, DiscordUser $discordUser): Response
|
||||
{
|
||||
$remainder = Remainder::create(array_merge(
|
||||
$request->validated(),
|
||||
[
|
||||
'discord_user_id' => $discordUser->id,
|
||||
'status' => RemainderStatus::NEW->value,
|
||||
]
|
||||
));
|
||||
|
||||
return response(
|
||||
content: [
|
||||
'data' => RemainderResource::make($remainder),
|
||||
],
|
||||
status: 201
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
36
src/app/Actions/DeleteDiscordUserAction.php
Normal file
36
src/app/Actions/DeleteDiscordUserAction.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Models\DiscordUser;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Contracts\Routing\ResponseFactory;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
/**
|
||||
* Deletes the DiscordUser
|
||||
*
|
||||
* NOTE: only performs softdelete
|
||||
*/
|
||||
class DeleteDiscordUserAction
|
||||
{
|
||||
/**
|
||||
* Deletes the DiscordUser with all it's remainders
|
||||
*
|
||||
* @param DiscordUser $discordUser The DiscordUser to delete
|
||||
*
|
||||
* @return Response|ResponseFactory [204] Returns an empty response
|
||||
*
|
||||
*/
|
||||
public static function run(DiscordUser $discordUser): Response|ResponseFactory
|
||||
{
|
||||
Gate::authorize('delete', $discordUser);
|
||||
|
||||
$discordUser->remainders()->delete();
|
||||
|
||||
$discordUser->delete();
|
||||
|
||||
return response(status: 204);
|
||||
}
|
||||
|
||||
}
|
||||
32
src/app/Actions/DeleteRemainderAction.php
Normal file
32
src/app/Actions/DeleteRemainderAction.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Models\Remainder;
|
||||
use Illuminate\Contracts\Routing\ResponseFactory;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
/**
|
||||
* Deletes a Remainder
|
||||
*
|
||||
* NOTE: only performs softdelete
|
||||
*/
|
||||
class DeleteRemainderAction
|
||||
{
|
||||
|
||||
/**
|
||||
* Deleted the Remainder
|
||||
*
|
||||
* @param Remainder $remainder The Remainder to delete
|
||||
*
|
||||
* @return Response|ResponseFactory [204] Returns an empty response
|
||||
*
|
||||
*/
|
||||
public static function run(Remainder $remainder): Response|ResponseFactory
|
||||
{
|
||||
$remainder->delete();
|
||||
|
||||
return response(status: 204);
|
||||
}
|
||||
|
||||
}
|
||||
37
src/app/Actions/ForceDeleteDiscordUserAction.php
Normal file
37
src/app/Actions/ForceDeleteDiscordUserAction.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Models\DiscordUser;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Routing\ResponseFactory;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
/**
|
||||
* Deletes the DiscordUser
|
||||
*
|
||||
* NOTE: performs permanent delete
|
||||
*/
|
||||
class ForceDeleteDiscordUserAction
|
||||
{
|
||||
|
||||
/**
|
||||
* Deletes the DiscordUser with all it's remainders
|
||||
*
|
||||
* @param DiscordUser $discordUser The DiscordUser to delete
|
||||
*
|
||||
* @return Response|ResponseFactory [204] Returns an empty response
|
||||
*
|
||||
*/
|
||||
public static function run(DiscordUser $discordUser): Response|ResponseFactory
|
||||
{
|
||||
Gate::authorize('forcedelete', $discordUser);
|
||||
|
||||
$discordUser->allRemainders()->forceDelete();
|
||||
|
||||
$discordUser->permanentDelete();
|
||||
|
||||
return response(status: 204);
|
||||
}
|
||||
|
||||
}
|
||||
54
src/app/Actions/PruneOrphanedRemaindersAction.php
Normal file
54
src/app/Actions/PruneOrphanedRemaindersAction.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Enums\RemainderStatus;
|
||||
use App\Models\Remainder;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Removes orphaned Remainders
|
||||
*
|
||||
* Removes unhandled/crashed Remainders with overdue schedules.
|
||||
*
|
||||
* NOTE: performs permanent delete
|
||||
*/
|
||||
class PruneOrphanedRemaindersAction
|
||||
{
|
||||
/**
|
||||
* Invoke the class instance.
|
||||
*
|
||||
* @param bool $dryRun If true, only logging is performed, nothing will be deleted, otherwise matching Remainders will be deleted
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function __invoke(bool $dryRun = false): void
|
||||
{
|
||||
// how long should orphaned Remainders kept
|
||||
$daysToKeep = config('proxima.maintenance.keep_orphaned_remainders_for');
|
||||
|
||||
// get orphaned remainders
|
||||
$orphaned = Remainder::where('due_at', '<', Carbon::now()->subDays($daysToKeep))
|
||||
->whereIn('status', [
|
||||
RemainderStatus::NEW,
|
||||
RemainderStatus::PENDING,
|
||||
]);
|
||||
$orphanedCount = $orphaned->count();
|
||||
|
||||
// create message to log
|
||||
$logMessage = "{$orphanedCount} Remainder(s) are orphaned, ";
|
||||
$logMessage .= $dryRun ? "(DRY RUN) Keeping orphaned remainders." : "Pruning {$orphanedCount} orphaned remainders.";
|
||||
|
||||
// choose log level and log the message
|
||||
$logLevel = $orphanedCount > 0 ? 'warning' : 'info';
|
||||
Log::$logLevel($logMessage);
|
||||
|
||||
// remove orphaned remainders
|
||||
if (false === $dryRun) {
|
||||
$orphaned->forceDelete();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
48
src/app/Actions/RemoveDeletedDiscordUsersAction.php
Normal file
48
src/app/Actions/RemoveDeletedDiscordUsersAction.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Models\DiscordUser;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Removes finished Remainders
|
||||
*
|
||||
* Removes finished/cancelled/deleted Remainders after the keep period.
|
||||
*
|
||||
* NOTE: performs permanent delete
|
||||
*/
|
||||
class RemoveDeletedDiscordUsersAction
|
||||
{
|
||||
/**
|
||||
* Invoke the class instance.
|
||||
*
|
||||
* @param bool $dryRun If true, only logging is performed, nothing will be deleted, otherwise matching DiscordUsers will be deleted
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function __invoke(bool $dryRun = false): void
|
||||
{
|
||||
// how long should deleted DiscordUsers kept
|
||||
$daysToKeep = config('proxima.maintenance.keep_deleted_discord_users_for');
|
||||
|
||||
// get deleted DiscordUsers
|
||||
$deleted = DiscordUser::onlyTrashed()->where('deleted_at', '<', Carbon::now()->subDays($daysToKeep));
|
||||
$deletedCount = $deleted->count();
|
||||
|
||||
// create message to log
|
||||
$logMessage = "{$deletedCount} DiscordUsers(s) are deleted, ";
|
||||
$logMessage .= $dryRun ? "(DRY RUN) Keeping deleted DiscordUsers." : "Removing {$deletedCount} deleted DiscordUsers.";
|
||||
|
||||
// log the message
|
||||
Log::info($logMessage);
|
||||
|
||||
// remove deleted DiscordUsers
|
||||
if (false === $dryRun) {
|
||||
$deleted->each(fn (DiscordUser $discordUser) => $discordUser->permanentDelete());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
56
src/app/Actions/RemoveFinishedRemaindersAction.php
Normal file
56
src/app/Actions/RemoveFinishedRemaindersAction.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Enums\RemainderStatus;
|
||||
use App\Models\Remainder;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Removes finished Remainders
|
||||
*
|
||||
* Removes finished/cancelled/deleted Remainders after the keep period.
|
||||
*
|
||||
* NOTE: performs permanent delete
|
||||
*/
|
||||
class RemoveFinishedRemaindersAction
|
||||
{
|
||||
|
||||
/**
|
||||
* Invoke the class instance.
|
||||
*
|
||||
* @param bool $dryRun If true, only logging is performed, nothing will be deleted, otherwise matching Remainders will be deleted
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function __invoke(bool $dryRun = false): void
|
||||
{
|
||||
// how long should finished remainders kept
|
||||
$daysToKeep = config('proxima.maintenance.keep_finished_remainders_for');
|
||||
|
||||
// get finished remainders
|
||||
$finished = Remainder::withTrashed()->where('due_at', '<', Carbon::now()->subDays($daysToKeep))
|
||||
->whereIn('status', [
|
||||
RemainderStatus::FAILED,
|
||||
RemainderStatus::FINISHED,
|
||||
RemainderStatus::CANCELLED,
|
||||
RemainderStatus::DELETED,
|
||||
]);
|
||||
$finishedCount = $finished->count();
|
||||
|
||||
// create message to log
|
||||
$logMessage = "{$finishedCount} Remainder(s) are finished, ";
|
||||
$logMessage .= $dryRun ? "(DRY RUN) Keeping finished remainders." : "Removing {$finishedCount} finished remainders.";
|
||||
|
||||
// log the message
|
||||
Log::info($logMessage);
|
||||
|
||||
// remove finished remainders
|
||||
if (false === $dryRun) {
|
||||
$finished->forceDelete();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
43
src/app/Actions/RestoreDiscordUserAction.php
Normal file
43
src/app/Actions/RestoreDiscordUserAction.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Http\Resources\Api\v1\DiscordUserResource;
|
||||
use App\Models\DiscordUser;
|
||||
use Illuminate\Contracts\Routing\ResponseFactory;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
/**
|
||||
* Restores a trashed DiscordUser
|
||||
*/
|
||||
class RestoreDiscordUserAction
|
||||
{
|
||||
|
||||
/**
|
||||
* Restores the trashed DiscordUser
|
||||
*
|
||||
* @param DiscordUser $discordUser The DiscordUser to restore
|
||||
*
|
||||
* @return ResponseFactory|Response [200] The DiscordUser
|
||||
*
|
||||
*/
|
||||
public static function run(DiscordUser $discordUser): ResponseFactory|Response
|
||||
{
|
||||
Gate::authorize('restore', $discordUser);
|
||||
|
||||
$trashedRemainders = $discordUser->remainders()->onlyTrashed();
|
||||
|
||||
$trashedRemainders->restore();
|
||||
|
||||
$discordUser->restore();
|
||||
|
||||
return response(
|
||||
status: 200,
|
||||
content: [
|
||||
'data' => DiscordUserResource::make($discordUser),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
11
src/app/Attributes/Description.php
Normal file
11
src/app/Attributes/Description.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Attributes;
|
||||
|
||||
#[\Attribute]
|
||||
class Description
|
||||
{
|
||||
public function __construct(
|
||||
public string $description,
|
||||
) {}
|
||||
}
|
||||
26
src/app/Enums/ApiPermission.php
Normal file
26
src/app/Enums/ApiPermission.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Attributes\Description;
|
||||
use App\Traits\BackedEnumHelper;
|
||||
use App\Traits\HasEnumDescription;
|
||||
|
||||
enum ApiPermission: string
|
||||
{
|
||||
use BackedEnumHelper;
|
||||
use HasEnumDescription;
|
||||
|
||||
#[Description('Handle discord users')]
|
||||
case ManageDiscordUsers = 'discord-users';
|
||||
|
||||
#[Description('Handle Remainders')]
|
||||
case ManageDiscordUserRemainders = 'discord-user-remainders';
|
||||
|
||||
#[Description('Get Discord User By Snowflake')]
|
||||
case ManageDiscordUserBySnowflake = 'discord-user-by-snowflake';
|
||||
|
||||
#[Description('Get Remainders By Due At')]
|
||||
case GetRemaindersByDueAt = 'remainders-by-due-at';
|
||||
|
||||
}
|
||||
17
src/app/Enums/RemainderStatus.php
Normal file
17
src/app/Enums/RemainderStatus.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Traits\BackedEnumHelper;
|
||||
|
||||
enum RemainderStatus: string
|
||||
{
|
||||
use BackedEnumHelper;
|
||||
|
||||
case NEW = 'new';
|
||||
case FAILED = 'failed';
|
||||
case PENDING = 'pending';
|
||||
case DELETED = 'deleted';
|
||||
case FINISHED = 'finished';
|
||||
case CANCELLED = 'cancelled';
|
||||
}
|
||||
34
src/app/Filament/Actions/InfoAction.php
Normal file
34
src/app/Filament/Actions/InfoAction.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Actions;
|
||||
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
/**
|
||||
* Modal ViewAction
|
||||
*/
|
||||
class InfoAction
|
||||
{
|
||||
|
||||
/**
|
||||
* Creates a modal 'info' view action
|
||||
*
|
||||
* @return ViewAction
|
||||
*
|
||||
*/
|
||||
public static function make(): ViewAction
|
||||
{
|
||||
return ViewAction::make('info')
|
||||
->label('')
|
||||
->icon('heroicon-o-information-circle')
|
||||
->iconSize(IconSize::Small)
|
||||
->iconButton()
|
||||
->color('warning')
|
||||
->extraAttributes(
|
||||
[
|
||||
'title' => 'Info',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
149
src/app/Filament/Resources/DiscordUserResource.php
Normal file
149
src/app/Filament/Resources/DiscordUserResource.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\DiscordUserResource\Pages;
|
||||
use App\Filament\Resources\DiscordUserResource\Pages\ViewDiscordUser;
|
||||
use App\Filament\Resources\DiscordUserResource\RelationManagers\RemaindersRelationManager;
|
||||
use App\Filament\Resources\DiscordUserResource\Services\DiscordUserResourceForm;
|
||||
use App\Filament\Resources\DiscordUserResource\Services\DiscordUserResourceInfoList;
|
||||
use App\Filament\Resources\DiscordUserResource\Services\DiscordUserResourceTable;
|
||||
use App\Models\DiscordUser;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\GlobalSearch\Actions\Action;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class DiscordUserResource extends Resource
|
||||
{
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
protected static ?string $model = DiscordUser::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-users';
|
||||
|
||||
protected static ?string $navigationGroup = 'Backend';
|
||||
|
||||
protected static ?string $navigationBadgeTooltip = 'Total Discord Users count';
|
||||
|
||||
|
||||
public static function getGlobalSearchResultActions(Model $record): array
|
||||
{
|
||||
return [
|
||||
Action::make('View')
|
||||
->url(ViewDiscordUser::getUrl(['record' => $record])),
|
||||
Action::make('Remainders')
|
||||
->url(DiscordUserResourceTable::getRemainderActionUrl($record)),
|
||||
Action::make('edit')
|
||||
->url(static::getUrl('edit', ['record' => $record]), shouldOpenInNewTab: false),
|
||||
];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getGlobalSearchResultTitle(Model $record): string|Htmlable
|
||||
{
|
||||
return "{$record->global_name} ({$record->user_name})";
|
||||
}
|
||||
|
||||
public static function getGlobalSearchResultUrl(Model $record): string
|
||||
{
|
||||
return DiscordUserResource::getUrl('show', ['record' => $record]);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getGloballySearchableAttributes(): array
|
||||
{
|
||||
return [
|
||||
'user_name',
|
||||
'global_name',
|
||||
'snowflake',
|
||||
];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getGlobalSearchResultDetails(Model $record): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
if ($record->trashed()) {
|
||||
$result['deleted'] = $record->deleted_at;
|
||||
}
|
||||
$result['remainders'] = $record->remainders_count;
|
||||
return $result;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getGlobalSearchEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getGlobalSearchEloquentQuery()->withCount('remainders');
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function infolist(Infolist $infolist): Infolist
|
||||
{
|
||||
return DiscordUserResourceInfoList::infolist($infolist);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return DiscordUserResourceForm::form($form);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return DiscordUserResourceTable::table($table);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
RemaindersRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListDiscordUsers::route('/'),
|
||||
'show' => Pages\ViewDiscordUser::route('/{record}'), // replaces default view
|
||||
'edit' => Pages\EditDiscordUser::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getEloquentQuery()
|
||||
->withoutGlobalScopes([
|
||||
SoftDeletingScope::class,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
//NOTE: this gets called twice per page load, (see calls bellow), so we cache it for better performance
|
||||
// GET /admin/discord-users
|
||||
// POST /livewire/update (ajax)
|
||||
//NOTE: this could be cached for a longer time, but then the cache had to be invalidated manually on DU count change...
|
||||
|
||||
return Number::format(
|
||||
cache()->remember(
|
||||
key: 'DiscordUserResourceBadgeCount',
|
||||
ttl: 10,
|
||||
callback: fn () => static::getModel()::count()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DiscordUserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\DiscordUserResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateDiscordUser extends CreateRecord
|
||||
{
|
||||
protected static string $resource = DiscordUserResource::class;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DiscordUserResource\Pages;
|
||||
|
||||
use App\Actions\DeleteDiscordUserAction;
|
||||
use App\Actions\ForceDeleteDiscordUserAction;
|
||||
use App\Actions\RestoreDiscordUserAction;
|
||||
use App\Filament\Resources\DiscordUserResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use App\Models\DiscordUser;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
use Filament\Actions\RestoreAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
|
||||
class EditDiscordUser extends EditRecord
|
||||
{
|
||||
protected static string $resource = DiscordUserResource::class;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Restores trashed Discord User Action
|
||||
*/
|
||||
public static function restoreAction(Page $page): Action
|
||||
{
|
||||
$trashedRemainderCount = $page->record->trashedRemainderCount;
|
||||
return RestoreAction::make()
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Are you sure you\'d like to restore this discord user and all it\'s remainders?')
|
||||
->modalIcon('heroicon-o-arrow-path')
|
||||
->badge($trashedRemainderCount)
|
||||
->badgeColor(
|
||||
fn () => match ($trashedRemainderCount) {
|
||||
0 => 'gray',
|
||||
default => 'warning'
|
||||
}
|
||||
)
|
||||
->action(function (DiscordUser $discordUser) use ($page) {
|
||||
RestoreDiscordUserAction::run($discordUser);
|
||||
Notification::make()
|
||||
->title("Discord user \"{$discordUser->user_name}\" restored.")
|
||||
->success()
|
||||
->send();
|
||||
cache()->forget("DiscordUserRemainderCount_{$discordUser->id}");
|
||||
$page->redirect(EditDiscordUser::getUrl(['record' => $discordUser]));
|
||||
});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Delete Discord User Action
|
||||
*
|
||||
* NOTE: only performs softdelete
|
||||
*/
|
||||
public static function deleteAction(Page $page): Action
|
||||
{
|
||||
return Action::make('delete')
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Are you sure you\'d like to delete this discord user and all it\'s remainders?')
|
||||
->modalIcon('heroicon-o-trash')
|
||||
->label('Delete')
|
||||
->color('danger')
|
||||
->badge($page->record->RemainderCount)
|
||||
->badgeColor(
|
||||
fn () => match ($page->record->RemainderCount) {
|
||||
0 => 'gray',
|
||||
default => 'warning'
|
||||
}
|
||||
)
|
||||
->action(function (DiscordUser $discordUser) use ($page) {
|
||||
DeleteDiscordUserAction::run($discordUser);
|
||||
Notification::make()
|
||||
->title('Discord user "'.$page->record->user_name.'" deleted.')
|
||||
->success()
|
||||
->send();
|
||||
cache()->forget('DiscordUserTrashedRemainderCount_'.$discordUser->id);
|
||||
$page->redirect(EditDiscordUser::getUrl(['record' => $discordUser]));
|
||||
})
|
||||
->hidden(fn (DiscordUser $discordUser) => $discordUser->trashed());
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Delete Discord User Action
|
||||
*
|
||||
* NOTE: performs permanent delete
|
||||
*/
|
||||
public static function forceDeleteAction(Page $page): Action
|
||||
{
|
||||
return ForceDeleteAction::make()
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Are you sure you\'d like to PERMANENTLY delete this discord user and all it\'s remainders?')
|
||||
->modalIcon('heroicon-o-trash')
|
||||
->color('danger')
|
||||
->badge($page->record->allRemainderCount)
|
||||
->badgeColor(
|
||||
fn () => match ($page->record->remainders_count) {
|
||||
0 => 'gray',
|
||||
default => 'warning'
|
||||
}
|
||||
)
|
||||
->action(function (DiscordUser $discordUser) use ($page) {
|
||||
ForceDeleteDiscordUserAction::run($discordUser);
|
||||
Notification::make()
|
||||
->title("Discord user \"{$discordUser->global_name}\" deleted.")
|
||||
->success()
|
||||
->send();
|
||||
$page->redirect(ListDiscordUsers::getUrl());
|
||||
});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
self::deleteAction($this),
|
||||
self::forceDeleteAction($this),
|
||||
self::restoreAction($this),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DiscordUserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\DiscordUserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListDiscordUsers extends ListRecords
|
||||
{
|
||||
protected static string $resource = DiscordUserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DiscordUserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\DiscordUserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewDiscordUser extends ViewRecord
|
||||
{
|
||||
protected static string $resource = DiscordUserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\EditAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DiscordUserResource\RelationManagers;
|
||||
|
||||
use App\Filament\Resources\RemainderResource;
|
||||
use App\Filament\Resources\RemainderResource\Sections\RemainderResourceInfoList;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class RemaindersRelationManager extends RelationManager
|
||||
{
|
||||
|
||||
protected static ?string $icon = 'heroicon-o-calendar';
|
||||
|
||||
protected static ?string $iconColor = 'red';
|
||||
|
||||
protected static string $relationship = 'remainders';
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return RemainderResource::form($form);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
// add custom icon to the related section to match the design of the main section
|
||||
$heading = new HtmlString(
|
||||
html: view(
|
||||
view: 'filament.admin.relation-manager-section-icon',
|
||||
data: [
|
||||
'icon' => self::$icon,
|
||||
'content' => $table->getheading(),
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
return RemainderResource::table($table)
|
||||
->description('The remainders of the discord user.')
|
||||
->heading($heading)
|
||||
;
|
||||
}
|
||||
|
||||
public function applyFiltersToTableQuery(Builder $query): Builder
|
||||
{
|
||||
// return the record even if it is trashed
|
||||
if ($this->ownerRecord->trashed()) {
|
||||
return $query->withTrashed();
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function infolist(Infolist $infolist): Infolist
|
||||
{
|
||||
// return our own infolist instead of the default one
|
||||
return RemainderResourceInfoList::infolist($infolist);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DiscordUserResource\Services;
|
||||
|
||||
use App\Http\Requests\Api\v1\StoreDiscordUserRequest;
|
||||
use App\Http\Requests\Api\v1\UpdateDiscordUserRequest;
|
||||
use App\Models\DiscordUser;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms;
|
||||
use Tapp\FilamentTimezoneField\Forms\Components\TimezoneSelect;
|
||||
|
||||
final class DiscordUserResourceForm
|
||||
{
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
|
||||
$rules = match ($form->getOperation()) {
|
||||
'edit' => UpdateDiscordUserRequest::asFilamentRules(),
|
||||
'create' => StoreDiscordUserRequest::asFilamentRules(),
|
||||
default => [],
|
||||
};
|
||||
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\Placeholder::make('trashed')
|
||||
->columnSpan(2)
|
||||
->view('filament.admin.trashed')
|
||||
->visible(fn (DiscordUser $discordUser) => $discordUser->trashed()),
|
||||
Forms\Components\TextInput::make('snowflake')
|
||||
->rules($rules['snowflake'] ?? [])
|
||||
->visibleOn('create')
|
||||
->disabledOn('edit'),
|
||||
Forms\Components\Placeholder::make('snowflake')
|
||||
->content(fn (DiscordUser $discordUser): string => $discordUser->snowflake)
|
||||
->hiddenOn(['create']),
|
||||
Forms\Components\TextInput::make('user_name')
|
||||
->rules($rules['user_name'] ?? []),
|
||||
Forms\Components\TextInput::make('global_name')
|
||||
->rules($rules['global_name'] ?? []),
|
||||
Forms\Components\TextInput::make('avatar')
|
||||
->rules($rules['avatar'] ?? []),
|
||||
Forms\Components\Select::make('locale')
|
||||
->rules($rules['locale'] ?? [])
|
||||
->options(array_combine(LOCALES, LOCALES)),
|
||||
TimezoneSelect::make('timezone')
|
||||
->searchable(),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DiscordUserResource\Services;
|
||||
|
||||
use App\Models\DiscordUser;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Infolists\Components\Section;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
|
||||
final class DiscordUserResourceInfoList
|
||||
{
|
||||
|
||||
public static function infolist(Infolist $infolist): Infolist
|
||||
{
|
||||
return $infolist
|
||||
->schema([
|
||||
Section::make('Trashed')
|
||||
->schema([
|
||||
TextEntry::make('trashed')
|
||||
->columnSpan(2)
|
||||
->view('filament.admin.trashed')
|
||||
->extraAttributes(['margin-bottom' => '1em;']),
|
||||
])
|
||||
->visible(fn (DiscordUser $discordUser) => $discordUser->trashed()),
|
||||
Section::make('Discrod User')
|
||||
->icon('heroicon-o-users')
|
||||
->iconColor('info')
|
||||
->description('The common data of the discord user.')
|
||||
->columns(2)
|
||||
->compact()
|
||||
->schema([
|
||||
TextEntry::make('user_name')->label('User name'),
|
||||
TextEntry::make('global_name')->label('Global name'),
|
||||
TextEntry::make('snowflake')->label('Snowflake'),
|
||||
TextEntry::make('remainders.count')
|
||||
->label('Remainders')
|
||||
->state(fn (DiscordUser $discordUser): int => $discordUser->remainders()->count()),
|
||||
TextEntry::make('locale')->label('Locale'),
|
||||
TextEntry::make('timezone')->label('Timezone'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DiscordUserResource\Services;
|
||||
|
||||
use App\Filament\Actions\InfoAction;
|
||||
use App\Models\DiscordUser;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
final class DiscordUserResourceTable
|
||||
{
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the RemainderResource::ListRemainders view filtered by the DiscordUser
|
||||
*
|
||||
* @param DiscordUser $discordUser The DiscordUser to view
|
||||
*
|
||||
* @return string The route for the view
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function getRemainderActionUrl(DiscordUser $discordUser): string
|
||||
{
|
||||
return route('filament.admin.resources.remainders.index', [
|
||||
'tableFilters' => [
|
||||
'discord_user_id' =>
|
||||
['value' => $discordUser->id],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Creates an Action to navigate to RemainderResource::ListRemainders view filtered by the DiscordUser
|
||||
*
|
||||
* @return Action The action to navigate to the view
|
||||
*
|
||||
*/
|
||||
public static function createRemainderAction(): Action
|
||||
{
|
||||
return Action::make('Remainders')
|
||||
->label('')
|
||||
->extraAttributes(['title' => 'Remainders'])
|
||||
->icon('heroicon-o-calendar')
|
||||
->url(self::getRemainderActionUrl(...));
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the RemainderResource::ViewRemainder view
|
||||
*
|
||||
* @param DiscordUser $discordUser The DiscordUser to view
|
||||
*
|
||||
* @return string The route for the view
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function getViewActionUrl(DiscordUser $discordUser): string
|
||||
{
|
||||
return route('filament.admin.resources.discord-users.show', [
|
||||
'record' => $discordUser,
|
||||
]);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Creates an Action to navigate to RemainderResource::ViewRemainder view
|
||||
*
|
||||
* @return Action The action to navigate to the view
|
||||
*
|
||||
*/
|
||||
public static function createViewAction(): Action
|
||||
{
|
||||
return Action::make('View')
|
||||
->label('')
|
||||
->extraAttributes(['title' => 'View'])
|
||||
->icon('heroicon-o-eye')
|
||||
->url(self::getViewActionUrl(...));
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the classes of a colummn based of the Remainders state
|
||||
*
|
||||
* @param DiscordUser $record The Remainder to get the class for (Injected by filament)
|
||||
*
|
||||
* @return string The class(es) based on the state of the Remainder
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function getRecordClass(DiscordUser $record): string
|
||||
{
|
||||
if ($record->trashed()) {
|
||||
return 'border-l-4 !border-l-danger-500 line-through !decoration-danger-700 !text-amber-700';
|
||||
}
|
||||
|
||||
return 'border-l-4 !border-l-success-500 !text-yellow-400';
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Deletes multiple Discord Users Action
|
||||
*
|
||||
* NOTE: performs permanent delete
|
||||
*/
|
||||
public static function forceDeleteBulkAction(): ForceDeleteBulkAction
|
||||
{
|
||||
$action = ForceDeleteBulkAction::make()
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Are you sure you\'d like to PERMANENTLY delete this discord user(s) and all it\'s remainders?')
|
||||
->modalIcon('heroicon-o-trash')
|
||||
->color('danger');
|
||||
|
||||
$action->action(function () use ($action): void {
|
||||
$action->process(static function (Collection $records): void {
|
||||
$records->each(fn (DiscordUser $record) => $record->permanentDelete());
|
||||
});
|
||||
$action->success();
|
||||
});
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordClasses(self::getRecordClass(...))
|
||||
->columns([
|
||||
TextColumn::make('snowflake')
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('user_name')
|
||||
->searchable(),
|
||||
TextColumn::make('global_name')
|
||||
->searchable(),
|
||||
TextColumn::make('remainders_count')
|
||||
->label('Remainders')
|
||||
->badge()
|
||||
->counts('remainders')
|
||||
->sortable(),
|
||||
TextColumn::make('locale')
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('timezone')
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('deleted_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\TrashedFilter::make(),
|
||||
])
|
||||
->actions([
|
||||
InfoAction::make(),
|
||||
self::createViewAction(),
|
||||
self::createRemainderAction(),
|
||||
Tables\Actions\EditAction::make()
|
||||
->label('')
|
||||
->extraAttributes(['title' => 'Edit']),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
self::forceDeleteBulkAction(),
|
||||
Tables\Actions\RestoreBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
159
src/app/Filament/Resources/RemainderResource.php
Normal file
159
src/app/Filament/Resources/RemainderResource.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\RemainderResource\Pages;
|
||||
use App\Filament\Resources\RemainderResource\Sections\RemainderResourceForm;
|
||||
use App\Filament\Resources\RemainderResource\Sections\RemainderResourceInfoList;
|
||||
use App\Filament\Resources\RemainderResource\Sections\RemainderResourceTable;
|
||||
use App\Models\Remainder;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\GlobalSearch\Actions\Action;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class RemainderResource extends Resource
|
||||
{
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
protected static ?string $model = Remainder::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-calendar';
|
||||
|
||||
protected static ?string $navigationGroup = 'Backend';
|
||||
|
||||
protected static ?string $navigationBadgeTooltip = 'Total Remainders count';
|
||||
|
||||
public static function getGlobalSearchResultActions(Model $record): array
|
||||
{
|
||||
// identify record
|
||||
$parameters = [
|
||||
'tableActionRecord' => $record->id,
|
||||
];
|
||||
|
||||
// if trashed, set filter on table to include deleted items
|
||||
if ($record->trashed()) {
|
||||
$parameters['tableFilters'] = [
|
||||
'trashed' => [
|
||||
'value' => 1,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// return the actions
|
||||
return [
|
||||
Action::make('info')
|
||||
->url(route('filament.admin.resources.remainders.index', $parameters + [
|
||||
'tableAction' => 'info',
|
||||
])),
|
||||
Action::make('edit')
|
||||
->url(route('filament.admin.resources.remainders.index', $parameters + [
|
||||
'tableAction' => 'edit',
|
||||
])),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getGlobalSearchResultTitle(Model $record): string|Htmlable
|
||||
{
|
||||
return $record->message;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getGloballySearchableAttributes(): array
|
||||
{
|
||||
return [
|
||||
'message',
|
||||
'channel_id',
|
||||
];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getGlobalSearchResultDetails(Model $record): array
|
||||
{
|
||||
return [
|
||||
'user' => $record->discordUser->global_name,
|
||||
'due at' => $record->due_at,
|
||||
'status' => $record->status,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function infolist(Infolist $infolist): Infolist
|
||||
{
|
||||
return RemainderResourceInfoList::infolist($infolist);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return RemainderResourceForm::form($form);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return RemainderResourceTable::table($table);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListRemainders::route('/'),
|
||||
//'create' => Pages\CreateRemainder::route('/create'),
|
||||
//'view' => Pages\ViewRemainder::route('/{record}'),
|
||||
//'edit' => Pages\EditRemainder::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getEloquentQuery()
|
||||
->with('discordUser')
|
||||
->withoutGlobalScopes([
|
||||
SoftDeletingScope::class,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
//NOTE: this gets called twice, (see calls bellow), we cache it for better performance
|
||||
// GET /admin/discord-users
|
||||
// POST /livewire/update (ajax)
|
||||
|
||||
return Number::format(
|
||||
cache()->remember(
|
||||
key: 'RemainderResourceBadgeCount',
|
||||
ttl: 10,
|
||||
callback: fn () => static::getModel()::count()
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getNavigationBadgeColor(): ?string
|
||||
{
|
||||
return 'primary';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\RemainderResource\Pages;
|
||||
|
||||
use App\Filament\Resources\RemainderResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateRemainder extends CreateRecord
|
||||
{
|
||||
protected static string $resource = RemainderResource::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\RemainderResource\Pages;
|
||||
|
||||
use App\Filament\Resources\RemainderResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditRemainder extends EditRecord
|
||||
{
|
||||
|
||||
protected static string $resource = RemainderResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\ViewAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\ForceDeleteAction::make(),
|
||||
Actions\RestoreAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\RemainderResource\Pages;
|
||||
|
||||
use App\Filament\Resources\RemainderResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Components\Tab;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Concerns\HasFilters;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class ListRemainders extends ListRecords
|
||||
{
|
||||
use HasFilters;
|
||||
|
||||
protected static string $resource = RemainderResource::class;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public function getTabs(): array
|
||||
{
|
||||
$intervals = [
|
||||
'all' => [
|
||||
now()->subMillenniaNoOverflow()->startOfMillennium(),
|
||||
now()->addMillenniaNoOverflow()->endOfMillennium(),
|
||||
],
|
||||
'today' => [
|
||||
now()->startOfDay(),
|
||||
now()->endOfDay(),
|
||||
],
|
||||
'this_week' => [ //NOTE: does not includes previous dates
|
||||
now()->endOfDay(),
|
||||
now()->endOfWeek(),
|
||||
],
|
||||
'this_month' => [ //NOTE: does not includes previous dates
|
||||
now()->endOfWeek(),
|
||||
now()->endOfMonth(),
|
||||
],
|
||||
'this_year' => [ //NOTE: does not includes previous dates
|
||||
now()->endOfMonth(),
|
||||
now()->endOfYear(),
|
||||
],
|
||||
'later' => [ //NOTE: does not includes previous dates
|
||||
now()->endOfYear(),
|
||||
now()->addMillenniaNoOverflow()->endOfMillennium(),
|
||||
],
|
||||
'overdue' => [
|
||||
now()->subMillenniaNoOverflow()->startOfMillennium(),
|
||||
now()->startOfHour(), //NOTE: no need for higher precision for now
|
||||
],
|
||||
];
|
||||
|
||||
$tabs = array_map($this->intervalTab(...), $intervals);
|
||||
$tabs['overdue']->badgeColor('danger');
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Creates a filter tab for the table
|
||||
*
|
||||
* @param array $interval The interval to filter Example: [min:max]
|
||||
*
|
||||
* @return Tab The filter Tab
|
||||
*
|
||||
*/
|
||||
protected function intervalTab(array $interval): Tab
|
||||
{
|
||||
return Tab::make()
|
||||
->modifyQueryUsing(
|
||||
fn (Builder $query): Builder =>
|
||||
$query->whereBetween('due_at', $interval)
|
||||
)
|
||||
->badge(function () use ($interval) {
|
||||
$query = $this
|
||||
->applyFiltersToTableQuery(static::getResource()::getEloquentQuery())
|
||||
->whereBetween('due_at', $interval)
|
||||
;
|
||||
|
||||
//NOTE: cache is mainly used to get rid of duplicate queries by filament
|
||||
return cache()->remember(
|
||||
key: 'badge_'.crc32($query->toRawSql()),
|
||||
ttl: 5,
|
||||
callback: fn () => Number::format($query->count())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\RemainderResource\Pages;
|
||||
|
||||
use App\Filament\Resources\RemainderResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewRemainder extends ViewRecord
|
||||
{
|
||||
protected static string $resource = RemainderResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\EditAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\RemainderResource\Sections;
|
||||
|
||||
use App\Enums\RemainderStatus;
|
||||
use App\Filament\Resources\RemainderResource\Pages\ListRemainders;
|
||||
use App\Http\Requests\Api\v1\StoreRemainderRequest;
|
||||
use App\Models\Remainder;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
final class RemainderResourceForm
|
||||
{
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the DiscordUser ID from the current table filter
|
||||
*
|
||||
* @param HasTable $livewire The current livewire componennt (automatically injected by filament)
|
||||
*
|
||||
* @return int|null The ID of the DiscordUser if the filtering is used, null otherwise
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function getFilteredDiscordUserId(HasTable $livewire): ?int
|
||||
{
|
||||
return $livewire->getTableFilterState('discord_user_id')['value'];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Checks if the table is filtered by DiscordUser
|
||||
*
|
||||
* @param HasTable $livewire The current livewire componennt (automatically injected by filament)
|
||||
*
|
||||
* @return bool True if a filter is present, false otherwise
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function isNotFilteredByDiscordUser(HasTable $livewire): bool
|
||||
{
|
||||
return null === self::getFilteredDiscordUserId($livewire) ;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
$rules = StoreRemainderRequest::asFilamentRules();
|
||||
|
||||
return $form
|
||||
->schema([
|
||||
Select::make('discord_user_id')
|
||||
->prefixIcon('heroicon-o-users')
|
||||
->relationship(
|
||||
name: 'discordUser',
|
||||
titleAttribute: 'user_name',
|
||||
//NOTE: hidden()/disabled() removes the field from the data sent to the server, so we filter it instead
|
||||
modifyQueryUsing: fn (Builder $query, ListRemainders $livewire) =>
|
||||
($discordUserId = self::getFilteredDiscordUserId($livewire)) !== null
|
||||
? $query->where('id', $discordUserId)
|
||||
: $query
|
||||
)
|
||||
->searchable(self::isNotFilteredByDiscordUser(...))
|
||||
->preload(self::isNotFilteredByDiscordUser(...))
|
||||
->default(self::getFilteredDiscordUserId(...))
|
||||
->native(false)
|
||||
->visibleOn('create')
|
||||
->selectablePlaceholder(false)
|
||||
->required()
|
||||
->columnSpan(2),
|
||||
Placeholder::make('discordUser.user_name')
|
||||
->content(fn (Remainder $remainder): string => $remainder->discordUser->user_name)
|
||||
->hiddenOn(['create']),
|
||||
Placeholder::make('discordUser.global_name')
|
||||
->content(fn (Remainder $remainder): string => $remainder->discordUser->global_name)
|
||||
->hiddenOn(['create']),
|
||||
DateTimePicker::make('due_at')
|
||||
->prefixIcon('heroicon-o-calendar')
|
||||
->required()
|
||||
->native(false)
|
||||
->timezone(Auth::user()->timezone)
|
||||
->rules($rules['due_at'])
|
||||
->default(Carbon::now()->addMinutes(10)),
|
||||
TextInput::make('channel_id')
|
||||
->prefixIcon('heroicon-o-hashtag')
|
||||
->placeholder('snowflake or leave empty')
|
||||
->rules($rules['channel_id']),
|
||||
TextInput::make('message')
|
||||
->required()
|
||||
->prefixIcon('heroicon-o-chat-bubble-bottom-center-text')
|
||||
->placeholder('the message tor the remainder')
|
||||
->rules($rules['message'])
|
||||
->columnSpan(2),
|
||||
Select::make('status')
|
||||
->visibleOn('edit')
|
||||
->options(RemainderStatus::toSelectOptions())
|
||||
->selectablePlaceholder(false)
|
||||
->columnSpan(2)
|
||||
->default(RemainderStatus::NEW),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\RemainderResource\Sections;
|
||||
|
||||
use App\Models\Remainder;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Infolists\Components\Section;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
|
||||
final class RemainderResourceInfoList
|
||||
{
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the color of a field based of the Remainders state
|
||||
*
|
||||
* @param Remainder $record The Remainder to get the class for (Injected by filament)
|
||||
*
|
||||
* @return string The color based on the state of the Remainder
|
||||
*
|
||||
*/
|
||||
public static function getStatusColor(Remainder $record): string
|
||||
{
|
||||
return match ($record->isFailed()) {
|
||||
true => 'danger',
|
||||
default => 'info',
|
||||
};
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Creates an infoList view for RemainderResource
|
||||
*
|
||||
* @param Infolist $infolist The current InfoList (Injected by filament)
|
||||
*
|
||||
* @return Infolist The created infoList
|
||||
*
|
||||
*/
|
||||
public static function infolist(Infolist $infolist): Infolist
|
||||
{
|
||||
return $infolist
|
||||
->schema([
|
||||
Section::make('Discrod User')
|
||||
->icon('heroicon-o-users')
|
||||
->iconColor('info')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextEntry::make('discordUser.user_name')->label('User name'),
|
||||
TextEntry::make('discordUser.global_name')->label('Global name'),
|
||||
]),
|
||||
Section::make('Remainder')
|
||||
->icon('heroicon-o-calendar')
|
||||
->iconColor('info')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextEntry::make('due_at')->dateTime(),
|
||||
TextEntry::make('channel_id')->placeholder('No chanel set.'),
|
||||
TextEntry::make('message')->columnSpan(2),
|
||||
TextEntry::make('status')->color(self::getStatusColor(...)),
|
||||
TextEntry::make('error')
|
||||
->placeholder('no error')
|
||||
->color(self::getStatusColor(...)),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\RemainderResource\Sections;
|
||||
|
||||
use App\Enums\RemainderStatus;
|
||||
use App\Filament\Actions\InfoAction;
|
||||
use App\Filament\Resources\DiscordUserResource\RelationManagers\RemaindersRelationManager;
|
||||
use App\Filament\Resources\RemainderResource\Pages\ListRemainders;
|
||||
use App\Models\Remainder;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Enums;
|
||||
use Filament\Tables\Actions;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\BulkActionGroup;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Tables\Actions\RestoreAction;
|
||||
use Filament\Tables\Actions\RestoreBulkAction;
|
||||
use Filament\Tables\Columns\ColumnGroup;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Filters\TernaryFilter;
|
||||
use Filament\Tables\Filters\TrashedFilter;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Livewire;
|
||||
|
||||
final class RemainderResourceTable
|
||||
{
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Checks if the table is filtered by the given field
|
||||
*
|
||||
* @param string $field The name of the field to check for
|
||||
*
|
||||
* @return bool True if the table is filtered by the given field, false otherwise
|
||||
*
|
||||
*/
|
||||
protected static function isTableFilteredBy(string $field): bool
|
||||
{
|
||||
$liveWire = Livewire::current();
|
||||
|
||||
// hide details on relation manager
|
||||
if ($liveWire::class === RemaindersRelationManager::class) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// hide details if the table is filtered to one user only
|
||||
if ($liveWire->tableFilters[$field]['value'] ?? null !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// don't hide
|
||||
return false;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Checks if the current table is included in another view as a relation
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
protected static function isRelationManagerView(): bool
|
||||
{
|
||||
return is_subclass_of(
|
||||
Livewire::current(),
|
||||
RelationManager::class
|
||||
);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Checks if the DiscordUser of the Remainder is trashed
|
||||
*
|
||||
* @param Remainder $remainder The Remainder to check
|
||||
*
|
||||
* @return bool True if the DiscordUser (owner) is trashed, false otherwise
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
protected static function isDiscordUserTrashed(Remainder $remainder): bool
|
||||
{
|
||||
//NOTE: this needs to be cached becouse this gets called for each row twice, so caching is only needed just for a short time
|
||||
return cache()->remember(
|
||||
key: 'DiscordUserIsTrashed-'.$remainder->discord_user_id,
|
||||
ttl: 3,
|
||||
callback: fn () => $remainder->discordUser->trashed()
|
||||
);
|
||||
//NOTE: vendor/livewire/livewire/src/Features/SupportModels/ModelSynth.php:65 calls this query twice,
|
||||
// which cannot be cached sadly...
|
||||
// SQL: select * from "discord_users" where "discord_users"."id" = 1 limit 1
|
||||
// but 3 queries are better then 22, so...
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Checks if the table is filtered by the DiscordUser
|
||||
*
|
||||
* @return bool true if a filter is present, falser otherwise
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function isTableFilteredByDiscordUser(): bool
|
||||
{
|
||||
return static::isTableFilteredBy('discord_user_id');
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the color of a colummn based of the state of the Remainder property
|
||||
*
|
||||
* @param Remainder $record The Remainder to check if it is trashed or not (Injected by filament)
|
||||
*
|
||||
* @return string The color based on the trashed state
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function getColumnColor(Remainder $record): string
|
||||
{
|
||||
return match ($record->trashed()) {
|
||||
true => 'danger-500',
|
||||
default => 'gray',
|
||||
};
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the classes of a colummn based of the Remainders state
|
||||
*
|
||||
* @param Remainder $record The Remainder to check if it is trashed or not (Injected by filament)
|
||||
*
|
||||
* @return string The class(es) based on the state of the Remainder
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function getRecordClass(Remainder $record): string
|
||||
{
|
||||
if ($record->trashed()) {
|
||||
return 'border-l-4 !border-l-danger-500 line-through !decoration-danger-700 !text-amber-700';
|
||||
}
|
||||
|
||||
if ($record->isOverDue()) {
|
||||
return 'border-l-4 !border-l-warning-500 !decoration-warning-700 !text-amber-700';
|
||||
}
|
||||
|
||||
return 'border-l-4 !border-l-success-500 !text-yellow-400';
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the status icon based of the Remainder
|
||||
*
|
||||
* @param Remainder $record The Remainder to check if it is trashed or not (Injected by filament)
|
||||
*
|
||||
* @return string The icon of matching the Remainders status
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function getStatusIcon(Remainder $record): string
|
||||
{
|
||||
return match ($record->status) {
|
||||
RemainderStatus::NEW => 'heroicon-o-clock',
|
||||
RemainderStatus::FAILED => 'heroicon-o-x-circle',
|
||||
RemainderStatus::FINISHED => 'heroicon-o-check-circle',
|
||||
default => 'heroicon-o-exclamation-circle',
|
||||
};
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the status icon color based of the Remainder
|
||||
*
|
||||
* @param Remainder $record The Remainder to check if it is trashed or not (Injected by filament)
|
||||
*
|
||||
* @return string The color of the icon of the Remainders status
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function getStatusIconColor(Remainder $record): string
|
||||
{
|
||||
return match ($record->status) {
|
||||
RemainderStatus::NEW => 'info',
|
||||
RemainderStatus::PENDING => 'warning',
|
||||
RemainderStatus::FAILED => 'danger',
|
||||
RemainderStatus::FINISHED => 'success',
|
||||
default => 'gray',
|
||||
};
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the status icon color based of the Remainder
|
||||
*
|
||||
* @param Remainder $record The Remainder to check if it is trashed or not (Injected by filament)
|
||||
*
|
||||
* @return string The color of the icon of the Remainders status
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function getDueAtIconColor(Remainder $record): string
|
||||
{
|
||||
return $record->isOverDue() ? 'danger' : 'gray';
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
|
||||
return $table
|
||||
->recordClasses(self::getRecordClass(...))
|
||||
->columns([
|
||||
|
||||
ColumnGroup::make('Discord user')
|
||||
->columns([
|
||||
|
||||
TextColumn::make('id')
|
||||
->color(self::getColumnColor(...))
|
||||
->toggleable(isToggledHiddenByDefault: true)
|
||||
->numeric()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('discordUser.user_name')
|
||||
->color(self::getColumnColor(...))
|
||||
->hidden(self::isTableFilteredByDiscordUser(...))
|
||||
->toggleable(isToggledHiddenByDefault: true)
|
||||
->sortable()
|
||||
->searchable(isIndividual: true)
|
||||
->label('Name'),
|
||||
|
||||
TextColumn::make('discordUser.global_name')
|
||||
->color(self::getColumnColor(...))
|
||||
->hidden(self::isTableFilteredByDiscordUser(...))
|
||||
->sortable()
|
||||
->searchable(isIndividual: true)
|
||||
->label('Global Name'),
|
||||
|
||||
])->alignCenter(),
|
||||
|
||||
ColumnGroup::make('Remainder')
|
||||
->columns([
|
||||
|
||||
IconColumn::make('status')
|
||||
->sortable()
|
||||
->alignCenter()
|
||||
->icon(self::getStatusIcon(...))
|
||||
->color(self::getStatusIconColor(...))
|
||||
->extraAttributes(fn ($state) => ['title' => $state->value]),
|
||||
|
||||
TextColumn::make('due_at')
|
||||
->color(self::getColumnColor(...))
|
||||
->dateTime()
|
||||
->timezone(Auth::user()->timezone)
|
||||
->sortable()
|
||||
->icon('heroicon-m-clock')
|
||||
->iconColor(self::getDueAtIconColor(...)),
|
||||
|
||||
TextColumn::make('message')
|
||||
->color(self::getColumnColor(...))
|
||||
->searchable()
|
||||
->limit(50),
|
||||
|
||||
TextColumn::make('channel_id')
|
||||
->searchable()
|
||||
->color(self::getColumnColor(...))
|
||||
->toggleable(isToggledHiddenByDefault: true)
|
||||
->placeholder('No chanel set.'),
|
||||
|
||||
])->alignCenter(),
|
||||
|
||||
ColumnGroup::make('Dates')
|
||||
->columns([
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->color(self::getColumnColor(...))
|
||||
->toggleable(isToggledHiddenByDefault: true)
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('updated_at')
|
||||
->color(self::getColumnColor(...))
|
||||
->toggleable(isToggledHiddenByDefault: true)
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('deleted_at')
|
||||
->color(self::getColumnColor(...))
|
||||
->toggleable(isToggledHiddenByDefault: true)
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
|
||||
])->alignCenter(),
|
||||
])
|
||||
->defaultSort('due_at')
|
||||
->filters([
|
||||
|
||||
SelectFilter::make('discord_user_id')
|
||||
->relationship(
|
||||
name: 'discordUser',
|
||||
titleAttribute: 'user_name',
|
||||
modifyQueryUsing: fn (Builder $query, $livewire) =>
|
||||
($discordUserId = $livewire->tableFilters['discord_user_id']['value']) !== null
|
||||
? $query->where('id', $discordUserId)
|
||||
: $query
|
||||
)
|
||||
->searchable()
|
||||
->preload() //NOTE: this is very resource intensive for large data sets
|
||||
->label('Discord User')
|
||||
->hidden(static::isRelationManagerView(...))
|
||||
->resetState(fn () => redirect(ListRemainders::getUrl())),
|
||||
|
||||
SelectFilter::make('status')
|
||||
->multiple()
|
||||
->hidden(static::isRelationManagerView(...))
|
||||
->options(RemainderStatus::toSelectOptions()),
|
||||
|
||||
TernaryFilter::make('channel_id')
|
||||
->hidden(self::isTableFilteredByDiscordUser(...))
|
||||
->label('Channel')
|
||||
->placeholder('All')
|
||||
->trueLabel('Provided')
|
||||
->falseLabel('Not provided')
|
||||
->queries(
|
||||
true: fn (Builder $query) => $query->whereNotNull('channel_id'),
|
||||
false: fn (Builder $query) => $query->whereNull('channel_id'),
|
||||
blank: fn (Builder $query) => $query,
|
||||
),
|
||||
|
||||
TrashedFilter::make()
|
||||
->hidden(self::isTableFilteredByDiscordUser(...)),
|
||||
|
||||
], layout: Enums\FiltersLayout::Dropdown)
|
||||
->filtersTriggerAction(
|
||||
fn (Action $action) => $action
|
||||
->button()
|
||||
->label('Filter'),
|
||||
)
|
||||
->filtersFormColumns(4)
|
||||
|
||||
->actions([
|
||||
InfoAction::make(),
|
||||
|
||||
EditAction::make()
|
||||
->label('')
|
||||
->extraAttributes(['title' => 'Edit'])
|
||||
->closeModalByClickingAway(false),
|
||||
|
||||
DeleteAction::make()->label('')->extraAttributes(['title' => 'Delete']),
|
||||
|
||||
RestoreAction::make()
|
||||
->disabled(self::isDiscordUserTrashed(...))
|
||||
->label('')
|
||||
->iconButton()
|
||||
->color('danger')
|
||||
->iconSize(IconSize::Small)
|
||||
->extraAttributes(
|
||||
fn (Remainder $remainder) =>
|
||||
self::isDiscordUserTrashed($remainder)
|
||||
? ['title' => 'Cannot Restore']
|
||||
: ['title' => 'Restore']
|
||||
),
|
||||
])
|
||||
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Actions\DeleteBulkAction $action) {
|
||||
$action->redirect('/admin/remainders');
|
||||
}),
|
||||
ForceDeleteBulkAction::make(),
|
||||
RestoreBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
59
src/app/Filament/Resources/UserResource.php
Normal file
59
src/app/Filament/Resources/UserResource.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\UserResource\Services\UserResourceForm;
|
||||
use App\Filament\Resources\UserResource\Services\UserResourceInfoList;
|
||||
use App\Filament\Resources\UserResource\Services\UserResourceTable;
|
||||
use App\Filament\Resources\UserResource\Pages;
|
||||
use App\Models\User;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-user';
|
||||
|
||||
protected static ?string $navigationGroup = 'Admin';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return UserResourceForm::form($form);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return UserResourceTable::table($table);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function infolist(Infolist $infolist): Infolist
|
||||
{
|
||||
return UserResourceInfoList::infolist($infolist);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListUsers::route('/'),
|
||||
//'create' => Pages\CreateUser::route('/create'),
|
||||
//'view' => Pages\ViewUser::route('/{record}'),
|
||||
'show' => Pages\ViewUser::route('/{record}'),
|
||||
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
11
src/app/Filament/Resources/UserResource/Pages/CreateUser.php
Normal file
11
src/app/Filament/Resources/UserResource/Pages/CreateUser.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateUser extends CreateRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
}
|
||||
32
src/app/Filament/Resources/UserResource/Pages/EditUser.php
Normal file
32
src/app/Filament/Resources/UserResource/Pages/EditUser.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EditUser extends EditRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
//Actions\ViewAction::make(),
|
||||
//Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
public function handleRecordUpdate(Model $record, array $data): Model
|
||||
{
|
||||
// filter out null values
|
||||
$changes = array_filter($data);
|
||||
|
||||
$record->update($changes);
|
||||
|
||||
return $record;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
19
src/app/Filament/Resources/UserResource/Pages/ListUsers.php
Normal file
19
src/app/Filament/Resources/UserResource/Pages/ListUsers.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
src/app/Filament/Resources/UserResource/Pages/ViewUser.php
Normal file
19
src/app/Filament/Resources/UserResource/Pages/ViewUser.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewUser extends ViewRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\EditAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Services;
|
||||
|
||||
use App\Models\User;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Tapp\FilamentTimezoneField\Forms\Components\TimezoneSelect;
|
||||
|
||||
final class UserResourceForm
|
||||
{
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('email')
|
||||
->email()
|
||||
->required(),
|
||||
TimezoneSelect::make('timezone')
|
||||
->searchable(),
|
||||
Forms\Components\DateTimePicker::make('email_verified_at')
|
||||
->default(now())
|
||||
->native(false)
|
||||
->suffixAction(
|
||||
Action::make('now')
|
||||
->icon('heroicon-o-clock')
|
||||
->name('now')
|
||||
->action(function (DateTimePicker $component, Set $set) {
|
||||
$set($component->getName(), now()->format($component->getFormat()));
|
||||
})
|
||||
->visible(
|
||||
fn (User $user, $livewire): bool =>
|
||||
$livewire instanceof EditRecord
|
||||
&& !$user->isEmailVerified()
|
||||
)
|
||||
),
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Services;
|
||||
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Infolists\Components\Section;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
|
||||
final class UserResourceInfoList
|
||||
{
|
||||
|
||||
public static function infolist(Infolist $infolist): Infolist
|
||||
{
|
||||
return $infolist
|
||||
->schema([
|
||||
Section::make('User')
|
||||
->icon('heroicon-o-users')
|
||||
->iconColor('info')
|
||||
->description('The common data of the user.')
|
||||
->columns(2)
|
||||
->compact()
|
||||
->schema([
|
||||
TextEntry::make('name')->label('Name'),
|
||||
TextEntry::make('email')->label('Email'),
|
||||
TextEntry::make('timezone')->label('Timezone'),
|
||||
TextEntry::make('email_verified_at')->label('Verified')->placeholder('not verified'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Services;
|
||||
|
||||
use App\Filament\Actions\InfoAction;
|
||||
use App\Models\User;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
final class UserResourceTable
|
||||
{
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the url for the UserResource::ViewUser
|
||||
*
|
||||
* @param User $user The User to view
|
||||
*
|
||||
* @return string The url to the view
|
||||
*
|
||||
* @callback_function
|
||||
*
|
||||
*/
|
||||
public static function getViewUserUrl(User $user): string
|
||||
{
|
||||
return route('filament.admin.resources.users.show', [
|
||||
'record' => $user,
|
||||
]);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('email')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('email_verified_at')
|
||||
->dateTime()
|
||||
->placeholder('not verified')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('timezone')
|
||||
->sortable()
|
||||
->toggleable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\TrashedFilter::make(),
|
||||
])
|
||||
->actions([
|
||||
InfoAction::make(),
|
||||
Tables\Actions\Action::make('View')
|
||||
->label('')
|
||||
->extraAttributes(['title' => 'View'])
|
||||
->icon('heroicon-o-eye')
|
||||
->url(self::getViewUserUrl(...)),
|
||||
Tables\Actions\EditAction::make()
|
||||
->label('')
|
||||
->extraAttributes(['title' => 'Edit']),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
73
src/app/Filament/Widgets/Admin/RemainderAllChart.php
Normal file
73
src/app/Filament/Widgets/Admin/RemainderAllChart.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets\Admin;
|
||||
|
||||
use App\Models\Remainder;
|
||||
//use Carbon\Carbon;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Flowframe\Trend\Trend;
|
||||
use Flowframe\Trend\TrendValue;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class RemainderAllChart extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'All Remainders due at';
|
||||
|
||||
protected static ?string $pollingInterval = '10s';
|
||||
|
||||
protected static ?int $sort = 3;
|
||||
|
||||
protected static string $color = 'info';
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$data = Trend::model(Remainder::class)
|
||||
->dateColumn('due_at')
|
||||
->between(
|
||||
start: Remainder::getFirstDueAt()?->due_at ?? Carbon::now(),
|
||||
end: Remainder::getLastDueAt()?->due_at ?? Carbon::now()->addMonth(),
|
||||
)
|
||||
->perMonth()
|
||||
->count();
|
||||
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Remainders',
|
||||
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
|
||||
'fill' => true,
|
||||
'tension' => 1,
|
||||
|
||||
],
|
||||
],
|
||||
|
||||
'labels' => $data->map(fn (TrendValue $value) => $value->date),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'line';
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'plugins' => [
|
||||
'legend' => [
|
||||
'display' => false,
|
||||
],
|
||||
],
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'min' => 0,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return 'The number of remainders due at per month.';
|
||||
}
|
||||
}
|
||||
76
src/app/Filament/Widgets/Admin/RemainderTodayChart.php
Normal file
76
src/app/Filament/Widgets/Admin/RemainderTodayChart.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets\Admin;
|
||||
|
||||
use App\Models\Remainder;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Flowframe\Trend\Trend;
|
||||
use Flowframe\Trend\TrendValue;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class RemainderTodayChart extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Todays Remainders due at';
|
||||
|
||||
protected static ?string $pollingInterval = '10s';
|
||||
|
||||
protected static ?int $sort = 2;
|
||||
|
||||
protected static string $color = 'success';
|
||||
|
||||
//protected int | string | array $columnSpan = 2;
|
||||
|
||||
//protected static ?string $maxHeight = '10rem';
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$data = Trend::model(Remainder::class)
|
||||
->dateColumn('due_at')
|
||||
->between(
|
||||
start: Carbon::today(),
|
||||
end: Carbon::today()->endOfDay(),
|
||||
)
|
||||
->perHour()
|
||||
->count()
|
||||
;
|
||||
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Remainders',
|
||||
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
|
||||
'fill' => true,
|
||||
'tension' => 0.5,
|
||||
],
|
||||
],
|
||||
'labels' => $data->map(fn (TrendValue $value) => explode(' ', $value->date)[1]),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'line';
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'plugins' => [
|
||||
'legend' => [
|
||||
'display' => false,
|
||||
],
|
||||
],
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'min' => 0,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return 'The number of remainders due at today.';
|
||||
}
|
||||
|
||||
}
|
||||
25
src/app/Filament/Widgets/StatsOverview.php
Normal file
25
src/app/Filament/Widgets/StatsOverview.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\DiscordUser;
|
||||
use App\Models\Remainder;
|
||||
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class StatsOverview extends BaseWidget
|
||||
{
|
||||
protected function getStats(): array
|
||||
{
|
||||
|
||||
$discordUserCount = DiscordUser::count();
|
||||
$remainderCount = Remainder::count();
|
||||
$avarageRemainders = $discordUserCount === 0 ? 0 : $remainderCount / $discordUserCount;
|
||||
return [
|
||||
Stat::make('Discord users', Number::format($discordUserCount)),
|
||||
Stat::make('Remainders', Number::format($remainderCount)),//abbreviate
|
||||
Stat::make('Average remainders per user', Number::format($avarageRemainders, 2)),
|
||||
];
|
||||
}
|
||||
}
|
||||
18
src/app/Helpers/helpers.php
Normal file
18
src/app/Helpers/helpers.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ApiPermission;
|
||||
|
||||
/**
|
||||
* Returns a ability filter of az ApiPermission case for 'auth:sanctum' 'ability' middleware.
|
||||
*
|
||||
* @param ApiPermission $ability The ability to generate a filter for
|
||||
*
|
||||
* @return string The ability string. Example: 'ability:auth-user'
|
||||
*
|
||||
* @see https://laravel.com/docs/11.x/sanctum#token-ability-middleware
|
||||
*
|
||||
*/
|
||||
function ability(ApiPermission $ability): string
|
||||
{
|
||||
return "ability:{$ability->value}";
|
||||
}
|
||||
10
src/app/Helpers/locales.php
Normal file
10
src/app/Helpers/locales.php
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\v1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\v1\UpdateDiscordUserRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\DiscordUser;
|
||||
use App\Http\Resources\Api\v1\DiscordUserResource;
|
||||
|
||||
/**
|
||||
* @group Discord User By snowflake Managment
|
||||
*
|
||||
* APIs to manage DiscordUser records.
|
||||
*
|
||||
* These endpoints can be used to identify/create DiscordUser records identified by the [snowflake](#snowflake) that already exists in the discord app.
|
||||
*
|
||||
*/
|
||||
|
||||
class DiscordUserBySnoflakeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Get the DiscordUser identified by the specified snowflake.
|
||||
*
|
||||
* Returns the [DiscordUser](#discorduser) record for the specified [snowflake](#snowflake), given in the url __discord_user_snowflake__ parameter.
|
||||
*
|
||||
* If it cannot be found, a [**404, Not Found**](#not-found-404) error is returned.
|
||||
*
|
||||
* @urlParam discord_user_snowflake string required A valid [snowflake](#snowflake). Example: 481398158916845568
|
||||
* @response 200 scenario=success {"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/Budapest"}}
|
||||
* @response 404 scenario="not found" {"message":"Not Found."}
|
||||
*
|
||||
*/
|
||||
public function show(Request $request, DiscordUser $discordUser)
|
||||
{
|
||||
return DiscordUserResource::make($discordUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get _OR_ Update/Create the DiscordUser identified by the specified snowflake.
|
||||
*
|
||||
* If the record specified by the url __discord_user_snowflake__ parameter exists, it will be updated with the data provided in the body of the request.
|
||||
*
|
||||
* If it does not exists, it will be created using the given data.
|
||||
*
|
||||
* Returns the **updated/created** [DiscordUser](#discorduser) record.
|
||||
*
|
||||
* If anything goes wrong, a [**422, Unprocessable Content**](#unprocessable-content-422) error with more details will be returned.
|
||||
*
|
||||
* @urlParam snowflake string required A valid [snowflake](#snowflake). Example: 481398158916845568
|
||||
* @bodyParam snowflake string required A valid [snowflake](#snowflake). Example: 481398158916845568
|
||||
* @bodyParam locale string A valid [locale](#locale). Example: en_GB
|
||||
* @bodyParam user_name string The user_name registered in Discord. Example: bigfootmcfly
|
||||
* @bodyParam global_name string The global_name registered in Discord. Example: BigFoot McFly
|
||||
* @bodyParam avatar string The avatar url registered in Discord. No-example
|
||||
* @bodyParam timezone string A valid [time zone](#timezone). Example: Europe/London
|
||||
* @response 200 scenario=success {"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"en_GB","timezone":"Europe\/London"},"changes":{"locale":{"old":"hu_HU","new":"en_GB"},"timezone":{"old":"Europe\/Budapest","new":"Europe\/London"}}}
|
||||
* @response 422 scenario="Unprocessable Content" {"errors":{"snowflake":["The snowflake field is required."]}}
|
||||
*
|
||||
*/
|
||||
public function update(UpdateDiscordUserRequest $request, int $snowflake)
|
||||
{
|
||||
|
||||
$statusCode = match (($original = DiscordUser::where('snowflake', $snowflake)->first()) === null) {
|
||||
true => 201,
|
||||
false => 200
|
||||
};
|
||||
|
||||
//NOTE: needed for the changeed field list, may be null
|
||||
$original = DiscordUser::where('snowflake', $snowflake)->first();
|
||||
|
||||
$discordUser = DiscordUser::updateOrCreate(['snowflake' => $snowflake], $request->validated());
|
||||
|
||||
return response([
|
||||
'data' => (DiscordUserResource::make($discordUser)),
|
||||
'changes' => $discordUser->getChangedValues($original, ['updated_at']),
|
||||
], $statusCode);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
145
src/app/Http/Controllers/Api/v1/DiscordUserController.php
Normal file
145
src/app/Http/Controllers/Api/v1/DiscordUserController.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\v1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\v1\StoreDiscordUserRequest;
|
||||
use App\Http\Requests\Api\v1\UpdateDiscordUserRequest;
|
||||
use App\Http\Requests\Api\v1\DeleteDiscordUserRequest;
|
||||
use App\Http\Resources\Api\v1\DiscordUserResource;
|
||||
use App\Models\DiscordUser;
|
||||
use App\Actions\DeleteDiscordUserAction;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
/**
|
||||
* @group Discord User Managment
|
||||
*
|
||||
* APIs to manage [DiscordUser](#discorduser) records.
|
||||
*
|
||||
* These endpoints can be used to Query/Update/Delete [DiscordUser](#discorduser) records.
|
||||
*/
|
||||
class DiscordUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* List DiscorUsers.
|
||||
*
|
||||
* Paginated list of [DiscordUser](#discorduser) records.
|
||||
*
|
||||
* @queryParam page_size int Items per page. Defaults to 100. Example: 25
|
||||
* @queryParam page int Page to query. Defaults to 1. Example: 1
|
||||
* @response 200 scenario=success {"data":[{"id":1,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe/Budapest"},{"id":6,"snowflake":"860046989130727450","user_name":"Teszt Elek","global_name":"holnap_is_teszt_elek","locale":"hu","timezone":"Europe/Budapest"},{"id":12,"snowflake":"112233445566778899","user_name":"Igaz Álmos","global_name":"almos#1244","locale":null,"timezone":null}],"meta":{"current_page":1,"from":1,"last_page":1,"per_page":10,"to":3,"total":3}}
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return ResourceCollection
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
return DiscordUserResource::collection(DiscordUser::paginate(perPage: $request->page_size ?? 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DiscordUser record.
|
||||
*
|
||||
* Creates a new [DiscordUser](#discorduser) record with the provided parameters.
|
||||
*
|
||||
* @bodyParam snowflake string required A valid [snowflake](#snowflake). Example: 481398158916845568
|
||||
* @bodyParam locale string A valid [locale](#locale). Example: hu_HU
|
||||
* @bodyParam user_name string The user_name registered in Discord. Example: bigfootmcfly
|
||||
* @bodyParam global_name string The global_name registered in Discord. Example: BigFoot McFly
|
||||
* @bodyParam avatar string The avatar url registered in Discord. No-example
|
||||
* @bodyParam timezone string A valid [time zone](#timezone). Example: Europe/Budapest
|
||||
* @response 200 scenario=success {"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/Budapest"}}
|
||||
* @response 422 scenario="Unprocessable Content" {"errors":{"snowflake":["The snowflake has already been taken."]}}
|
||||
*/
|
||||
public function store(StoreDiscordUserRequest $request)
|
||||
{
|
||||
$discordUser = DiscordUser::create($request->validated());
|
||||
return response(
|
||||
content: [
|
||||
'data' => DiscordUserResource::make($discordUser),
|
||||
],
|
||||
status: 201
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the specified DiscordUser record.
|
||||
*
|
||||
* Returns the specified [DiscordUser](#discorduser) record.
|
||||
*
|
||||
* @urlParam id int required [DiscordUser](#discorduser) ID. Example: 42
|
||||
* @response 200 scenario=success {"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/Budapest"}}
|
||||
*
|
||||
* @return DiscordUserResource
|
||||
*
|
||||
*/
|
||||
public function show(DiscordUser $discordUser)
|
||||
{
|
||||
return DiscordUserResource::make($discordUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified DiscordUser record.
|
||||
*
|
||||
* Updates the specified [DiscordUser](#discorduser) with the supplied values.
|
||||
*
|
||||
* @urlParam id int required [DiscordUser](#discorduser) ID. Example: 42
|
||||
* @bodyParam snowflake string required The snowflake of the [DiscordUser](#discorduser) to update. Example: 481398158916845568
|
||||
* @bodyParam user_name string The user_name registered in Discord. No-example
|
||||
* @bodyParam global_name string The global_name registered in Discord. No-example
|
||||
* @bodyParam avatar string The avatar url registered in Discord. No-example
|
||||
* @bodyParam locale string A valid locale. <a href="https://github.com/Nerdtrix/language-list/blob/main/language-list-json.json" target="_blank">Locale list (json)</a> No-example
|
||||
* @bodyParam timezone string A valid [time zone](#timezone). Example: Europe/London
|
||||
* @response 200 {"data":{"id":42,"snowflake":"481398158916845568","user_name":"bigfootmcfly","global_name":"BigFoot McFly","locale":"hu_HU","timezone":"Europe\/London"}}
|
||||
* @response 422 scenario="Unprocessable Content" {"errors":{"snowflake":["Invalid snowflake"]}}
|
||||
*/
|
||||
public function update(UpdateDiscordUserRequest $request, DiscordUser $discordUser)
|
||||
{
|
||||
//NOTE: due to filament validation, the snowflake is not checked in the request, it must be checked here.
|
||||
$snowflake = $request->input('snowflake');
|
||||
if ($discordUser->snowflake !== $snowflake) {
|
||||
return response(
|
||||
content: [
|
||||
"errors" => [
|
||||
"snowflake" => [
|
||||
"Invalid snowflake",
|
||||
],
|
||||
],
|
||||
],
|
||||
status: 422
|
||||
);
|
||||
}
|
||||
|
||||
$original = DiscordUser::where('snowflake', $snowflake)->first();
|
||||
|
||||
$discordUser->update($request->validated());
|
||||
|
||||
return response(
|
||||
content: [
|
||||
'data' => (DiscordUserResource::make($discordUser)),
|
||||
'changes' => $discordUser->getChangedValues($original, ['updated_at']),
|
||||
],
|
||||
status: 200
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified DiscordUser record.
|
||||
*
|
||||
* Removes the specified [DiscordUser](#discorduser) record **with** all the [Remainder](#remainder) records belonging to it.
|
||||
*
|
||||
* @urlParam id int required [DiscordUser](#discorduser) ID. Example: 42
|
||||
* @bodyParam snowflake string required The snowflake of the [DiscordUser](#discorduser) to delete. Example: 481398158916845568
|
||||
* @response 204
|
||||
*
|
||||
* @param \App\Http\Requests\Api\v1\DeleteDiscordUserRequest $request
|
||||
* @param \App\Models\DiscordUser $discordUser
|
||||
*
|
||||
*/
|
||||
public function destroy(DeleteDiscordUserRequest $request, DiscordUser $discordUser)
|
||||
{
|
||||
return DeleteDiscordUserAction::run($discordUser);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\v1;
|
||||
|
||||
use App\Actions\CreateRemainderAction;
|
||||
use App\Actions\DeleteRemainderAction;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\v1\DeleteRemainderRequest;
|
||||
use App\Http\Requests\Api\v1\StoreRemainderRequest;
|
||||
use App\Http\Requests\Api\v1\UpdateRemainderRequest;
|
||||
use App\Http\Resources\Api\v1\RemainderResource;
|
||||
use App\Models\DiscordUser;
|
||||
use App\Models\Remainder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
/**
|
||||
* @group Remainder Managment
|
||||
*
|
||||
* APIs to manage Remainders records.
|
||||
*
|
||||
* These endpoints can be used to Query/Update/Delete [Remainder](#remainder) records.
|
||||
*
|
||||
*/
|
||||
|
||||
class DiscordUserRemaindersController extends Controller
|
||||
{
|
||||
/**
|
||||
* List of Remainder records.
|
||||
*
|
||||
* Paginated list of [Remainder](#remainder) records belonging to the specified [DiscordUser](#discorduser).
|
||||
*
|
||||
* @urlParam discord_user_id int required [DiscordUser](#discorduser) ID. Example: 42
|
||||
* @queryParam page_size int Items per page. Defaults to 100. Example: 25
|
||||
* @queryParam page int Page to query. Defaults to 1. Example: 1
|
||||
* @response 200 scenario=success {"data":[{"id":38,"discord_user_id":42,"channel_id":null,"due_at":1736259300,"message":"Update todo list","status":"new","error":null},{"id":121,"discord_user_id":42,"channel_id":null,"due_at":1736259480,"message":"Water plants","status":"new","error":null}],"meta":{"current_page":1,"from":1,"last_page":1,"per_page":25,"to":2,"total":2}}
|
||||
*
|
||||
* @param DiscordUser $discordUser
|
||||
* @param Request $request
|
||||
* @return ResourceCollection
|
||||
*/
|
||||
public function index(DiscordUser $discordUser, Request $request): ResourceCollection
|
||||
{
|
||||
return RemainderResource::collection($discordUser->remainders()->paginate(perPage: $request->page_size ?? 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Remainder record.
|
||||
*
|
||||
* Creates a new [Remainder](#remainder) record with the provided parameters.
|
||||
*
|
||||
* @urlParam discord_user_id int required [DiscordUser](#discorduser) ID. Example: 42
|
||||
*
|
||||
* @bodyParam due_at string required The "Due at" time ([timestamp](#timestamp)) of the remainder Example: 1732550400
|
||||
* @bodyParam message string required The message to send to the discord user. Example: Check maintance results!
|
||||
* @bodyParam channel_id string The [snowflake](#snowflake) of the channel to send the remainder to. No-example
|
||||
*
|
||||
* @response 200 {"data":{"id":18568,"discord_user_id":42,"channel_id":null,"due_at":1732550400,"message":"Check maintance results!","status":"new","error":null}}
|
||||
*
|
||||
*/
|
||||
public function store(StoreRemainderRequest $request, DiscordUser $discordUser)
|
||||
{
|
||||
return CreateRemainderAction::run($request, $discordUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified Remainder record.
|
||||
*
|
||||
* Updates the specified [Remainder](#remainder) record with the provided parameters.
|
||||
*
|
||||
* @urlParam discord_user_id int required [DiscordUser](#discorduser) ID. Example: 42
|
||||
* @urlParam id int required [Remainder](#remainder) ID. Example: 18568
|
||||
* @bodyParam due_at string The "Due at" time ([timestamp](#timestamp)) of the remainder. No-example
|
||||
* @bodyParam message string The message to send to the discord user. No-example
|
||||
* @bodyParam channel_id string The [snowflake](#snowflake) of the channel to send the remainder to. No-example
|
||||
* @bodyParam error string Error description in case of failure. Example: Unknow user
|
||||
* @bodyParam status string Status of the [Remainder](#remainder).
|
||||
*
|
||||
* For possible values see: [RemainderStatus](#remainderstatus) Example: failed
|
||||
*
|
||||
* @response 200 {"data":{"id":18568,"discord_user_id":42,"channel_id":null,"due_at":1732550400,"message":"Check maintance results!","status":"failed","error":"Unknow user"},"changes":{"status":{"old":"new","new":"failed"},"error":{"old":null,"new":"Unknow user"}}}
|
||||
*
|
||||
*/
|
||||
public function update(UpdateRemainderRequest $request, DiscordUser $discordUser, Remainder $remainder)
|
||||
{
|
||||
$original = Remainder::find($remainder->id)->first();
|
||||
$remainder->update($request->validated());
|
||||
|
||||
return response(
|
||||
content: [
|
||||
'data' => (RemainderResource::make($remainder)),
|
||||
'changes' => $remainder->getChangedValues($original, ['updated_at']),
|
||||
],
|
||||
status: 200
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified Remainder record.
|
||||
*
|
||||
* Removes the specified [Remainder](#remainder) record with the provided parameters.
|
||||
*
|
||||
* @urlParam discord_user_id int required [DiscordUser](#discorduser) ID. Example: 42
|
||||
* @urlParam id int required [Remainder](#remainder) ID. Example: 18568
|
||||
* @bodyParam snowflake string required The [snowflake](#snowflake) of the DiscordUser of the Remainder to delete. Example: 481398158916845568
|
||||
|
||||
* @response 204
|
||||
*
|
||||
*/
|
||||
public function destroy(DeleteRemainderRequest $request, DiscordUser $discordUser, Remainder $remainder)
|
||||
{
|
||||
return DeleteRemainderAction::run($remainder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\v1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\Api\v1\RemainderResource;
|
||||
use App\Models\Remainder;
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* @group Remainder by DueAt Managment
|
||||
*
|
||||
* API to get Remainder records.
|
||||
*
|
||||
* This endpoint can be used to Query the actual [Remainder](#remainder) records.
|
||||
*
|
||||
*/
|
||||
|
||||
class RemainderByDueAtController extends Controller
|
||||
{
|
||||
/**
|
||||
* Returns all the "actual" reaminders for the given second.
|
||||
*
|
||||
* @urlParam due_at int required The time ([timestamp](#timestamp)) of the requested remainders. Example: 1735685999
|
||||
* @queryParam page_size int Items per page. Defaults to 100. Example: 25
|
||||
* @queryParam page int Page to query. Defaults to 1. Example: 1
|
||||
* @response 200 scenario=success {"data":[{"id":56,"discord_user_id":42,"channel_id":null,"due_at":1735685999,"message":"Update conatiner registry!","status":"new","error":null},{"id":192,"discord_user_id":47,"channel_id":null,"due_at":1735685999,"message":"Get some milk","status":"new","error":null}],"meta":{"current_page":1,"from":1,"last_page":1,"per_page":100,"to":2,"total":2}}
|
||||
*/
|
||||
public function __invoke(int|string $due_at)
|
||||
{
|
||||
return RemainderResource::collection(Remainder::query()
|
||||
->where('due_at', Carbon::createFromTimestamp($due_at))
|
||||
->where('status', 'new')
|
||||
->paginate(perPage: $request->page_size ?? 100));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AuthenticatedSessionController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the login view.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming authentication request.
|
||||
*/
|
||||
public function store(LoginRequest $request): RedirectResponse
|
||||
{
|
||||
$request->authenticate();
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy an authenticated session.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ConfirmablePasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the confirm password view.
|
||||
*/
|
||||
public function show(): View
|
||||
{
|
||||
return view('auth.confirm-password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the user's password.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if (! Auth::guard('web')->validate([
|
||||
'email' => $request->user()->email,
|
||||
'password' => $request->password,
|
||||
])) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('auth.password'),
|
||||
]);
|
||||
}
|
||||
|
||||
$request->session()->put('auth.password_confirmed_at', time());
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EmailVerificationNotificationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Send a new email verification notification.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
$request->user()->sendEmailVerificationNotification();
|
||||
|
||||
return back()->with('status', 'verification-link-sent');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class EmailVerificationPromptController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the email verification prompt.
|
||||
*/
|
||||
public function __invoke(Request $request): RedirectResponse|View
|
||||
{
|
||||
return $request->user()->hasVerifiedEmail()
|
||||
? redirect()->intended(route('dashboard', absolute: false))
|
||||
: view('auth.verify-email');
|
||||
}
|
||||
}
|
||||
61
src/app/Http/Controllers/Auth/NewPasswordController.php
Normal file
61
src/app/Http/Controllers/Auth/NewPasswordController.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class NewPasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the password reset view.
|
||||
*/
|
||||
public function create(Request $request): View
|
||||
{
|
||||
return view('auth.reset-password', ['request' => $request]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming new password request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'token' => ['required'],
|
||||
'email' => ['required', 'email'],
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$status = Password::reset(
|
||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function ($user) use ($request) {
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($request->password),
|
||||
'remember_token' => Str::random(60),
|
||||
])->save();
|
||||
|
||||
event(new PasswordReset($user));
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
return $status == Password::PASSWORD_RESET
|
||||
? redirect()->route('login')->with('status', __($status))
|
||||
: back()->withInput($request->only('email'))
|
||||
->withErrors(['email' => __($status)]);
|
||||
}
|
||||
}
|
||||
29
src/app/Http/Controllers/Auth/PasswordController.php
Normal file
29
src/app/Http/Controllers/Auth/PasswordController.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Update the user's password.
|
||||
*/
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validateWithBag('updatePassword', [
|
||||
'current_password' => ['required', 'current_password'],
|
||||
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||
]);
|
||||
|
||||
$request->user()->update([
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
return back()->with('status', 'password-updated');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class PasswordResetLinkController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the password reset link request view.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('auth.forgot-password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming password reset link request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => ['required', 'email'],
|
||||
]);
|
||||
|
||||
// We will send the password reset link to this user. Once we have attempted
|
||||
// to send the link, we will examine the response then see the message we
|
||||
// need to show to the user. Finally, we'll send out a proper response.
|
||||
$status = Password::sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
return $status == Password::RESET_LINK_SENT
|
||||
? back()->with('status', __($status))
|
||||
: back()->withInput($request->only('email'))
|
||||
->withErrors(['email' => __($status)]);
|
||||
}
|
||||
}
|
||||
50
src/app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
50
src/app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the registration view.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('auth.register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return redirect(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
27
src/app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
27
src/app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class VerifyEmailController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mark the authenticated user's email address as verified.
|
||||
*/
|
||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
if ($request->user()->markEmailAsVerified()) {
|
||||
event(new Verified($request->user()));
|
||||
}
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
}
|
||||
8
src/app/Http/Controllers/Controller.php
Normal file
8
src/app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
60
src/app/Http/Controllers/ProfileController.php
Normal file
60
src/app/Http/Controllers/ProfileController.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ProfileUpdateRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the user's profile form.
|
||||
*/
|
||||
public function edit(Request $request): View
|
||||
{
|
||||
return view('profile.edit', [
|
||||
'user' => $request->user(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return Redirect::route('profile.edit')->with('status', 'profile-updated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's account.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validateWithBag('userDeletion', [
|
||||
'password' => ['required', 'current_password'],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
}
|
||||
58
src/app/Http/Middleware/HardenHeaders.php
Normal file
58
src/app/Http/Middleware/HardenHeaders.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class HardenHeaders
|
||||
{
|
||||
protected $headersToRemove = [
|
||||
'X-Powered-By',
|
||||
'Server',
|
||||
];
|
||||
|
||||
protected $headersToAdd = [
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
'Strict-Transport-Security' => 'max-age:63072000; includeSubDomains; preload',
|
||||
'X-Answer' => '42',
|
||||
//'X-Powered-By' => 'NCSA HTTPd v1.5',
|
||||
//'X-Powered-By' => 'CERN httpd/3.0A',
|
||||
];
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
$this
|
||||
->removeHeaders($response)
|
||||
->addHeaders($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function removeHeaders(Response $response): self
|
||||
{
|
||||
foreach ($this->headersToRemove as $header) {
|
||||
header_remove($header); // remove header already stored by php
|
||||
$response->headers->remove($header); // remove header managed by laravel
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function addHeaders(Response $response): self
|
||||
{
|
||||
foreach ($this->headersToAdd as $header => $value) {
|
||||
$response->headers->set($header, $value);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
33
src/app/Http/Middleware/StripPaginationInfo.php
Normal file
33
src/app/Http/Middleware/StripPaginationInfo.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Removes web links from api paginated response.
|
||||
*/
|
||||
class StripPaginationInfo
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$response = $next($request);
|
||||
|
||||
$content = json_decode($response->getContent(), true);
|
||||
|
||||
unset($content['links']);
|
||||
unset($content['meta']['links']);
|
||||
unset($content['meta']['path']);
|
||||
|
||||
$response->setContent(json_encode($content));
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
49
src/app/Http/Requests/Api/v1/DeleteDiscordUserRequest.php
Normal file
49
src/app/Http/Requests/Api/v1/DeleteDiscordUserRequest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\v1;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
|
||||
class DeleteDiscordUserRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; //maybe add guard (route is already guarded...)
|
||||
}
|
||||
|
||||
protected function failedValidation(Validator $validator): void
|
||||
{
|
||||
throw new HttpResponseException(response()->json(['errors' => $validator->errors()], 422));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
/**
|
||||
* @var string snowflake The snowflake of the DiscordUser
|
||||
* NOTE: the failsafe is for Scribe.
|
||||
* @see: https://scribe.knuckles.wtf/laravel/troubleshooting#some-weird-behaviour-when-using-formrequests
|
||||
* @see 'git show eb10344ce1'
|
||||
*/
|
||||
$snowflake = request('discord_user')?->snowflake ?? 0;
|
||||
|
||||
return [
|
||||
'snowflake' => [
|
||||
'required',
|
||||
'snowflake',
|
||||
Rule::in($snowflake),
|
||||
],
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
70
src/app/Http/Requests/Api/v1/DeleteRemainderRequest.php
Normal file
70
src/app/Http/Requests/Api/v1/DeleteRemainderRequest.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\v1;
|
||||
|
||||
use App\Models\DiscordUser;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
|
||||
class DeleteRemainderRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; //maybe add guard (route is already guarded...)
|
||||
}
|
||||
|
||||
protected function failedValidation(Validator $validator): void
|
||||
{
|
||||
throw new HttpResponseException(response()->json(['errors' => $validator->errors()], 422));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string snowflake The snowflake of the DiscordUser
|
||||
* NOTE: the failsafe is for Scribe.
|
||||
* @see: https://scribe.knuckles.wtf/laravel/troubleshooting#some-weird-behaviour-when-using-formrequests
|
||||
* @see 'git show eb10344ce1'
|
||||
*/
|
||||
$snowflake = request('discord_user')?->snowflake ?? 0;
|
||||
|
||||
return [
|
||||
'snowflake' => [
|
||||
'required',
|
||||
'string',
|
||||
'digits_between:18,19',
|
||||
Rule::in($snowflake),
|
||||
'exclude',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "after" validation callables for the request.
|
||||
*/
|
||||
public function after(): array
|
||||
{
|
||||
return [
|
||||
function (Validator $validator) {
|
||||
if (request()->remainder->discord_user_id != request()->discord_user->id) {
|
||||
$validator->errors()->add(
|
||||
'remainder',
|
||||
'Remainder does not belong to the discord user!',
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
48
src/app/Http/Requests/Api/v1/StoreDiscordUserRequest.php
Normal file
48
src/app/Http/Requests/Api/v1/StoreDiscordUserRequest.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\v1;
|
||||
|
||||
use App\Traits\FilamentFormRequest;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
|
||||
class StoreDiscordUserRequest extends FormRequest
|
||||
{
|
||||
use FilamentFormRequest;
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; //maybe add guard (route is already guarded...)
|
||||
}
|
||||
|
||||
protected function failedValidation(Validator $validator): void
|
||||
{
|
||||
throw new HttpResponseException(response()->json(['errors' => $validator->errors()], 422));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'snowflake' => 'required|snowflake|unique:discord_users,snowflake',
|
||||
'user_name' => 'string|nullable',
|
||||
'global_name' => 'string|nullable',
|
||||
'avatar' => 'string|nullable',
|
||||
'locale' => [
|
||||
Rule::in(LOCALES),
|
||||
'nullable',
|
||||
],
|
||||
'timezone' => 'string|nullable|timezone:all',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
40
src/app/Http/Requests/Api/v1/StoreRemainderRequest.php
Normal file
40
src/app/Http/Requests/Api/v1/StoreRemainderRequest.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\v1;
|
||||
|
||||
use App\Traits\FilamentFormRequest;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
|
||||
class StoreRemainderRequest extends FormRequest
|
||||
{
|
||||
use FilamentFormRequest;
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
protected function failedValidation(Validator $validator): void
|
||||
{
|
||||
throw new HttpResponseException(response()->json(['errors' => $validator->errors()], 422));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'due_at' => 'required',
|
||||
'message' => 'required|string',
|
||||
'channel_id' => 'snowflake|nullable',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
52
src/app/Http/Requests/Api/v1/UpdateDiscordUserRequest.php
Normal file
52
src/app/Http/Requests/Api/v1/UpdateDiscordUserRequest.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\v1;
|
||||
|
||||
use App\Traits\FilamentFormRequest;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
|
||||
class UpdateDiscordUserRequest extends FormRequest
|
||||
{
|
||||
|
||||
use FilamentFormRequest;
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; //maybe add guard (route is already guarded...)
|
||||
}
|
||||
|
||||
protected function failedValidation(Validator $validator): void
|
||||
{
|
||||
throw new HttpResponseException(response()->json(['errors' => $validator->errors()], 422));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'snowflake' => [
|
||||
'required',
|
||||
'string',
|
||||
'snowflake',
|
||||
'exclude',
|
||||
],
|
||||
'user_name' => 'string|nullable',
|
||||
'global_name' => 'string|nullable',
|
||||
'avatar' => 'string|nullable',
|
||||
'locale' => [
|
||||
Rule::in(LOCALES),
|
||||
'nullable',
|
||||
],
|
||||
'timezone' => 'string|nullable|timezone:all',
|
||||
];
|
||||
}
|
||||
}
|
||||
76
src/app/Http/Requests/Api/v1/UpdateRemainderRequest.php
Normal file
76
src/app/Http/Requests/Api/v1/UpdateRemainderRequest.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\v1;
|
||||
|
||||
use App\Enums\RemainderStatus;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
|
||||
class UpdateRemainderRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
protected function failedValidation(Validator $validator): void
|
||||
{
|
||||
throw new HttpResponseException(response()->json(['errors' => $validator->errors()], 422));
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if (!$this->request->has('due_at')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert from timestamp to Carbon object because laravel lacks timestamp validation
|
||||
$this->merge([
|
||||
'due_at' => Carbon::createFromTimestamp($this->request->get('due_at')),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'due_at' => 'nullable|date|after:now',//'after:tomorrow'
|
||||
'message' => 'string',
|
||||
'channel_id' => 'digits_between:18,19|nullable',
|
||||
'status' => [
|
||||
Rule::in(RemainderStatus::values()),
|
||||
'nullable',
|
||||
],
|
||||
'error' => 'nullable|string',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the "after" validation callables for the request.
|
||||
*/
|
||||
public function after(): array
|
||||
{
|
||||
return [
|
||||
function (Validator $validator) {
|
||||
if (request()->remainder->discord_user_id != request()->discord_user->id) {
|
||||
$validator->errors()->add(
|
||||
'remainder',
|
||||
'Remainder does not belong to the discord user!',
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
85
src/app/Http/Requests/Auth/LoginRequest.php
Normal file
85
src/app/Http/Requests/Auth/LoginRequest.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Auth\Events\Lockout;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class LoginRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to authenticate the request's credentials.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function authenticate(): void
|
||||
{
|
||||
$this->ensureIsNotRateLimited();
|
||||
|
||||
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
|
||||
RateLimiter::hit($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.failed'),
|
||||
]);
|
||||
}
|
||||
|
||||
RateLimiter::clear($this->throttleKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the login request is not rate limited.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function ensureIsNotRateLimited(): void
|
||||
{
|
||||
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event(new Lockout($this));
|
||||
|
||||
$seconds = RateLimiter::availableIn($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.throttle', [
|
||||
'seconds' => $seconds,
|
||||
'minutes' => ceil($seconds / 60),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rate limiting throttle key for the request.
|
||||
*/
|
||||
public function throttleKey(): string
|
||||
{
|
||||
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||||
}
|
||||
}
|
||||
23
src/app/Http/Requests/ProfileUpdateRequest.php
Normal file
23
src/app/Http/Requests/ProfileUpdateRequest.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ProfileUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\v1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class DiscordUserRemainderResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return array_merge(
|
||||
DiscordUserResource::make($this)->toArray($request),
|
||||
['remainders' => $this->remainders->toArray($request)]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
27
src/app/Http/Resources/Api/v1/DiscordUserResource.php
Normal file
27
src/app/Http/Resources/Api/v1/DiscordUserResource.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\v1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class DiscordUserResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'snowflake' => $this->snowflake,
|
||||
'user_name' => $this->user_name,
|
||||
'global_name' => $this->global_name,
|
||||
'locale' => $this->locale,
|
||||
'timezone' => $this->timezone,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
39
src/app/Http/Resources/Api/v1/RemainderResource.php
Normal file
39
src/app/Http/Resources/Api/v1/RemainderResource.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\v1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class RemainderResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'discord_user_id' => $this->discord_user_id,
|
||||
'channel_id' => $this->channel_id,
|
||||
'due_at' => $this->due_at->timestamp,
|
||||
'message' => $this->message,
|
||||
'status' => $this->status,
|
||||
'error' => $this->error,
|
||||
...(
|
||||
$request->has('withDiscordUser')
|
||||
? ['discord_user' => new DiscordUserResource($this->discordUser)]
|
||||
: []
|
||||
),
|
||||
//NOTE: the above could be done with this:
|
||||
// it is easier to read, but then, the relation must be loaded before this call, so the Request must be handled before...
|
||||
/*
|
||||
'discord_user' => whenLoaded(new DiscordUserResource($this->discordUser)),
|
||||
*/
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
66
src/app/Livewire/ProfileTimezoneComponent.php
Normal file
66
src/app/Livewire/ProfileTimezoneComponent.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Joaopaulolndev\FilamentEditProfile\Concerns\HasSort;
|
||||
use Joaopaulolndev\FilamentEditProfile\Concerns\HasUser;
|
||||
use Tapp\FilamentTimezoneField\Forms\Components\TimezoneSelect;
|
||||
|
||||
class ProfileTimezoneComponent extends Component implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
use HasSort;
|
||||
use HasUser;
|
||||
|
||||
public ?array $data = [];
|
||||
|
||||
protected static int $sort = 0;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$user = $this->getUser();
|
||||
|
||||
$this->form->fill(['timezone' => $user->timezone]);
|
||||
}
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Section::make('Update Timezone')
|
||||
->aside()
|
||||
->description('Set/Update your Timezone.')
|
||||
->schema([
|
||||
TimezoneSelect::make('timezone')
|
||||
->searchable(),
|
||||
]),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$data = $this->form->getState();
|
||||
|
||||
$user = $this->getUser();
|
||||
$user->update($data);
|
||||
|
||||
Notification::make()
|
||||
->title('Your profile information has been saved successfully.')
|
||||
->success()
|
||||
->send();
|
||||
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.profile-timezone-component');
|
||||
}
|
||||
}
|
||||
118
src/app/Models/DiscordUser.php
Normal file
118
src/app/Models/DiscordUser.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\HasChanges;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class DiscordUser extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes, HasApiTokens, HasChanges;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'snowflake',
|
||||
'user_name',
|
||||
'global_name',
|
||||
'avatar',
|
||||
'locale',
|
||||
'timezone',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'avatar',
|
||||
];
|
||||
|
||||
/**
|
||||
* retuns only the non-trashed remainders for the DiscordUser
|
||||
*/
|
||||
public function remainders()
|
||||
{
|
||||
return $this->hasMany(Remainder::class);//->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* retuns only the trashed remainders for the DiscordUser
|
||||
*/
|
||||
public function trashedRemainders()
|
||||
{
|
||||
return $this->hasMany(Remainder::class)->onlyTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* retuns all remainders for the DiscordUser
|
||||
*/
|
||||
public function allRemainders()
|
||||
{
|
||||
return $this->hasMany(Remainder::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* retuns the count of non-trashed remainders of the DiscordUser
|
||||
*/
|
||||
public function getRemainderCountAttribute(): int
|
||||
{
|
||||
return cache()->remember(
|
||||
key: 'DiscordUserRemainderCount_'.$this->id,
|
||||
ttl: 3,
|
||||
callback: fn () => $this->remainders_count ?? $this->remainders()->count()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* retuns the count of trashed remainders of the DiscordUser
|
||||
*/
|
||||
public function getTrashedRemainderCountAttribute(): int
|
||||
{
|
||||
return cache()->remember(
|
||||
key: 'DiscordUserTrashedRemainderCount_'.$this->id,
|
||||
ttl: 1,
|
||||
callback: fn () => $this->trashedRemainders()->count()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* retuns the count of all remainders of the DiscordUser
|
||||
*/
|
||||
public function getAllRemainderCountAttribute(): int
|
||||
{
|
||||
return cache()->remember(
|
||||
key: 'DiscordUserAllRemainderCount_'.$this->id,
|
||||
ttl: 3,
|
||||
callback: fn () => $this->remainders()->withTrashed()->count()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the remainders count to the model
|
||||
* @param Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
public function scopeRemainderCount(Builder $builder): void
|
||||
{
|
||||
$builder->withCount('remainders');
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanantly delete the DiscordUser and all its remainders
|
||||
*
|
||||
* The records are not protected by soft-delete
|
||||
*
|
||||
* @return [type]
|
||||
*
|
||||
*/
|
||||
public function permanentDelete()
|
||||
{
|
||||
$this->allRemainders()->forceDelete();
|
||||
|
||||
$this->forceDelete();
|
||||
}
|
||||
}
|
||||
115
src/app/Models/Remainder.php
Normal file
115
src/app/Models/Remainder.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\RemainderStatus;
|
||||
use App\Traits\HasChanges;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
|
||||
class Remainder extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes, HasChanges;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'discord_user_id',
|
||||
'channel_id',
|
||||
'due_at',
|
||||
'message',
|
||||
'status',
|
||||
'error',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'status' => RemainderStatus::class,
|
||||
'due_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* retuns the owner of the remainder
|
||||
*/
|
||||
public function discordUser(): Relation|EloquentBuilder
|
||||
{
|
||||
return $this->belongsTo(DiscordUser::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the channel_id to the model
|
||||
*
|
||||
* @param EloquentBuilder $query
|
||||
* @param bool $present
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function scopeChannel(EloquentBuilder $query, bool $present = true): void
|
||||
{
|
||||
$query->whereNull('channel_id', not: $present);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first active remainder
|
||||
*
|
||||
* @return self|null the first remainder if found, null if there are no active remainders
|
||||
*
|
||||
*/
|
||||
public static function getFirstDueAt(): self|null
|
||||
{
|
||||
return cache()->remember(
|
||||
key: 'RemainderFirstDueAt',
|
||||
ttl: 1,
|
||||
callback: fn () => self::orderBy('due_at')->first()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last active remainder
|
||||
*
|
||||
* @return self|null the last remainder if found, null if there are no active remainders
|
||||
*
|
||||
*/
|
||||
public static function getLastDueAt(): self|null
|
||||
{
|
||||
return cache()->remember(
|
||||
key: 'RemainderLastDueAt',
|
||||
ttl: 1,
|
||||
callback: fn () => self::orderByDesc('due_at')->first()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if the remainder has failed
|
||||
*
|
||||
* @return bool true if the remainder failed, false otherwise
|
||||
*
|
||||
*/
|
||||
public function isFailed(): bool
|
||||
{
|
||||
return $this->status === RemainderStatus::FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if the remainder is overdue
|
||||
*
|
||||
* @return bool true if the remainder is overdue, false otherwise
|
||||
*
|
||||
*/
|
||||
public function isOverDue(): bool
|
||||
{
|
||||
return
|
||||
$this->due_at < Carbon::now()
|
||||
&& $this->status !== RemainderStatus::FINISHED
|
||||
&& $this->status !== RemainderStatus::FAILED
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
65
src/app/Models/User.php
Normal file
65
src/app/Models/User.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Filament\Models\Contracts\FilamentUser;
|
||||
use Filament\Panel;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class User extends Authenticatable implements FilamentUser
|
||||
{
|
||||
use HasFactory, Notifiable, HasApiTokens, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'timezone',
|
||||
'email_verified_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
public function isEmailVerified(): bool
|
||||
{
|
||||
return $this->email_verified_at !== null;
|
||||
}
|
||||
|
||||
public function canAccessPanel(Panel $panel): bool
|
||||
{
|
||||
//TODO: this is temporary and should be replaced by role managment...
|
||||
return $this->id === 1;
|
||||
}
|
||||
|
||||
}
|
||||
66
src/app/Policies/DiscordUserPolicy.php
Normal file
66
src/app/Policies/DiscordUserPolicy.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\DiscordUser;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class DiscordUserPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*/
|
||||
public function view(User $user, DiscordUser $discordUser): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*/
|
||||
public function update(User $user, DiscordUser $discordUser): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*/
|
||||
public function delete(User $user, DiscordUser $discordUser): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*/
|
||||
public function restore(User $user, DiscordUser $discordUser): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*/
|
||||
public function forceDelete(User $user, DiscordUser $discordUser): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
36
src/app/Providers/AppServiceProvider.php
Normal file
36
src/app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Validators\SnowflakeValidator;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// load telescope only if not in production
|
||||
if ($this->app->environment('local')) {
|
||||
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
|
||||
$this->app->register(TelescopeServiceProvider::class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
// setting default color for Infolist textEntry objects
|
||||
TextEntry::configureUsing(function (TextEntry $textEntry): void {
|
||||
$textEntry->color('info');
|
||||
});
|
||||
|
||||
// register custom validator
|
||||
SnowflakeValidator::registerValidator();
|
||||
}
|
||||
}
|
||||
115
src/app/Providers/Filament/AdminPanelProvider.php
Normal file
115
src/app/Providers/Filament/AdminPanelProvider.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use App\Enums\ApiPermission;
|
||||
use App\Livewire\ProfileTimezoneComponent;
|
||||
use CharrafiMed\GlobalSearchModal\GlobalSearchModalPlugin;
|
||||
use Devonab\FilamentEasyFooter\EasyFooterPlugin;
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Navigation\MenuItem;
|
||||
use Filament\Pages;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use Joaopaulolndev\FilamentEditProfile\FilamentEditProfilePlugin;
|
||||
use Joaopaulolndev\FilamentEditProfile\Pages\EditProfilePage;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->sidebarCollapsibleOnDesktop()
|
||||
->id('admin')
|
||||
->path('admin')
|
||||
->login()
|
||||
->colors([
|
||||
'primary' => Color::Amber,
|
||||
'gray' => Color::Stone,
|
||||
])
|
||||
->viteTheme('resources/css/filament/admin/theme.css')
|
||||
->brandLogo(fn () => view('filament.admin.logo'))
|
||||
->favicon(asset('images/logo.png'))
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([
|
||||
Pages\Dashboard::class,
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||
->widgets([
|
||||
//
|
||||
])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
])
|
||||
->brandName('Proxima')
|
||||
//NOTE: only set this if the page is on that domain
|
||||
//->domain('proxima.goliath.hu')
|
||||
->globalSearchKeyBindings(['CTRL+ALT+S', 'command+option+s'])
|
||||
->globalSearchFieldKeyBindingSuffix()
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
])
|
||||
->plugins([
|
||||
GlobalSearchModalPlugin::make()
|
||||
->expandedUrlTarget(enabled: false),
|
||||
FilamentEditProfilePlugin::make()
|
||||
->shouldShowDeleteAccountForm(false)
|
||||
->setIcon('heroicon-o-user')
|
||||
->shouldShowBrowserSessionsForm(true)
|
||||
->shouldRegisterNavigation(false)
|
||||
->shouldShowSanctumTokens(
|
||||
condition: true,
|
||||
permissions: ApiPermission::toDescribedArray()
|
||||
)
|
||||
->customProfileComponents([
|
||||
ProfileTimezoneComponent::class,
|
||||
]),
|
||||
//TODO: only add github if github credentials are found!!!
|
||||
EasyFooterPlugin::make()
|
||||
->footerEnabled()
|
||||
->withFooterPosition('footer')
|
||||
->withSentence(new HtmlString('<img class="non-grayscale" src="'.asset('images/icon-512.png').'" alt="Proxima Backend Logo" width="20" height="20"> Proxima Backend'))
|
||||
->withGithub(showLogo: true, showUrl: true)
|
||||
->withLoadTime()
|
||||
->withLinks([
|
||||
[
|
||||
'title' => 'About',
|
||||
'url' => 'https://proxima.goliath.hu',
|
||||
],
|
||||
[
|
||||
'title' => 'Docs',
|
||||
'url' => 'https://proxima.goliath.hu/assets/docs/index.html',
|
||||
],
|
||||
]),
|
||||
])
|
||||
->userMenuItems([
|
||||
'profile' => MenuItem::make()
|
||||
->label(fn () => Auth::user()->name)
|
||||
->url(fn () => EditProfilePage::getUrl())
|
||||
->icon('heroicon-m-user-circle'),
|
||||
])
|
||||
;
|
||||
}
|
||||
}
|
||||
64
src/app/Providers/TelescopeServiceProvider.php
Normal file
64
src/app/Providers/TelescopeServiceProvider.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Telescope\IncomingEntry;
|
||||
use Laravel\Telescope\Telescope;
|
||||
use Laravel\Telescope\TelescopeApplicationServiceProvider;
|
||||
|
||||
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
Telescope::night();
|
||||
|
||||
$this->hideSensitiveRequestDetails();
|
||||
|
||||
$isLocal = $this->app->environment('local');
|
||||
|
||||
Telescope::filter(function (IncomingEntry $entry) use ($isLocal) {
|
||||
return $isLocal ||
|
||||
$entry->isReportableException() ||
|
||||
$entry->isFailedRequest() ||
|
||||
$entry->isFailedJob() ||
|
||||
$entry->isScheduledTask() ||
|
||||
$entry->hasMonitoredTag();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent sensitive request details from being logged by Telescope.
|
||||
*/
|
||||
protected function hideSensitiveRequestDetails(): void
|
||||
{
|
||||
if ($this->app->environment('local')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telescope::hideRequestParameters(['_token']);
|
||||
|
||||
Telescope::hideRequestHeaders([
|
||||
'cookie',
|
||||
'x-csrf-token',
|
||||
'x-xsrf-token',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Telescope gate.
|
||||
*
|
||||
* This gate determines who can access Telescope in non-local environments.
|
||||
*/
|
||||
protected function gate(): void
|
||||
{
|
||||
Gate::define('viewTelescope', function ($user) {
|
||||
return in_array($user->email, [
|
||||
//
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
52
src/app/Traits/BackedEnumHelper.php
Normal file
52
src/app/Traits/BackedEnumHelper.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
trait BackedEnumHelper
|
||||
{
|
||||
/**
|
||||
* Returnbs the names of all casee
|
||||
*
|
||||
* @return array The names of all cases
|
||||
*
|
||||
*/
|
||||
public static function names(): array
|
||||
{
|
||||
return array_column(self::cases(), 'name');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returnbs the values of all casee
|
||||
*
|
||||
* @return array The values of all cases
|
||||
*
|
||||
*/
|
||||
public static function values(): array
|
||||
{
|
||||
return array_column(self::cases(), 'value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the enum to array
|
||||
*
|
||||
* @return array The key => value pairs of the cases
|
||||
*
|
||||
*/
|
||||
public static function toArray(): array
|
||||
{
|
||||
return array_column(self::cases(), 'value', 'name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the enum to array with lower key values
|
||||
*
|
||||
* @return array The strtolower(key) => value pairs of the cases
|
||||
*
|
||||
* @todo this was a temporary fix, refactor it!
|
||||
*/
|
||||
public static function toSelectOptions(): array
|
||||
{
|
||||
return array_change_key_case(self::toArray(), CASE_LOWER);
|
||||
}
|
||||
}
|
||||
22
src/app/Traits/FilamentFormRequest.php
Normal file
22
src/app/Traits/FilamentFormRequest.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
trait FilamentFormRequest
|
||||
{
|
||||
/**
|
||||
* Converts complex rules to usable versions for filament
|
||||
*
|
||||
* @return array list of rules
|
||||
*
|
||||
*/
|
||||
public static function asFilamentRules(): array
|
||||
{
|
||||
//NOTE: currently all special rules are removed from the project, this is here for compatibility
|
||||
//NOTE: filament may not know the table name in a 'unique' filter, so a 'unique' should be converted to 'unique:table_name'
|
||||
//NOTE: In:: rules should be converted to array ( '->__toString()' )
|
||||
|
||||
/* @phpstan-ignore new.static (Symfony package) */
|
||||
return (new static())->rules();
|
||||
}
|
||||
}
|
||||
47
src/app/Traits/HasChanges.php
Normal file
47
src/app/Traits/HasChanges.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
trait HasChanges
|
||||
{
|
||||
/**
|
||||
* Returns the list of changed fields with old/new values of a Model
|
||||
*
|
||||
* @param Model|null $original the Model befor the changes applied for $this (if $this is just created, this will be null)
|
||||
* @param array $hiddenFields list of fields to hide from teh result
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* result example:
|
||||
* [
|
||||
* 'name' => [
|
||||
* 'old' => 'Old Shatterhand'
|
||||
* 'new' => 'Lex Barker'
|
||||
* ]
|
||||
* ]
|
||||
*
|
||||
*/
|
||||
public function getChangedValues(?Model $original = null, array $hiddenFields = []): array
|
||||
{
|
||||
$changes = [];
|
||||
|
||||
foreach ($this->getChanges() as $key => $value) {
|
||||
|
||||
if (in_array($key, $hiddenFields)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isDateTime = $this?->$key && is_object($this?->$key) && $this->$key::class === Carbon::class;
|
||||
|
||||
$changes[$key] = [
|
||||
'old' => $isDateTime ? $original?->$key->timestamp : $original?->$key,
|
||||
'new' => $isDateTime ? Carbon::parse($value)->getTimestamp() : $value,
|
||||
];
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
}
|
||||
60
src/app/Traits/HasEnumDescription.php
Normal file
60
src/app/Traits/HasEnumDescription.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Attributes\Description;
|
||||
use Illuminate\Support\Str;
|
||||
use ReflectionEnum;
|
||||
|
||||
trait HasEnumDescription
|
||||
{
|
||||
/**
|
||||
* Returns the description of the current enum case
|
||||
*
|
||||
* @return string The description of the case if defined, the value as headline otherwise
|
||||
*
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
$ref = new ReflectionEnum($this);
|
||||
|
||||
$case = $ref->getCase($this->name);
|
||||
|
||||
$attr = $case->getAttributes(Description::class);
|
||||
|
||||
if (count($attr) === 0) {
|
||||
return Str::headline($this->value);
|
||||
}
|
||||
|
||||
return $attr[0]->newInstance()->description;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the cases in 'value'=>'description' form
|
||||
*
|
||||
* @return array The value => description pairs of the cases
|
||||
*
|
||||
*/
|
||||
public static function toDescribedArray(): array
|
||||
{
|
||||
|
||||
//NOTE: the one-liner, a marvel of overengineering
|
||||
return array_reduce(
|
||||
array: self::cases(),
|
||||
callback: fn (array $carry, self $case): array =>
|
||||
$carry + [$case->value => $case->description()],
|
||||
initial: []
|
||||
);
|
||||
|
||||
//NOTE: the wise way... (yields the same result)
|
||||
/*
|
||||
$result = [];
|
||||
foreach (self::cases() as $case) {
|
||||
$result[$case->value] = $case->description();
|
||||
}
|
||||
return $result;
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user