! 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:
20
.dockerignore
Normal file
20
.dockerignore
Normal file
@@ -0,0 +1,20 @@
|
||||
src/vendor
|
||||
src/database/database.*
|
||||
src/database/sqlite/database.*
|
||||
src/node_modules
|
||||
src/.env
|
||||
src/.env.example
|
||||
src/.scribe
|
||||
src/public/vendor/telescope
|
||||
src/phpunit.xml
|
||||
src/.php-cs-fixer.*
|
||||
src/.editorconfig
|
||||
src/vite.config.js
|
||||
src/tailwind.config.js
|
||||
src/postcss.config.js
|
||||
src/package-lock.json
|
||||
src/storage
|
||||
volumes/
|
||||
.git/
|
||||
.git-crypt/
|
||||
.github/
|
||||
0
.gitattributes
vendored
Normal file
0
.gitattributes
vendored
Normal file
67
.github/workflows/latest.yaml
vendored
Normal file
67
.github/workflows/latest.yaml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Build docker image
|
||||
|
||||
env:
|
||||
PACKAGE_NAME: backend
|
||||
PACKAGE_TAG: latest
|
||||
|
||||
run-name: ${{ github.actor }} is building the docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
# tags:
|
||||
# - v**
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build_docker_image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checking out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to Goliath Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ vars.PACKAGE_REPOSITORY }}
|
||||
username: ${{ secrets.PACKAGE_USER }}
|
||||
password: ${{ secrets.PACKAGE_PASS }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Initializing packages
|
||||
run: |
|
||||
pushd src
|
||||
npm install
|
||||
composer install
|
||||
popd
|
||||
|
||||
- name: Compiling assets
|
||||
run: |
|
||||
pushd src
|
||||
npm run build
|
||||
popd
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Create Docker Buildx contex
|
||||
run: docker buildx create --name goliath; docker buildx use goliath; docker buildx inspect --bootstrap;
|
||||
|
||||
- name: Building docker image
|
||||
run: |
|
||||
docker buildx build . \
|
||||
--file build/nginx/Dockerfile \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--tag ${{ vars.PACKAGE_REPOSITORY }}/${{ vars.PACKAGE_ORGANIZATION }}/${{ env.PACKAGE_NAME }}:${{ env.PACKAGE_TAG }} \
|
||||
--provenance=false \
|
||||
--sbom=false \
|
||||
--push
|
||||
66
.github/workflows/testing.yaml
vendored
Normal file
66
.github/workflows/testing.yaml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Build docker image
|
||||
|
||||
env:
|
||||
PACKAGE_NAME: backend
|
||||
PACKAGE_TAG: testing
|
||||
|
||||
run-name: ${{ github.actor }} is building the docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v**
|
||||
branches:
|
||||
- 'dev'
|
||||
|
||||
jobs:
|
||||
build_docker_image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checking out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to Goliath Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ vars.PACKAGE_REPOSITORY }}
|
||||
username: ${{ secrets.PACKAGE_USER }}
|
||||
password: ${{ secrets.PACKAGE_PASS }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Initializing packages
|
||||
run: |
|
||||
pushd src
|
||||
npm install
|
||||
composer install
|
||||
popd
|
||||
|
||||
- name: Compiling assets
|
||||
run: |
|
||||
pushd src
|
||||
npm run build
|
||||
popd
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Create Docker Buildx contex
|
||||
run: docker buildx create --name goliath; docker buildx use goliath; docker buildx inspect --bootstrap;
|
||||
|
||||
- name: Building docker image
|
||||
run: |
|
||||
docker buildx build . \
|
||||
--file build/nginx/Dockerfile \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--tag ${{ vars.PACKAGE_REPOSITORY }}/${{ vars.PACKAGE_ORGANIZATION }}/${{ env.PACKAGE_NAME }}:${{ env.PACKAGE_TAG }} \
|
||||
--provenance=false \
|
||||
--sbom=false \
|
||||
--push
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
volumes/debug/*
|
||||
volumes/env/*
|
||||
9
LICENSE
Normal file
9
LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 proxima
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
108
build/nginx/Dockerfile
Normal file
108
build/nginx/Dockerfile
Normal file
@@ -0,0 +1,108 @@
|
||||
ARG ALPINE_VERSION=3.21
|
||||
|
||||
FROM alpine:${ALPINE_VERSION}
|
||||
|
||||
# initialize arguments
|
||||
# php versions: 82, 83, 84
|
||||
ARG PHP_VERSION=84
|
||||
ARG USER_ID=1000
|
||||
ARG GROUP_ID=1000
|
||||
ARG WORKDIR=/app/Backend
|
||||
ARG PHP_FPM_LISTEN_DIR=/app/run
|
||||
ARG LOG_DIR=/app/log
|
||||
|
||||
ENV BUILD_CONTENTS=build/nginx
|
||||
ENV PHP_INI_DIR=/etc/php${PHP_VERSION}
|
||||
|
||||
# install dependencies
|
||||
RUN apk add \
|
||||
bash \
|
||||
supervisor \
|
||||
nginx \
|
||||
icu-data-full \
|
||||
php${PHP_VERSION} \
|
||||
php${PHP_VERSION}-fpm \
|
||||
php${PHP_VERSION}-curl \
|
||||
php${PHP_VERSION}-intl \
|
||||
php${PHP_VERSION}-dom \
|
||||
php${PHP_VERSION}-fileinfo \
|
||||
php${PHP_VERSION}-iconv \
|
||||
php${PHP_VERSION}-mbstring \
|
||||
php${PHP_VERSION}-openssl \
|
||||
php${PHP_VERSION}-pdo \
|
||||
php${PHP_VERSION}-pdo_sqlite \
|
||||
php${PHP_VERSION}-phar \
|
||||
php${PHP_VERSION}-opcache \
|
||||
php${PHP_VERSION}-session \
|
||||
php${PHP_VERSION}-simplexml \
|
||||
php${PHP_VERSION}-sqlite3 \
|
||||
php${PHP_VERSION}-tokenizer \
|
||||
php${PHP_VERSION}-xml \
|
||||
php${PHP_VERSION}-xmlreader \
|
||||
php${PHP_VERSION}-xmlwriter \
|
||||
php${PHP_VERSION}-zip \
|
||||
&& ln -s /usr/bin/php${PHP_VERSION} /usr/bin/php
|
||||
|
||||
# add user and group for the app
|
||||
RUN addgroup -g ${GROUP_ID} bot \
|
||||
&& adduser -u ${USER_ID} -G bot -D bot
|
||||
|
||||
# install composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# copy the custom php.ini
|
||||
COPY ${BUILD_CONTENTS}/config/99_custom_php.ini ${PHP_INI_DIR}/conf.d/99_custom_php.ini
|
||||
|
||||
# copy the custom fpm.ini
|
||||
COPY ${BUILD_CONTENTS}/config/www.conf ${PHP_INI_DIR}/php-fpm.d/www.conf
|
||||
|
||||
# copy nginx config
|
||||
COPY ${BUILD_CONTENTS}/config/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# copy nginx server config
|
||||
COPY ${BUILD_CONTENTS}/config/default.conf /etc/nginx/http.d/default.conf
|
||||
|
||||
# copy supervisord config
|
||||
COPY --chown=${USER_ID}:${GROUP_ID} ${BUILD_CONTENTS}/config/supervisord.conf /etc/supervisord.conf
|
||||
|
||||
# set php version for supervisord
|
||||
RUN sed -i.bak "s/command=php-fpm -F/command=php-fpm${PHP_VERSION} -F/g" /etc/supervisord.conf
|
||||
|
||||
# setup workdir and php-fpm listener dir
|
||||
RUN mkdir -p ${WORKDIR} ${PHP_FPM_LISTEN_DIR} ${LOG_DIR}
|
||||
|
||||
# change ownership of folders to the user
|
||||
RUN chown -R ${USER_ID}:${GROUP_ID} ${WORKDIR} ${PHP_FPM_LISTEN_DIR} ${LOG_DIR}
|
||||
|
||||
# set tue workdir
|
||||
WORKDIR ${WORKDIR}
|
||||
|
||||
# copy the application
|
||||
COPY --chown=${USER_ID}:${GROUP_ID} src/ .
|
||||
|
||||
# change to the user
|
||||
USER ${USER_ID}:${GROUP_ID}
|
||||
|
||||
# initialize composer
|
||||
RUN mkdir -p storage/framework/views \
|
||||
&& composer install \
|
||||
--no-interaction \
|
||||
--no-dev \
|
||||
--optimize-autoloader
|
||||
|
||||
#NOTE: this is a messy hack, there is no guaranty it wont be changed, use this only until a better way is found
|
||||
RUN sed -i.bak "s/->modalWidth('md')/->modalWidth('xl')/g" vendor/joaopaulolndev/filament-edit-profile/src/Livewire/SanctumTokens.php
|
||||
|
||||
# copy the entrypoint script
|
||||
COPY --chown=${USER_ID}:${GROUP_ID} --chmod=0555 ${BUILD_CONTENTS}/entrypoint.sh .
|
||||
|
||||
# define the storage volume
|
||||
VOLUME [ "/app/Backend/storage" ]
|
||||
|
||||
VOLUME [ "/app/Backend/database/sqlite" ]
|
||||
|
||||
# define the exposed port
|
||||
EXPOSE 9000
|
||||
|
||||
# start the app
|
||||
CMD ["/usr/bin/supervisord"]
|
||||
53
build/nginx/build.sh
Executable file
53
build/nginx/build.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
|
||||
PACKAGE_REPOSITORY=gitea.goliath.hu/packages/discord-bot-goliath-backend
|
||||
BUILD_TAG=nginx
|
||||
PACKAGE_TAG=nginx
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
echo Building "${BUILD_TAG}" package with "${PACKAGE_TAG}" tag...
|
||||
|
||||
echo
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
echo Changing to project root directory...
|
||||
|
||||
pushd ../.. > /dev/null
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
echo Building assets...
|
||||
|
||||
pushd src > /dev/null
|
||||
npm run build
|
||||
popd > /dev/null
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
echo Determining tag name...
|
||||
|
||||
branch=$(git branch --show-current)
|
||||
tag=${PACKAGE_TAG:-temp}
|
||||
|
||||
#[[ $branch == dev ]] && tag=testing
|
||||
#[[ $branch == master ]] && tag=latest
|
||||
#[[ $branch == features/nginx-server ]] && tag=nginx-joint
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
echo Building image...
|
||||
|
||||
docker build \
|
||||
--tag ${PACKAGE_REPOSITORY}:$tag \
|
||||
--build-arg GROUP_ID=$(id -g) \
|
||||
--build-arg USER_ID=$(id -u) \
|
||||
--file build/${BUILD_TAG}/Dockerfile \
|
||||
.
|
||||
# --progress=plain \
|
||||
# --push \
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
echo Changing back to build directory...
|
||||
|
||||
popd > /dev/null
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
echo Done.
|
||||
38
build/nginx/config/99_custom_php.ini
Normal file
38
build/nginx/config/99_custom_php.ini
Normal file
@@ -0,0 +1,38 @@
|
||||
; Excludes arguments from stack traces generated from exceptions.
|
||||
; Default Value: Off
|
||||
+zend.exception_ignore_args = On
|
||||
|
||||
; Allows setting the maximum string length in an argument of a stringified stack trace
|
||||
; to a value between 0 and 1000000.
|
||||
; Default Value: 15
|
||||
; In production, it is recommended to set this to 0 to reduce the output
|
||||
; of sensitive information in stack traces.
|
||||
zend.exception_string_param_max_len = 0
|
||||
|
||||
; Set the error reporting level.
|
||||
; Defult Value: E_ALL
|
||||
; https://php.net/error-reporting
|
||||
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
|
||||
|
||||
; This directive controls whether or not and where PHP will output errors,
|
||||
; notices and warnings too. Error output is very useful during development, but
|
||||
; it should never be used on production system.
|
||||
; https://php.net/display-errors
|
||||
display_errors = Off
|
||||
|
||||
; The display of errors which occur during PHP's startup sequence are handled
|
||||
; separately from display_errors. It's strongly recommended to keep
|
||||
; display_startup_errors off, except for debugging.
|
||||
; https://php.net/display-startup-errors
|
||||
display_startup_errors = Off
|
||||
|
||||
; Mysql is not used right now, but in case we change database prowider later...
|
||||
; Enable / Disable collection of memory usage statistics by mysqlnd which can be
|
||||
; used to tune and monitor MySQL operations.
|
||||
mysqlnd.collect_memory_statistics = Off
|
||||
|
||||
; Records communication from all extensions using mysqlnd to the specified log
|
||||
; file.
|
||||
; https://php.net/zend.assertions
|
||||
zend.assertions = -1
|
||||
|
||||
34
build/nginx/config/default.conf
Normal file
34
build/nginx/config/default.conf
Normal file
@@ -0,0 +1,34 @@
|
||||
# This is a default site configuration which will simply return 404, preventing
|
||||
# chance access to any other virtualhost.
|
||||
|
||||
server {
|
||||
listen 9000 default_server;
|
||||
listen [::]:9000 default_server;
|
||||
|
||||
# server_name proxima.goliath.hu;
|
||||
|
||||
access_log /app/log/nginx-access.log;
|
||||
error_log /app/log/nginx-error.log error;
|
||||
|
||||
|
||||
location ~ \.php$ {
|
||||
root /app/Backend/public/;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/app/run/php-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
|
||||
include fastcgi.conf;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /app/Backend/public/;
|
||||
try_files $uri $uri/ /index.php?$args;
|
||||
index index.php index.html index.htm;
|
||||
}
|
||||
|
||||
# You may need this to prevent return 404 recursion.
|
||||
location = /404.html {
|
||||
internal;
|
||||
}
|
||||
}
|
||||
53
build/nginx/config/nginx.conf
Normal file
53
build/nginx/config/nginx.conf
Normal file
@@ -0,0 +1,53 @@
|
||||
#user bot;
|
||||
|
||||
worker_processes auto;
|
||||
|
||||
#error_log /app/log/nginx.error.log warn;
|
||||
|
||||
error_log stderr warn;
|
||||
|
||||
pid /app/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
# Threat files with a unknown filetype as binary
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Define custom log format to include reponse times
|
||||
log_format main_timed '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for" '
|
||||
'$request_time $upstream_response_time $pipe $upstream_cache_status';
|
||||
|
||||
access_log /dev/stdout main_timed;
|
||||
error_log /dev/stderr notice;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
# Write temporary files to /tmp so they can be created as a non-privileged user
|
||||
client_body_temp_path /tmp/client_temp;
|
||||
proxy_temp_path /tmp/proxy_temp_path;
|
||||
fastcgi_temp_path /tmp/fastcgi_temp;
|
||||
uwsgi_temp_path /tmp/uwsgi_temp;
|
||||
scgi_temp_path /tmp/scgi_temp;
|
||||
|
||||
# Hide headers that identify the server to prevent information leakage
|
||||
proxy_hide_header X-Powered-By;
|
||||
fastcgi_hide_header X-Powered-By;
|
||||
server_tokens off;
|
||||
|
||||
# Enable gzip compression by default
|
||||
gzip on;
|
||||
gzip_proxied any;
|
||||
# Based on CloudFlare's recommended settings
|
||||
gzip_types text/richtext text/plain text/css text/x-script text/x-component text/x-java-source text/x-markdown application/javascript application/x-javascript text/javascript text/js image/x-icon image/vnd.microsoft.icon application/x-perl application/x-httpd-cgi text/xml application/xml application/rss+xml application/vnd.api+json application/x-protobuf application/json multipart/bag multipart/mixed application/xhtml+xml font/ttf font/otf font/x-woff image/svg+xml application/vnd.ms-fontobject application/ttf application/x-ttf application/otf application/x-otf application/truetype application/opentype application/x-opentype application/font-woff application/eot application/font application/font-sfnt application/wasm application/javascript-binast application/manifest+json application/ld+json application/graphql+json application/geo+json;
|
||||
gzip_vary on;
|
||||
gzip_disable "msie6";
|
||||
|
||||
# Include server configs
|
||||
include /etc/nginx/http.d/*.conf;
|
||||
}
|
||||
103
build/nginx/config/nginx.default.conf
Normal file
103
build/nginx/config/nginx.default.conf
Normal file
@@ -0,0 +1,103 @@
|
||||
# /etc/nginx/nginx.conf
|
||||
|
||||
user nginx;
|
||||
|
||||
# Set number of worker processes automatically based on number of CPU cores.
|
||||
worker_processes auto;
|
||||
|
||||
# Enables the use of JIT for regular expressions to speed-up their processing.
|
||||
pcre_jit on;
|
||||
|
||||
# Configures default error logger.
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
|
||||
# Includes files with directives to load dynamic modules.
|
||||
include /etc/nginx/modules/*.conf;
|
||||
|
||||
# Include files with config snippets into the root context.
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
|
||||
events {
|
||||
# The maximum number of simultaneous connections that can be opened by
|
||||
# a worker process.
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
# Includes mapping of file name extensions to MIME types of responses
|
||||
# and defines the default type.
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Name servers used to resolve names of upstream servers into addresses.
|
||||
# It's also needed when using tcpsocket and udpsocket in Lua modules.
|
||||
#resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001];
|
||||
|
||||
# Don't tell nginx version to the clients. Default is 'on'.
|
||||
server_tokens off;
|
||||
|
||||
# Specifies the maximum accepted body size of a client request, as
|
||||
# indicated by the request header Content-Length. If the stated content
|
||||
# length is greater than this size, then the client receives the HTTP
|
||||
# error code 413. Set to 0 to disable. Default is '1m'.
|
||||
client_max_body_size 1m;
|
||||
|
||||
# Sendfile copies data between one FD and other from within the kernel,
|
||||
# which is more efficient than read() + write(). Default is off.
|
||||
sendfile on;
|
||||
|
||||
# Causes nginx to attempt to send its HTTP response head in one packet,
|
||||
# instead of using partial frames. Default is 'off'.
|
||||
tcp_nopush on;
|
||||
|
||||
|
||||
# Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2.
|
||||
# TIP: If you're not obligated to support ancient clients, remove TLSv1.1.
|
||||
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
|
||||
# Path of the file with Diffie-Hellman parameters for EDH ciphers.
|
||||
# TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048`
|
||||
#ssl_dhparam /etc/ssl/nginx/dh2048.pem;
|
||||
|
||||
# Specifies that our cipher suits should be preferred over client ciphers.
|
||||
# Default is 'off'.
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
# Enables a shared SSL cache with size that can hold around 8000 sessions.
|
||||
# Default is 'none'.
|
||||
ssl_session_cache shared:SSL:2m;
|
||||
|
||||
# Specifies a time during which a client may reuse the session parameters.
|
||||
# Default is '5m'.
|
||||
ssl_session_timeout 1h;
|
||||
|
||||
# Disable TLS session tickets (they are insecure). Default is 'on'.
|
||||
ssl_session_tickets off;
|
||||
|
||||
|
||||
# Enable gzipping of responses.
|
||||
#gzip on;
|
||||
|
||||
# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
|
||||
gzip_vary on;
|
||||
|
||||
|
||||
# Helper variable for proxying websockets.
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
|
||||
# Specifies the main log format.
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
# Sets the path, format, and configuration for a buffered log write.
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
|
||||
# Includes virtual hosts configs.
|
||||
include /etc/nginx/http.d/*.conf;
|
||||
}
|
||||
65
build/nginx/config/supervisord.conf
Normal file
65
build/nginx/config/supervisord.conf
Normal file
@@ -0,0 +1,65 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
logfile=/dev/null
|
||||
logfile_maxbytes=0
|
||||
pidfile=/app/run/supervisord.pid
|
||||
|
||||
[unix_http_server]
|
||||
file=/app/run/supervisord.sock
|
||||
username = dummy
|
||||
password = dummy
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///app/run/supervisord.sock
|
||||
username = dummy
|
||||
password = dummy
|
||||
|
||||
[program:php-fpm]
|
||||
command=php-fpm -F
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autorestart=true
|
||||
autostart=false
|
||||
startretries=0
|
||||
startsecs=3
|
||||
priority=20
|
||||
|
||||
[program:nginx]
|
||||
command=nginx -c /etc/nginx/nginx.conf -e /app/run/nginx.error.log -g 'daemon off;'
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autorestart=true
|
||||
autostart=false
|
||||
startretries=0
|
||||
startsecs=3
|
||||
priority=30
|
||||
|
||||
[program:scheduler]
|
||||
command=php artisan schedule:work
|
||||
stdout_logfile=/dev/stdout
|
||||
stderr_logfile=/dev/stderr
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile_maxbytes=0
|
||||
autorestart=true
|
||||
autostart=false
|
||||
startsecs=3
|
||||
priority=40
|
||||
|
||||
[program:entrypoint]
|
||||
command=/app/Backend/entrypoint.sh
|
||||
stdout_logfile=/dev/stdout
|
||||
stderr_logfile=/dev/stderr
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile_maxbytes=0
|
||||
autorestart=false
|
||||
autostart=true
|
||||
startsecs=0
|
||||
startretries=1
|
||||
priority=10
|
||||
63
build/nginx/config/www.conf
Normal file
63
build/nginx/config/www.conf
Normal file
@@ -0,0 +1,63 @@
|
||||
; Custom FPM configuration
|
||||
|
||||
[global]
|
||||
; Log to stderr
|
||||
;error_log = /dev/stderr
|
||||
error_log = /app/log/fpm.log
|
||||
|
||||
[www]
|
||||
; The address on which to accept FastCGI requests.
|
||||
; Valid syntaxes are:
|
||||
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
|
||||
; a specific port;
|
||||
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
|
||||
; a specific port;
|
||||
; 'port' - to listen on a TCP socket to all addresses
|
||||
; (IPv6 and IPv4-mapped) on a specific port;
|
||||
; '/path/to/unix/socket' - to listen on a unix socket.
|
||||
; Note: This value is mandatory.
|
||||
listen = /app/run/php-fpm.sock
|
||||
listen.owner = bot
|
||||
listen.group = bot
|
||||
|
||||
access.log = /app/log/$pool.fpm.access.log
|
||||
|
||||
; Enable status page
|
||||
pm.status_path = /fpm-status
|
||||
|
||||
; Ondemand process manager
|
||||
pm = ondemand
|
||||
|
||||
; The number of child processes to be created when pm is set to 'static' and the
|
||||
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
|
||||
; This value sets the limit on the number of simultaneous requests that will be
|
||||
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
|
||||
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
|
||||
; CGI. The below defaults are based on a server without much resources. Don't
|
||||
; forget to tweak pm.* to fit your needs.
|
||||
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
|
||||
; Note: This value is mandatory.
|
||||
pm.max_children = 100
|
||||
|
||||
; The number of seconds after which an idle process will be killed.
|
||||
; Note: Used only when pm is set to 'ondemand'
|
||||
; Default Value: 10s
|
||||
pm.process_idle_timeout = 10s;
|
||||
|
||||
; The number of requests each child process should execute before respawning.
|
||||
; This can be useful to work around memory leaks in 3rd party libraries. For
|
||||
; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
|
||||
; Default Value: 0
|
||||
pm.max_requests = 1000
|
||||
|
||||
; Make sure the FPM workers can reach the environment variables for configuration
|
||||
clear_env = no
|
||||
|
||||
; Catch output from PHP
|
||||
catch_workers_output = yes
|
||||
|
||||
; Remove the 'child 10 said into stderr' prefix in the log and only show the actual message
|
||||
decorate_workers_output = no
|
||||
|
||||
; Enable ping page to use in healthcheck
|
||||
ping.path = /fpm-ping
|
||||
490
build/nginx/config/www.default.conf
Normal file
490
build/nginx/config/www.default.conf
Normal file
@@ -0,0 +1,490 @@
|
||||
; Start a new pool named 'www'.
|
||||
; the variable $pool can be used in any directive and will be replaced by the
|
||||
; pool name ('www' here)
|
||||
[www]
|
||||
|
||||
; Per pool prefix
|
||||
; It only applies on the following directives:
|
||||
; - 'access.log'
|
||||
; - 'slowlog'
|
||||
; - 'listen' (unixsocket)
|
||||
; - 'chroot'
|
||||
; - 'chdir'
|
||||
; - 'php_values'
|
||||
; - 'php_admin_values'
|
||||
; When not set, the global prefix (or /usr) applies instead.
|
||||
; Note: This directive can also be relative to the global prefix.
|
||||
; Default Value: none
|
||||
;prefix = /path/to/pools/$pool
|
||||
|
||||
; Unix user/group of the child processes. This can be used only if the master
|
||||
; process running user is root. It is set after the child process is created.
|
||||
; The user and group can be specified either by their name or by their numeric
|
||||
; IDs.
|
||||
; Note: If the user is root, the executable needs to be started with
|
||||
; --allow-to-run-as-root option to work.
|
||||
; Default Values: The user is set to master process running user by default.
|
||||
; If the group is not set, the user's group is used.
|
||||
user = nobody
|
||||
group = nobody
|
||||
|
||||
; The address on which to accept FastCGI requests.
|
||||
; Valid syntaxes are:
|
||||
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
|
||||
; a specific port;
|
||||
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
|
||||
; a specific port;
|
||||
; 'port' - to listen on a TCP socket to all addresses
|
||||
; (IPv6 and IPv4-mapped) on a specific port;
|
||||
; '/path/to/unix/socket' - to listen on a unix socket.
|
||||
; Note: This value is mandatory.
|
||||
listen = 127.0.0.1:9000
|
||||
|
||||
; Set listen(2) backlog.
|
||||
; Default Value: 511 (-1 on Linux, FreeBSD and OpenBSD)
|
||||
;listen.backlog = 511
|
||||
|
||||
; Set permissions for unix socket, if one is used. In Linux, read/write
|
||||
; permissions must be set in order to allow connections from a web server. Many
|
||||
; BSD-derived systems allow connections regardless of permissions. The owner
|
||||
; and group can be specified either by name or by their numeric IDs.
|
||||
; Default Values: Owner is set to the master process running user. If the group
|
||||
; is not set, the owner's group is used. Mode is set to 0660.
|
||||
;listen.owner = nobody
|
||||
;listen.group = nobody
|
||||
;listen.mode = 0660
|
||||
|
||||
; When POSIX Access Control Lists are supported you can set them using
|
||||
; these options, value is a comma separated list of user/group names.
|
||||
; When set, listen.owner and listen.group are ignored
|
||||
;listen.acl_users =
|
||||
;listen.acl_groups =
|
||||
|
||||
; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect.
|
||||
; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
|
||||
; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
|
||||
; must be separated by a comma. If this value is left blank, connections will be
|
||||
; accepted from any ip address.
|
||||
; Default Value: any
|
||||
;listen.allowed_clients = 127.0.0.1
|
||||
|
||||
; Set the associated the route table (FIB). FreeBSD only
|
||||
; Default Value: -1
|
||||
;listen.setfib = 1
|
||||
|
||||
; Specify the nice(2) priority to apply to the pool processes (only if set)
|
||||
; The value can vary from -19 (highest priority) to 20 (lower priority)
|
||||
; Note: - It will only work if the FPM master process is launched as root
|
||||
; - The pool processes will inherit the master process priority
|
||||
; unless it specified otherwise
|
||||
; Default Value: no set
|
||||
; process.priority = -19
|
||||
|
||||
; Set the process dumpable flag (PR_SET_DUMPABLE prctl for Linux or
|
||||
; PROC_TRACE_CTL procctl for FreeBSD) even if the process user
|
||||
; or group is different than the master process user. It allows to create process
|
||||
; core dump and ptrace the process for the pool user.
|
||||
; Default Value: no
|
||||
; process.dumpable = yes
|
||||
|
||||
; Choose how the process manager will control the number of child processes.
|
||||
; Possible Values:
|
||||
; static - a fixed number (pm.max_children) of child processes;
|
||||
; dynamic - the number of child processes are set dynamically based on the
|
||||
; following directives. With this process management, there will be
|
||||
; always at least 1 children.
|
||||
; pm.max_children - the maximum number of children that can
|
||||
; be alive at the same time.
|
||||
; pm.start_servers - the number of children created on startup.
|
||||
; pm.min_spare_servers - the minimum number of children in 'idle'
|
||||
; state (waiting to process). If the number
|
||||
; of 'idle' processes is less than this
|
||||
; number then some children will be created.
|
||||
; pm.max_spare_servers - the maximum number of children in 'idle'
|
||||
; state (waiting to process). If the number
|
||||
; of 'idle' processes is greater than this
|
||||
; number then some children will be killed.
|
||||
; pm.max_spawn_rate - the maximum number of rate to spawn child
|
||||
; processes at once.
|
||||
; ondemand - no children are created at startup. Children will be forked when
|
||||
; new requests will connect. The following parameter are used:
|
||||
; pm.max_children - the maximum number of children that
|
||||
; can be alive at the same time.
|
||||
; pm.process_idle_timeout - The number of seconds after which
|
||||
; an idle process will be killed.
|
||||
; Note: This value is mandatory.
|
||||
pm = dynamic
|
||||
|
||||
; The number of child processes to be created when pm is set to 'static' and the
|
||||
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
|
||||
; This value sets the limit on the number of simultaneous requests that will be
|
||||
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
|
||||
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
|
||||
; CGI. The below defaults are based on a server without much resources. Don't
|
||||
; forget to tweak pm.* to fit your needs.
|
||||
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
|
||||
; Note: This value is mandatory.
|
||||
pm.max_children = 5
|
||||
|
||||
; The number of child processes created on startup.
|
||||
; Note: Used only when pm is set to 'dynamic'
|
||||
; Default Value: (min_spare_servers + max_spare_servers) / 2
|
||||
pm.start_servers = 2
|
||||
|
||||
; The desired minimum number of idle server processes.
|
||||
; Note: Used only when pm is set to 'dynamic'
|
||||
; Note: Mandatory when pm is set to 'dynamic'
|
||||
pm.min_spare_servers = 1
|
||||
|
||||
; The desired maximum number of idle server processes.
|
||||
; Note: Used only when pm is set to 'dynamic'
|
||||
; Note: Mandatory when pm is set to 'dynamic'
|
||||
pm.max_spare_servers = 3
|
||||
|
||||
; The number of rate to spawn child processes at once.
|
||||
; Note: Used only when pm is set to 'dynamic'
|
||||
; Note: Mandatory when pm is set to 'dynamic'
|
||||
; Default Value: 32
|
||||
;pm.max_spawn_rate = 32
|
||||
|
||||
; The number of seconds after which an idle process will be killed.
|
||||
; Note: Used only when pm is set to 'ondemand'
|
||||
; Default Value: 10s
|
||||
;pm.process_idle_timeout = 10s;
|
||||
|
||||
; The number of requests each child process should execute before respawning.
|
||||
; This can be useful to work around memory leaks in 3rd party libraries. For
|
||||
; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
|
||||
; Default Value: 0
|
||||
;pm.max_requests = 500
|
||||
|
||||
; The URI to view the FPM status page. If this value is not set, no URI will be
|
||||
; recognized as a status page. It shows the following information:
|
||||
; pool - the name of the pool;
|
||||
; process manager - static, dynamic or ondemand;
|
||||
; start time - the date and time FPM has started;
|
||||
; start since - number of seconds since FPM has started;
|
||||
; accepted conn - the number of request accepted by the pool;
|
||||
; listen queue - the number of request in the queue of pending
|
||||
; connections (see backlog in listen(2));
|
||||
; max listen queue - the maximum number of requests in the queue
|
||||
; of pending connections since FPM has started;
|
||||
; listen queue len - the size of the socket queue of pending connections;
|
||||
; idle processes - the number of idle processes;
|
||||
; active processes - the number of active processes;
|
||||
; total processes - the number of idle + active processes;
|
||||
; max active processes - the maximum number of active processes since FPM
|
||||
; has started;
|
||||
; max children reached - number of times, the process limit has been reached,
|
||||
; when pm tries to start more children (works only for
|
||||
; pm 'dynamic' and 'ondemand');
|
||||
; Value are updated in real time.
|
||||
; Example output:
|
||||
; pool: www
|
||||
; process manager: static
|
||||
; start time: 01/Jul/2011:17:53:49 +0200
|
||||
; start since: 62636
|
||||
; accepted conn: 190460
|
||||
; listen queue: 0
|
||||
; max listen queue: 1
|
||||
; listen queue len: 42
|
||||
; idle processes: 4
|
||||
; active processes: 11
|
||||
; total processes: 15
|
||||
; max active processes: 12
|
||||
; max children reached: 0
|
||||
;
|
||||
; By default the status page output is formatted as text/plain. Passing either
|
||||
; 'html', 'xml' or 'json' in the query string will return the corresponding
|
||||
; output syntax. Example:
|
||||
; http://www.foo.bar/status
|
||||
; http://www.foo.bar/status?json
|
||||
; http://www.foo.bar/status?html
|
||||
; http://www.foo.bar/status?xml
|
||||
;
|
||||
; By default the status page only outputs short status. Passing 'full' in the
|
||||
; query string will also return status for each pool process.
|
||||
; Example:
|
||||
; http://www.foo.bar/status?full
|
||||
; http://www.foo.bar/status?json&full
|
||||
; http://www.foo.bar/status?html&full
|
||||
; http://www.foo.bar/status?xml&full
|
||||
; The Full status returns for each process:
|
||||
; pid - the PID of the process;
|
||||
; state - the state of the process (Idle, Running, ...);
|
||||
; start time - the date and time the process has started;
|
||||
; start since - the number of seconds since the process has started;
|
||||
; requests - the number of requests the process has served;
|
||||
; request duration - the duration in µs of the requests;
|
||||
; request method - the request method (GET, POST, ...);
|
||||
; request URI - the request URI with the query string;
|
||||
; content length - the content length of the request (only with POST);
|
||||
; user - the user (PHP_AUTH_USER) (or '-' if not set);
|
||||
; script - the main script called (or '-' if not set);
|
||||
; last request cpu - the %cpu the last request consumed
|
||||
; it's always 0 if the process is not in Idle state
|
||||
; because CPU calculation is done when the request
|
||||
; processing has terminated;
|
||||
; last request memory - the max amount of memory the last request consumed
|
||||
; it's always 0 if the process is not in Idle state
|
||||
; because memory calculation is done when the request
|
||||
; processing has terminated;
|
||||
; If the process is in Idle state, then information is related to the
|
||||
; last request the process has served. Otherwise information is related to
|
||||
; the current request being served.
|
||||
; Example output:
|
||||
; ************************
|
||||
; pid: 31330
|
||||
; state: Running
|
||||
; start time: 01/Jul/2011:17:53:49 +0200
|
||||
; start since: 63087
|
||||
; requests: 12808
|
||||
; request duration: 1250261
|
||||
; request method: GET
|
||||
; request URI: /test_mem.php?N=10000
|
||||
; content length: 0
|
||||
; user: -
|
||||
; script: /home/fat/web/docs/php/test_mem.php
|
||||
; last request cpu: 0.00
|
||||
; last request memory: 0
|
||||
;
|
||||
; Note: There is a real-time FPM status monitoring sample web page available
|
||||
; It's available in: /usr/share/php84/fpm/status.html
|
||||
;
|
||||
; Note: The value must start with a leading slash (/). The value can be
|
||||
; anything, but it may not be a good idea to use the .php extension or it
|
||||
; may conflict with a real PHP file.
|
||||
; Default Value: not set
|
||||
;pm.status_path = /status
|
||||
|
||||
; The address on which to accept FastCGI status request. This creates a new
|
||||
; invisible pool that can handle requests independently. This is useful
|
||||
; if the main pool is busy with long running requests because it is still possible
|
||||
; to get the status before finishing the long running requests.
|
||||
;
|
||||
; Valid syntaxes are:
|
||||
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
|
||||
; a specific port;
|
||||
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
|
||||
; a specific port;
|
||||
; 'port' - to listen on a TCP socket to all addresses
|
||||
; (IPv6 and IPv4-mapped) on a specific port;
|
||||
; '/path/to/unix/socket' - to listen on a unix socket.
|
||||
; Default Value: value of the listen option
|
||||
;pm.status_listen = 127.0.0.1:9001
|
||||
|
||||
; The ping URI to call the monitoring page of FPM. If this value is not set, no
|
||||
; URI will be recognized as a ping page. This could be used to test from outside
|
||||
; that FPM is alive and responding, or to
|
||||
; - create a graph of FPM availability (rrd or such);
|
||||
; - remove a server from a group if it is not responding (load balancing);
|
||||
; - trigger alerts for the operating team (24/7).
|
||||
; Note: The value must start with a leading slash (/). The value can be
|
||||
; anything, but it may not be a good idea to use the .php extension or it
|
||||
; may conflict with a real PHP file.
|
||||
; Default Value: not set
|
||||
;ping.path = /ping
|
||||
|
||||
; This directive may be used to customize the response of a ping request. The
|
||||
; response is formatted as text/plain with a 200 response code.
|
||||
; Default Value: pong
|
||||
;ping.response = pong
|
||||
|
||||
; The access log file
|
||||
; Default: not set
|
||||
;access.log = log/php84/$pool.access.log
|
||||
|
||||
; The access log format.
|
||||
; The following syntax is allowed
|
||||
; %%: the '%' character
|
||||
; %C: %CPU used by the request
|
||||
; it can accept the following format:
|
||||
; - %{user}C for user CPU only
|
||||
; - %{system}C for system CPU only
|
||||
; - %{total}C for user + system CPU (default)
|
||||
; %d: time taken to serve the request
|
||||
; it can accept the following format:
|
||||
; - %{seconds}d (default)
|
||||
; - %{milliseconds}d
|
||||
; - %{milli}d
|
||||
; - %{microseconds}d
|
||||
; - %{micro}d
|
||||
; %e: an environment variable (same as $_ENV or $_SERVER)
|
||||
; it must be associated with embraces to specify the name of the env
|
||||
; variable. Some examples:
|
||||
; - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e
|
||||
; - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e
|
||||
; %f: script filename
|
||||
; %l: content-length of the request (for POST request only)
|
||||
; %m: request method
|
||||
; %M: peak of memory allocated by PHP
|
||||
; it can accept the following format:
|
||||
; - %{bytes}M (default)
|
||||
; - %{kilobytes}M
|
||||
; - %{kilo}M
|
||||
; - %{megabytes}M
|
||||
; - %{mega}M
|
||||
; %n: pool name
|
||||
; %o: output header
|
||||
; it must be associated with embraces to specify the name of the header:
|
||||
; - %{Content-Type}o
|
||||
; - %{X-Powered-By}o
|
||||
; - %{Transfert-Encoding}o
|
||||
; - ....
|
||||
; %p: PID of the child that serviced the request
|
||||
; %P: PID of the parent of the child that serviced the request
|
||||
; %q: the query string
|
||||
; %Q: the '?' character if query string exists
|
||||
; %r: the request URI (without the query string, see %q and %Q)
|
||||
; %R: remote IP address
|
||||
; %s: status (response code)
|
||||
; %t: server time the request was received
|
||||
; it can accept a strftime(3) format:
|
||||
; %d/%b/%Y:%H:%M:%S %z (default)
|
||||
; The strftime(3) format must be encapsulated in a %{<strftime_format>}t tag
|
||||
; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
|
||||
; %T: time the log has been written (the request has finished)
|
||||
; it can accept a strftime(3) format:
|
||||
; %d/%b/%Y:%H:%M:%S %z (default)
|
||||
; The strftime(3) format must be encapsulated in a %{<strftime_format>}t tag
|
||||
; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
|
||||
; %u: basic auth user if specified in Authorization header
|
||||
;
|
||||
; Default: "%R - %u %t \"%m %r\" %s"
|
||||
;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{milli}d %{kilo}M %C%%"
|
||||
|
||||
; A list of request_uri values which should be filtered from the access log.
|
||||
;
|
||||
; As a security precaution, this setting will be ignored if:
|
||||
; - the request method is not GET or HEAD; or
|
||||
; - there is a request body; or
|
||||
; - there are query parameters; or
|
||||
; - the response code is outwith the successful range of 200 to 299
|
||||
;
|
||||
; Note: The paths are matched against the output of the access.format tag "%r".
|
||||
; On common configurations, this may look more like SCRIPT_NAME than the
|
||||
; expected pre-rewrite URI.
|
||||
;
|
||||
; Default Value: not set
|
||||
;access.suppress_path[] = /ping
|
||||
;access.suppress_path[] = /health_check.php
|
||||
|
||||
; The log file for slow requests
|
||||
; Default Value: not set
|
||||
; Note: slowlog is mandatory if request_slowlog_timeout is set
|
||||
;slowlog = log/php84/$pool.slow.log
|
||||
|
||||
; The timeout for serving a single request after which a PHP backtrace will be
|
||||
; dumped to the 'slowlog' file. A value of '0s' means 'off'.
|
||||
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
|
||||
; Default Value: 0
|
||||
;request_slowlog_timeout = 0
|
||||
|
||||
; Depth of slow log stack trace.
|
||||
; Default Value: 20
|
||||
;request_slowlog_trace_depth = 20
|
||||
|
||||
; The timeout for serving a single request after which the worker process will
|
||||
; be killed. This option should be used when the 'max_execution_time' ini option
|
||||
; does not stop script execution for some reason. A value of '0' means 'off'.
|
||||
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
|
||||
; Default Value: 0
|
||||
;request_terminate_timeout = 0
|
||||
|
||||
; The timeout set by 'request_terminate_timeout' ini option is not engaged after
|
||||
; application calls 'fastcgi_finish_request' or when application has finished and
|
||||
; shutdown functions are being called (registered via register_shutdown_function).
|
||||
; This option will enable timeout limit to be applied unconditionally
|
||||
; even in such cases.
|
||||
; Default Value: no
|
||||
;request_terminate_timeout_track_finished = no
|
||||
|
||||
; Set open file descriptor rlimit.
|
||||
; Default Value: system defined value
|
||||
;rlimit_files = 1024
|
||||
|
||||
; Set max core size rlimit.
|
||||
; Possible Values: 'unlimited' or an integer greater or equal to 0
|
||||
; Default Value: system defined value
|
||||
;rlimit_core = 0
|
||||
|
||||
; Chroot to this directory at the start. This value must be defined as an
|
||||
; absolute path. When this value is not set, chroot is not used.
|
||||
; Note: you can prefix with '$prefix' to chroot to the pool prefix or one
|
||||
; of its subdirectories. If the pool prefix is not set, the global prefix
|
||||
; will be used instead.
|
||||
; Note: chrooting is a great security feature and should be used whenever
|
||||
; possible. However, all PHP paths will be relative to the chroot
|
||||
; (error_log, sessions.save_path, ...).
|
||||
; Default Value: not set
|
||||
;chroot =
|
||||
|
||||
; Chdir to this directory at the start.
|
||||
; Note: relative path can be used.
|
||||
; Default Value: current directory or / when chroot
|
||||
;chdir = /var/www
|
||||
|
||||
; Redirect worker stdout and stderr into main error log. If not set, stdout and
|
||||
; stderr will be redirected to /dev/null according to FastCGI specs.
|
||||
; Note: on highloaded environment, this can cause some delay in the page
|
||||
; process time (several ms).
|
||||
; Default Value: no
|
||||
;catch_workers_output = yes
|
||||
|
||||
; Decorate worker output with prefix and suffix containing information about
|
||||
; the child that writes to the log and if stdout or stderr is used as well as
|
||||
; log level and time. This options is used only if catch_workers_output is yes.
|
||||
; Settings to "no" will output data as written to the stdout or stderr.
|
||||
; Default value: yes
|
||||
;decorate_workers_output = no
|
||||
|
||||
; Clear environment in FPM workers
|
||||
; Prevents arbitrary environment variables from reaching FPM worker processes
|
||||
; by clearing the environment in workers before env vars specified in this
|
||||
; pool configuration are added.
|
||||
; Setting to "no" will make all environment variables available to PHP code
|
||||
; via getenv(), $_ENV and $_SERVER.
|
||||
; Default Value: yes
|
||||
;clear_env = no
|
||||
|
||||
; Limits the extensions of the main script FPM will allow to parse. This can
|
||||
; prevent configuration mistakes on the web server side. You should only limit
|
||||
; FPM to .php extensions to prevent malicious users to use other extensions to
|
||||
; execute php code.
|
||||
; Note: set an empty value to allow all extensions.
|
||||
; Default Value: .php
|
||||
;security.limit_extensions = .php .php3 .php4 .php5 .php7
|
||||
|
||||
; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from
|
||||
; the current environment.
|
||||
; Default Value: clean env
|
||||
;env[HOSTNAME] = $HOSTNAME
|
||||
;env[PATH] = /usr/local/bin:/usr/bin:/bin
|
||||
;env[TMP] = /tmp
|
||||
;env[TMPDIR] = /tmp
|
||||
;env[TEMP] = /tmp
|
||||
|
||||
; Additional php.ini defines, specific to this pool of workers. These settings
|
||||
; overwrite the values previously defined in the php.ini. The directives are the
|
||||
; same as the PHP SAPI:
|
||||
; php_value/php_flag - you can set classic ini defines which can
|
||||
; be overwritten from PHP call 'ini_set'.
|
||||
; php_admin_value/php_admin_flag - these directives won't be overwritten by
|
||||
; PHP call 'ini_set'
|
||||
; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no.
|
||||
|
||||
; Defining 'extension' will load the corresponding shared extension from
|
||||
; extension_dir. Defining 'disable_functions' or 'disable_classes' will not
|
||||
; overwrite previously defined php.ini values, but will append the new value
|
||||
; instead.
|
||||
|
||||
; Note: path INI options can be relative and will be expanded with the prefix
|
||||
; (pool, global or /usr)
|
||||
|
||||
; Default Value: nothing is defined by default except the values in php.ini and
|
||||
; specified at startup with the -d argument
|
||||
;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
|
||||
;php_flag[display_errors] = off
|
||||
;php_admin_value[error_log] = /var/log/php84/$pool.error.log
|
||||
;php_admin_flag[log_errors] = on
|
||||
;php_admin_value[memory_limit] = 32M
|
||||
203
build/nginx/entrypoint.sh
Executable file
203
build/nginx/entrypoint.sh
Executable file
@@ -0,0 +1,203 @@
|
||||
#!/bin/bash
|
||||
|
||||
# constants
|
||||
dataBaseFile=database/sqlite/database.sqlite
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# functions
|
||||
|
||||
# Creating storage folders
|
||||
function create_storage_folders {
|
||||
|
||||
# create storage folders for laravel
|
||||
mkdir -p ./storage/framework/{cache,sessions,testing,views} || {
|
||||
# fail if something went wrong
|
||||
php artisan cli:error "Making storage folders failed!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# create log folder
|
||||
mkdir -p ./storage/logs || {
|
||||
# fail if something went wrong
|
||||
php artisan cli:error "Making log folder failed!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Initalizing caches
|
||||
function initalize_cache {
|
||||
|
||||
# populating/updating caches
|
||||
php artisan optimize \
|
||||
&& php artisan filament:cache-components || {
|
||||
# fail if something went wrong
|
||||
php artisan cli:error "Clearing cache failed!"
|
||||
exit 5
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# initialize environment file
|
||||
function initalize_environment_file() {
|
||||
|
||||
# if the .env doesn't exists, create it
|
||||
[[ ! -f .env ]] && {
|
||||
|
||||
# fail if environment cofig doesn't exists
|
||||
[[ -f .env.config ]] || {
|
||||
php artisan cli:error "Please provide a \".env.config\" file!"
|
||||
exit 7
|
||||
}
|
||||
|
||||
# fail if environment cofig is not readable
|
||||
[[ -r .env.config ]] || {
|
||||
php artisan cli:error "Cannot read \".env.config\" file! Please check permissions!"
|
||||
exit 7
|
||||
}
|
||||
|
||||
# create the custom environment file
|
||||
cp .env.config .env
|
||||
return
|
||||
}
|
||||
|
||||
# if .env already exists and no config file is found, nothing to do
|
||||
[[ ! -f .env.config ]] && {
|
||||
return
|
||||
}
|
||||
|
||||
# if the config file has ben updated since creating the .env file
|
||||
[[ ".env.config" -nt ".env" ]] && {
|
||||
|
||||
# backup the current configuration
|
||||
cp .env .env.old
|
||||
|
||||
# fail if environment cofig isn't readable
|
||||
[[ -r .env.config ]] || {
|
||||
php artisan cli:error "Cannot read \".env.config\" file! Please check permissions!"
|
||||
exit 7
|
||||
}
|
||||
|
||||
# create our custom environment file
|
||||
cp .env.config .env
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# create and save a new encryption key if it does not exists
|
||||
function generate_application_key {
|
||||
|
||||
php artisan state:initialized --silent || {
|
||||
|
||||
php artisan cli:info "Running for the forst time, generating application key"
|
||||
|
||||
local key=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64) \
|
||||
&& sed s~APP_KEY=.*\$~APP_KEY\=base64:$key~ .env > /tmp/.env \
|
||||
&& cat /tmp/.env > .env || {
|
||||
# fail if something went wrong
|
||||
php artisan cli:error "Generating the encription key failed!"
|
||||
exit 2
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# run database migration
|
||||
function migrate_database {
|
||||
|
||||
# fail if the database file is not writeable
|
||||
[[ ! -w $dataBaseFile ]] && {
|
||||
php artisan cli:error "Cannot write databese! Please check permissions!"
|
||||
exit 3
|
||||
}
|
||||
|
||||
# create a backup - NOTE: this may not be a good practice...
|
||||
cp $dataBaseFile $dataBaseFile.bak
|
||||
|
||||
php artisan migrate --force || {
|
||||
# fail if something went wrong
|
||||
php artisan cli:error "Migration failed! See storage/logs/laravel.log for more info."
|
||||
exit 3
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# run databesa seeding
|
||||
function seed_database {
|
||||
|
||||
#NOTE: this only creates the admin user if it does not exists yet
|
||||
php artisan db:seed --force || {
|
||||
# fail if something went wrong
|
||||
php artisan cli:error "Seeding failed! See storage/logs/laravel.log for more info."
|
||||
exit 4
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# start a supervisord process
|
||||
# @internal
|
||||
# @param string $1 command name example: "bash"
|
||||
# @param string $2 command display name example: "Bourne Again Shell"
|
||||
function start_process {
|
||||
|
||||
# variables
|
||||
local process=$1
|
||||
local process_name=$2
|
||||
|
||||
# log message
|
||||
php artisan cli:info "Starting ${process_name} process..."
|
||||
|
||||
# start the process
|
||||
supervisorctl start ${process} || {
|
||||
# log and fail if the process creation failed
|
||||
php artisan cli:error "Could not start ${process_name} process, exiting :("
|
||||
supervisorctl shutdown
|
||||
exit 99 # this should never be called
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# start supervisord defined server processes
|
||||
function start_server_processes {
|
||||
|
||||
# start php
|
||||
start_process "php-fpm" "PHP-Fpm"
|
||||
|
||||
# start nginx
|
||||
start_process "nginx" "Nginx"
|
||||
|
||||
# start schedule:work
|
||||
start_process "scheduler" "Scheduler"
|
||||
|
||||
}
|
||||
|
||||
# ---- main -----------------------------------------------------------------
|
||||
|
||||
# Initalizing the environment file
|
||||
initalize_environment_file
|
||||
|
||||
# Creating storage folders
|
||||
create_storage_folders
|
||||
|
||||
# generating application encryption key
|
||||
generate_application_key
|
||||
|
||||
# migrating the database
|
||||
migrate_database
|
||||
|
||||
# seed the database (if not already seeded)
|
||||
seed_database
|
||||
|
||||
# clear cache on (re)start
|
||||
initalize_cache
|
||||
|
||||
# adding notice to the app log
|
||||
echo -e "$(date +'[%Y-%m-%d %H:%M:%S]') production.NOTICE: Entrypoint reached.\n" >> storage/logs/laravel.log
|
||||
|
||||
# starting server processes
|
||||
start_server_processes
|
||||
|
||||
# confirm successfull program startup
|
||||
php artisan cli:info "Program stared succesfully."
|
||||
|
||||
exit 0
|
||||
141
readme.md
Normal file
141
readme.md
Normal file
@@ -0,0 +1,141 @@
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
# Proxima Backend
|
||||
|
||||
[Proxima](https://proxima.goliath.hu) is a **Remainder Application**, like many others, which primarily uses [Discord](https://discord.com/) to deliver the remainders to the user.
|
||||
|
||||
It provides an API interface to be embedded in other services, using Bearer Token Authentication.
|
||||
|
||||
This is the source code for the backend application.
|
||||
|
||||
This backend handles all the data managment and provides the API interface for the clients to be used.
|
||||
|
||||
This backend application is intended to be run as a docker container, see below.
|
||||
|
||||
For more details, see: [Proxima -> Backend](https://proxima.goliath.hu#backend)
|
||||
|
||||
# Main Technologies used
|
||||
- [PHP 8.4+](https://www.php.net/)
|
||||
- [composer](https://getcomposer.org/)
|
||||
- [Laravel 11+ ](https://laravel.com/)
|
||||
- [Sanctum](https://laravel.com/docs/11.x/sanctum)
|
||||
- [Livevire](https://laravel-livewire.com/)
|
||||
- [Filament 3+](https://filamentphp.com/)
|
||||
- [Scribe](https://scribe.knuckles.wtf/laravel)
|
||||
- and many more...
|
||||
|
||||
# What is needed to run the backend
|
||||
|
||||
- A working docker instance with compose.
|
||||
|
||||
# Runninig with docker
|
||||
|
||||
### Follow these easy steps to setup and run the backend application:
|
||||
|
||||
> **NOTE** The program inside the container is running under a **non-privileged** user with
|
||||
>
|
||||
> `UID:1000` and `GID:1000`.
|
||||
>
|
||||
> The database file/folder must be writable for that user!
|
||||
>
|
||||
> In the example we just make it writable to everyone (please use proper access control instead!)
|
||||
>
|
||||
> *Or jou can build your own image with different `UID`/`GID` values.*
|
||||
|
||||
#### 1. Create the directory structure (bash):
|
||||
|
||||
```bash
|
||||
mkdir -p volumes/backend/{database,env}
|
||||
```
|
||||
|
||||
#### 2. Update directory permissions to allow write access to the restricted docker user
|
||||
|
||||
```bash
|
||||
chmod o+w volumes/backend/database
|
||||
```
|
||||
|
||||
#### 3. (OPTIONAL) If you already have a working database file, you can copy it to the app:
|
||||
|
||||
```bash
|
||||
cp PATH/TO/DATABASE/<database.sqlite> volumes/backend/database/
|
||||
chmod o+w volumes/backend/database/database.sqlite
|
||||
```
|
||||
|
||||
#### 4. Create the `.env.config` file from [src/.env.example](src/.env.example) file.
|
||||
|
||||
```bash
|
||||
wget -O volumes/backend/env/.env.config https://proxima.goliath.hu/proxima/backend/raw/branch/main/src/.env.example
|
||||
|
||||
-OR-
|
||||
|
||||
curl -f -o volumes/backend/env/.env.config https://proxima.goliath.hu/proxima/backend/raw/branch/main/src/.env.example
|
||||
|
||||
```
|
||||
#### 5. Customize the `.env.config` file.
|
||||
|
||||
> **NOTE**: The program creates its own `.env` file based on the `.env.config` file.<br>
|
||||
> If you modify your `.env.config` file, simply restart the container and the new settings will take effect immediately.
|
||||
|
||||
```bash
|
||||
nano volumes/backend/env/.env.config
|
||||
```
|
||||
1. Replace or fill in the following values (after the "=" sign)
|
||||
|
||||
1. **APP_NAME** - The application's name <br>
|
||||
*defualt:* "`Backend`"
|
||||
1. **APP_TIMEZONE** - The application's timezone<br>
|
||||
*default:* "`UTC`"
|
||||
1. **APP_URL** - The application's url<br>
|
||||
*default:* "`localhost:9000`"
|
||||
1. **ADMIN_EMAIL** - The admin email for the backend<br>
|
||||
*default:* "`admin@example.com`"
|
||||
1. **ADMIN_PASSWORD** - The admin password for the backend<br>
|
||||
*default:* "`PlEaSe_ChAnGe_Me`"
|
||||
|
||||
#### 6.1 Run with docker
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
-it \
|
||||
--rm \
|
||||
--name "proxima-backend" \
|
||||
-v "./volumes/backend/env/.env.config:/app/Backend/.env.config" \
|
||||
-v "./volumes/backend/database/:/app/Backend/database/sqlite/" \
|
||||
-p "9000:9000" \
|
||||
git.magrathea.hu/proxima/backend:latest
|
||||
```
|
||||
|
||||
#### 6.2 Running with docker compose
|
||||
|
||||
#### 6.2.1 Create docker compose file `docker-compose.yaml`:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
backend:
|
||||
image: git.magrathea.hu/proxima/backend:latest
|
||||
container_name: backend
|
||||
ports:
|
||||
- "9000:9000"
|
||||
volumes:
|
||||
- "./volumes/backend/env/.env.config:/app/Backend/.env.config"
|
||||
- "./volumes/backend/database/:/app/Backend/database/sqlite/"
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
#### 6.2.2 Start up service
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
#### 6.3 You can watch the logs of the application
|
||||
|
||||
```bash
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
|
||||
706
res/logo.svg
Normal file
706
res/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 29 KiB |
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',
|
||||
];
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user