first commit

This commit is contained in:
JOSEPH LE 2024-05-13 15:49:01 +07:00
commit 839bbbac82
1069 changed files with 131413 additions and 0 deletions

82
BACKEND/.doc/nginx.conf Normal file
View File

@ -0,0 +1,82 @@
upstream fastcgi_backend {
server unix:/run/php/php8.1-fpm.sock;
}
#server {
# listen 8080;
#server_name mag1.kentest.xyz;
#server_name ipsupply.com.au www.ipsupply.com.au;
# server_name prology.net;
# set $MAGE_ROOT /var/www/html/ipsupply_prod;
# set $MAGE_RUN_TYPE website;
# set $MAGE_MODE developer;
# include /var/www/html/ipsupply_prod/nginx.conf.sample;
#}
#------------------------------------------------------------------
server {
listen 80;
listen [::]:80;
# server_name mag1.kentest.xyz;
#return 301 https://mag1.kentest.xyz$request_uri;
server_name gtc.nswteam.net;
root /var/www/html/gtc/public; # Path to your Laravel public directory
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri /index.php =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass fastcgi_backend; # Adjust the PHP version and socket path as needed
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
#server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# server_name mag1.kentest.xyz;
#ssl on;
# ssl_certificate /etc/letsencrypt/live/mag1.kentest.xyz/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/mag1.kentest.xyz/privkey.pem;
##
# set $MAGE_ROOT /var/www/html/ipsupply_prod;
# include /var/www/html/ipsupply_prod/nginx.conf.sample;
##
# location / {
#try_files $uri $uri/ /index.php$is_args$args;
# proxy_pass http://127.0.0.1:80;
# proxy_set_header Host $host;
# proxy_set_header X-Forwarded-Host $http_host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header Ssl-Offloaded "1";
# proxy_set_header X-Forwarded-Port 443;
# proxy_set_header X-Forwarded-Proto https;
# proxy_set_header X-Forwarded-Ssl on;
# proxy_buffer_size 128k;
# proxy_buffers 4 256k;
# proxy_busy_buffers_size 256k;
# fastcgi_buffer_size 32k;
# fastcgi_buffers 4 32k;
# }
#}

View File

@ -0,0 +1,8 @@
<VirtualHost *:80>
DocumentRoot "/var/www/html/public"
<Directory "/var/www/html/public">
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>

2
BACKEND/.docker/logs/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

18
BACKEND/.editorconfig Normal file
View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

77
BACKEND/.env.dev Normal file
View File

@ -0,0 +1,77 @@
APP_NAME=Payment
APP_ENV=production
APP_KEY=base64:Tj1+yM1P63LHIkEX0tNUW1wK9ZrGu8uenPhyhP1knBo=
APP_DEBUG=true
APP_URL=https://payment.nswteam.net
ADMIN_URL=https://adminpayment.nswteam.net
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=gtc
DB_USERNAME=worm
DB_PASSWORD=123456
BROADCAST_DRIVER=log
CACHE_DRIVER=redis
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
# Info PAYAPAL
# @auth: Joseph
# @since: 26/12/2023
#
# sandbox personal
# EMAIL=sb-krlhv27694898@personal.example.com
# PASSWORD=8!P_^{/q
#
# sandbox business
# EMAIL=sb-ufjry27694755@business.example.com
# PASSWORD=k"pS1-WE
PAYPAL_SANDBOX_CLIENT_ID=Aerhn4xoYaNeoOCUZqHRjOjV1nwpd6YsSItupNNhYEuRF2k_Jm9ID0SfAb4fey-1BFto1Evw2Fzw91SA
PAYPAL_SANDBOX_CLIENT_SECRET=EJuwDMec6pMR3ULuSd36lHHw38OlPtjBUEvDUl_uJ6_bWwcvcGvezJp3E6QSmKLlBMoua8GcQiUaqlvx
PAYPAL_MODE=sandbox
SN_SERVER_TOKEN="Work1234"

64
BACKEND/.env.docker Normal file
View File

@ -0,0 +1,64 @@
APP_PORT=8000
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:${APP_PORT}
ADMIN_URL=http://localhost:5173
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
# localhost for docker
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=payment
DB_USERNAME=laravel
DB_PASSWORD=laravel
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="naviclound@apachtech.io"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
SN_SERVER_TOKEN="Work1234"

62
BACKEND/.env.example Normal file
View File

@ -0,0 +1,62 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000
ADMIN_URL=http://localhost:5173
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
SN_SERVER_TOKEN="Work1234"

11
BACKEND/.gitattributes vendored Normal file
View 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

30
BACKEND/.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
/.fleet
/.idea
/.vscode
/.env
/public/medias
/public/pdf
/public/mp3
/public/mp4
/server.ini
/public/documentFile
/public/documentsFile
/public/document
/public/courseImage
/package-lock.json

71
BACKEND/Dockerfile Normal file
View File

@ -0,0 +1,71 @@
FROM --platform=linux/amd64 alpine:3.19.0
# Update package
RUN apk update --no-cache
RUN apk add --no-cache \
bash \
git \
vim \
zip
# APACHE2
RUN apk add --no-cache \
build-base \
autoconf \
curl \
tar \
libtool \
pcre-dev \
libxml2-dev \
openssl \
bison \
re2c \
file \
libressl-dev
# NOTE: APACHE
RUN apk add --no-cache \
php$phpverx-apache2 \
apache2 \
apache2-utils \
apache2-ssl
# config httpd.conf
RUN sed -i '/LoadModule rewrite_module/s/^#//g' /etc/apache2/httpd.conf
# NOTE: PHP
RUN apk add --no-cache \
composer \
php82-common \
php82-zip \
php82-curl \
php82-pdo \
php82-pdo_mysql \
php82-mysqli \
php82-openssl \
php82-mbstring \
php82-zip \
php82-bcmath \
php82-ctype \
php82-dom \
php82-gd \
php82-intl \
php82-simplexml \
php82-soap \
php82-sodium \
php82-xsl \
php82-tokenizer \
php82-fileinfo \
php82-sockets \
php82-xml \
php82-xmlwriter \
php82-session \
php82-apache2
# config php.ini
RUN sed -i 's/memory_limit = 128M/memory_limit = -1/' /etc/php82/php.ini
# Working file
WORKDIR /var/www/html
COPY ./docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]

View File

@ -0,0 +1,46 @@
# @name login
POST http://localhost:8000/api/v1/admin/login
Content-Type: application/json
Accept: application/json
{
"email": "admin@apactech.io",
"password": "Pay@2023"
}
###
GET http://localhost:8000/api/v1/admin/detail
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{login.token}}
###
GET http://localhost:8000/api/v1/admin/logout
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{login.token}}
###
GET http://localhost:8000/api/v1/admin/forgot-password?email=admin@apactech.io
Content-Type: application/json
Accept: application/json
###
POST http://localhost:8000/api/v1/admin/reset-password
Content-Type: application/json
Accept: application/json
{
"forgot_code": "RXTrVZBXqrdU9bW7",
"new_password": "Change@12345",
"confirm_password": "Change@12345"
}
###
POST http://localhost:8000/api/v1/admin/change-password
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{login.token}}
{
"password": "Pay@2023",
"new_password": "Change@12345",
"confirm_password": "Change@12345"
}

View File

@ -0,0 +1,26 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAzNjYxNDkxLCJleHAiOjE3MDM3NDc4OTEsIm5iZiI6MTcwMzY2MTQ5MSwianRpIjoiMEJzRnplRnZsbEh2bHhUUiIsInN1YiI6IjEiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0._9GTM3AmCbovzp5Mq-4ZbNYr0z1ncfF9HIeaTRzsrZA
###
POST http://localhost:8000/api/v1/admin/banner/create
Authorization: Bearer {{token}}
Content-Type: multipart/form-data; boundary=boundary123
Accept: Application/json
--boundary123
Content-Disposition: form-data; name="title"
Banner 1
--boundary123
Content-Disposition: form-data; name="link"
https://google.com
--boundary123
Content-Disposition: form-data; name="is_active"
false
--boundary123
Content-Disposition: form-data; name="image"; filename="code.png"
Content-Type: image/png
< /mnt/c/Users/Administrator/Pictures/avatar.png
# Windows: C:/Users/{Username}/Pictures/logo.png
--boundary123--

View File

@ -0,0 +1,30 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAzNjYxNDkxLCJleHAiOjE3MDM3NDc4OTEsIm5iZiI6MTcwMzY2MTQ5MSwianRpIjoiMEJzRnplRnZsbEh2bHhUUiIsInN1YiI6IjEiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0._9GTM3AmCbovzp5Mq-4ZbNYr0z1ncfF9HIeaTRzsrZA
###
POST http://localhost:8000/api/v1/admin/banner/update
Authorization: Bearer {{token}}
Content-Type: multipart/form-data; boundary=boundary123
Accept: Application/json
--boundary123
Content-Disposition: form-data; name="id"
2
--boundary123
Content-Disposition: form-data; name="title"
Banner 1
--boundary123
Content-Disposition: form-data; name="link"
https://google.com
--boundary123
Content-Disposition: form-data; name="is_active"
true
--boundary123
Content-Disposition: form-data; name="image"; filename="code.png"
Content-Type: image/png
< /mnt/c/Users/Administrator/Pictures/code.png
# Windows: C:/Users/{Username}/Pictures/logo.png
--boundary123--

View File

@ -0,0 +1,43 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAzNjYxNDkxLCJleHAiOjE3MDM3NDc4OTEsIm5iZiI6MTcwMzY2MTQ5MSwianRpIjoiMEJzRnplRnZsbEh2bHhUUiIsInN1YiI6IjEiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0._9GTM3AmCbovzp5Mq-4ZbNYr0z1ncfF9HIeaTRzsrZA
###
GET http://localhost:8000/api/v1/admin/banner/all
Accept: application/json
Content-Type: application/json
Authorization: Bearer {{token}}
###
GET http://localhost:8000/api/v1/admin/banner/delete?id=1
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
###
POST http://localhost:8000/api/v1/admin/banner/updates
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
{
"banners": [
{
"id": 1,
"name": "Richmond Okuneva",
"phone": "+1-307-367-8793",
"company": "Larson Ltd",
"country_code": "VN"
}
]
}
###
POST http://localhost:8000/api/v1/admin/banner/deletes
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
{
"banners": [
{
"id": 1
},
{
"id": 2
}
]
}

View File

@ -0,0 +1,34 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAzNTc0OTU4LCJleHAiOjE3MDM2NjEzNTgsIm5iZiI6MTcwMzU3NDk1OCwianRpIjoiSnQyZDJBT3lCR3MxV2JpUiIsInN1YiI6IjEiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0.x-G1dLHaSkgGlhuhXubnKnnd1HAQ2KPUwPcdkEYe2-A
###
POST http://localhost:8000/api/v1/admin/client/create
Authorization: Bearer {{token}}
Content-Type: multipart/form-data; boundary=boundary123
Accept: Application/json
--boundary123
Content-Disposition: form-data; name="name"
Kai 1
--boundary123
Content-Disposition: form-data; name="password"
Pay@2023
--boundary123
Content-Disposition: form-data; name="country_code"
VN
--boundary123
Content-Disposition: form-data; name="email"
kai.t1@apactech.io
--boundary123
Content-Disposition: form-data; name="phone"
01234567890
--boundary123
Content-Disposition: form-data; name="avatar"; filename="code.png"
Content-Type: image/png
< /mnt/c/Users/Administrator/Pictures/code.png
# Windows: C:/Users/{Username}/Pictures/logo.png
--boundary123--

View File

@ -0,0 +1,49 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAzNTUyMjM3LCJleHAiOjE3MDM2Mzg2MzcsIm5iZiI6MTcwMzU1MjIzNywianRpIjoiNjIzWmtUVG1jNnlOZEZ0ZSIsInN1YiI6IjUiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0._NIM-6oICYVbjxRwBTt8rFesXZli2W3bd8XCHFyd5Qw
###
POST http://localhost:8000/api/v1/admin/client/update
Authorization: Bearer {{token}}
Content-Type: multipart/form-data; boundary=boundary123
Accept: Application/json
--boundary123
Content-Disposition: form-data; name="id"
500
--boundary123
Content-Disposition: form-data; name="name"
Kai 6 edited
# --boundary123
# Content-Disposition: form-data; name="password"
# Pay@2023
--boundary123
Content-Disposition: form-data; name="country_code"
VN
# --boundary123
# Content-Disposition: form-data; name="email"
# kai.t6@apactech.io
--boundary123
Content-Disposition: form-data; name="phone"
01234567890
# --boundary123
# Content-Disposition: form-data; name="avatar"; filename="code.png"
# Content-Type: image/png
# < /mnt/c/Users/Administrator/Pictures/code.PNG
# # Windows: C:/Users/{Username}/Pictures/logo.png
--boundary123--
HTTP/1.1 200 - OK
host: localhost:8000
date: Wed, 27 Dec 2023 00:50:47 GMT, Wed, 27 Dec 2023 00:50:47 GMT
connection: close
x-powered-by: PHP/8.2.11
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
access-control-allow-origin: *

View File

@ -0,0 +1,56 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAzNTUyMjM3LCJleHAiOjE3MDM2Mzg2MzcsIm5iZiI6MTcwMzU1MjIzNywianRpIjoiNjIzWmtUVG1jNnlOZEZ0ZSIsInN1YiI6IjUiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0._NIM-6oICYVbjxRwBTt8rFesXZli2W3bd8XCHFyd5Qw
###
GET http://localhost:8000/api/v1/admin/client/get
?page=1
# @eg. 1|0|true|false
Accept: application/json
Content-Type: application/json
Authorization: Bearer {{token}}
HTTP/1.1 200 - OK
host: localhost:8000
date: Tue, 26 Dec 2023 10:27:57 GMT, Tue, 26 Dec 2023 10:27:57 GMT
connection: close
x-powered-by: PHP/8.2.11
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 60
x-ratelimit-remaining: 58
phpdebugbar-id: X9ba23f0265242b1d21d1f7aa5735b53c
access-control-allow-origin: *
###
GET http://localhost:8000/api/v1/admin/client/delete?id=1
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
###
POST http://localhost:8000/api/v1/admin/client/updates
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
{
"clients": [
{
"id": 1,
"name": "Richmond Okuneva",
"phone": "+1-307-367-8793",
"company": "Larson Ltd",
"country_code": "VN"
}
]
}
###
POST http://localhost:8000/api/v1/admin/client/deletes
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
{
"clients": [
{
"id": 1
},
{
"id": 2
}
]
}

View File

@ -0,0 +1,23 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAyNTIwNjAyLCJleHAiOjE3MDI2MDcwMDIsIm5iZiI6MTcwMjUyMDYwMiwianRpIjoidHE0VmhZSzhDMGY4cGpLZSIsInN1YiI6IjEiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0.9GTTVHlj93B3gXGZ1nmFH6VbwoJT7ll1Ll-TtytpTWMs
###
GET http://localhost:8000/api/v1/admin/custom-theme
Content-Type: multipart/form-data; boundary=boundary123
Accept: Application/json
Authorization: Bearer {{token}}
###
POST http://localhost:8000/api/v1/admin/custom-theme
Authorization: Bearer {{token}}
Content-Type: multipart/form-data; boundary=boundary123
Accept: Application/json
--boundary123
Content-Disposition: form-data; name="js"
console.log('hello')
--boundary123
Content-Disposition: form-data; name="css"
body {
background-color: red !important
}
--boundary123--

View File

@ -0,0 +1,26 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vYXBhYy5sb2NhbDo4MDAwL2FwaS92MS9hZG1pbi9sb2dpbiIsImlhdCI6MTcwNjU4MTI2NywiZXhwIjoxNzA2NjY3NjY3LCJuYmYiOjE3MDY1ODEyNjcsImp0aSI6Ik54ZHhveWVnT2sydlVtU2EiLCJzdWIiOiI1IiwicHJ2IjoiZDJmZjI5MzM5YThhM2U4MmMzNTgyYTVhOGU3MzlkZjE3ODliYjEyZiJ9.d-R6ZSXNzUq_AXNZWYMIh3BfHWAtpirdrNyewd-c8As
###
GET http://localhost:8000/api/v1/admin/dashboard/statistics-sn-keywords-by-month
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
{
"month": 1,
"year": 2024,
"timezone_offset": "+01:00"
}
GET http://localhost:8000/api/v1/admin/dashboard/statistics-revenues-by-month
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
{
"month": 1,
"year": 2024,
"timezone_offset": "+01:00"
}

View File

@ -0,0 +1,75 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAzNTYzMjk1LCJleHAiOjE3MDM2NDk2OTUsIm5iZiI6MTcwMzU2MzI5NSwianRpIjoicEs0bGJxSEtEMTVwdndVYSIsInN1YiI6IjEiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0.VvhrVE8noDywaMJne17XyeDzpnJrQC-bVLHFgkDgAIk
###
GET http://localhost:8000/api/v1/admin/discount/get
?page=1
&from_date=1695307952
&to_date=1701635729
&order_by_id=desc
# &date_used=1702615520
# &code=123456
# &status=0
# @eg. status=0|1
# &email=quitz
# @eg. email=admin@gmail.com
# &value=
&discount_type_id=1
# @eg. discount_type_id= 1|2 # 1: price, 2: percent
# @fetch. "http://localhost:8000/api/v1/admin/discount-type/all"
&search=
# @eg. search=code|email|value
Accept: application/json
Content-Type: application/json
Authorization: Bearer {{token}}
###
POST http://localhost:8000/api/v1/admin/discount/create
Accept: application/json
Content-Type: application/json
Authorization: Bearer {{token}}
{
"discount_type_id": 2,
"value": "99"
}
###
POST http://localhost:8000/api/v1/admin/discount/update
Content-Type: application/json
Authorization: Bearer {{token}}
Accept: application/json
{
"id": 1,
"date_used": "1702866337"
}
###
GET http://localhost:8000/api/v1/admin/discount/delete?id=1
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
###
POST http://localhost:8000/api/v1/admin/discount/updates
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
{
"discounts": [
{
"id": 1,
"discount_type_id": 1,
"value": "100",
"status": "1"
}
]
}
###
POST http://localhost:8000/api/v1/admin/discount/deletes
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
{
"discounts": [
{
"id": 1
},
{
"id": 2
}
]
}

View File

@ -0,0 +1,6 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAyNTIwNjAyLCJleHAiOjE3MDI2MDcwMDIsIm5iZiI6MTcwMjUyMDYwMiwianRpIjoidHE0VmhZSzhDMGY4cGpLZSIsInN1YiI6IjEiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0.9GTTVHlj93B3gXGZ1nmFH6VbwoJT7ll1Ll-TtytpTWM
###
GET http://localhost:8000/api/v1/admin/discount-type/all
Accept: application/json
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@ -0,0 +1,18 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAzNTUyMjM3LCJleHAiOjE3MDM2Mzg2MzcsIm5iZiI6MTcwMzU1MjIzNywianRpIjoiNjIzWmtUVG1jNnlOZEZ0ZSIsInN1YiI6IjUiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0._NIM-6oICYVbjxRwBTt8rFesXZli2W3bd8XCHFyd5Qw
###
GET http://localhost:8000/api/v1/admin/h-country
Accept: application/json
Content-Type: application/json
Authorization: Bearer {{token}}
HTTP/1.1 200 - OK
host: localhost:8000
date: Tue, 26 Dec 2023 07:31:38 GMT, Tue, 26 Dec 2023 07:31:38 GMT
connection: close
x-powered-by: PHP/8.2.11
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
phpdebugbar-id: X0745a3d657f1a8203f21d9fa55d07f77
access-control-allow-origin: *

View File

@ -0,0 +1,60 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAzNTc0OTU4LCJleHAiOjE3MDM2NjEzNTgsIm5iZiI6MTcwMzU3NDk1OCwianRpIjoiSnQyZDJBT3lCR3MxV2JpUiIsInN1YiI6IjEiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0.x-G1dLHaSkgGlhuhXubnKnnd1HAQ2KPUwPcdkEYe2-A
###
GET http://localhost:8000/api/v1/admin/package/all
?order_by_point=desc
Accept: application/json
Content-Type: application/json
Authorization: Bearer {{token}}
POST http://localhost:8000/api/v1/admin/package/create
Accept: application/json
Content-Type: application/json
Authorization: Bearer {{token}}
{
"point": 1000000000000000000000000000000000,
"price": "1000000000000000000000000000000000000"
}
POST http://localhost:8000/api/v1/admin/package/update
Content-Type: application/json
Authorization: Bearer {{token}}
Accept: application/json
{
"id": 1,
"point": 100,
"price": 100
}
GET http://localhost:8000/api/v1/admin/package/delete?id=1
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
POST http://localhost:8000/api/v1/admin/package/updates
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
{
"packages": [
{
"id": 4,
"point": "100",
"price": "1000.50",
"status": "1"
},
{
"id": 5,
"status": "0"
}
]
}
POST http://localhost:8000/api/v1/admin/package/deletes
Content-Type: application/json
Accept: application/json
Authorization: Bearer {{token}}
{
"packages": [
{
"id": 2
},
{
"id": 3
}
]
}

View File

@ -0,0 +1,57 @@
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2FkbWluL2xvZ2luIiwiaWF0IjoxNzAyNTIwNjAyLCJleHAiOjE3MDI2MDcwMDIsIm5iZiI6MTcwMjUyMDYwMiwianRpIjoidHE0VmhZSzhDMGY4cGpLZSIsInN1YiI6IjEiLCJwcnYiOiJkMmZmMjkzMzlhOGEzZTgyYzM1ODJhNWE4ZTczOWRmMTc4OWJiMTJmIn0.9GTTVHlj93B3gXGZ1nmFH6VbwoJT7ll1Ll-TtytpTWM
###
POST http://localhost:8000/api/v1/admin/setting
Authorization: Bearer {{token}}
Content-Type: multipart/form-data; boundary=boundary123
Accept: Application/json
--boundary123
Content-Disposition: form-data; name="page_title"
Payment
--boundary123
Content-Disposition: form-data; name="email"
admin@apactech.io
--boundary123
Content-Disposition: form-data; name="phone"
01234567890
--boundary123
Content-Disposition: form-data; name="meta_title"
Payment Accumulate
--boundary123
Content-Disposition: form-data; name="meta_description"
Payment is the voluntary transfer of money,
--boundary123
Content-Disposition: form-data; name="meta_keyword"
payment accumulate
--boundary123
Content-Disposition: form-data; name="facebook"
https://facebook.com
--boundary123
Content-Disposition: form-data; name="twitter"
https://twitter.com
--boundary123
Content-Disposition: form-data; name="linkedin"
https://lnkedin.com
--boundary123
Content-Disposition: form-data; name="logo"; filename="logo.png"
Content-Type: image/png
--boundary123
Content-Disposition: form-data; name="address"
APAC, Hung Loi, Ninh Kieu, Can Tho
--boundary123
Content-Disposition: form-data; name="favicon"; filename="favicon.png"
Content-Type: image/png
< /mnt/c/Users/Administrator/Pictures/code.png
--boundary123--

View File

@ -0,0 +1,43 @@
<?php
namespace Modules\Admin\app\Emails;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class ForgotPasswordMail extends Mailable
{
use Queueable, SerializesModels;
protected $email;
protected $name;
protected $forgot_code;
/**
* Forgot send mail
*
* @param string $email
* @param string $name
* @param string $forgot_code
*/
public function __construct($email, $name, $forgot_code)
{
$this->email = $email;
$this->name = $name;
$this->forgot_code = $forgot_code;
$this->to($email);
$this->subject('Forgot Password');
$this->replyTo(config('mail.from.address'));
}
/**
* Build the message.
*/
public function build(): self
{
return $this->view('admin::mails/forgot_password', get_object_vars($this));
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Modules\Admin\app\Emails;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class ResetPasswordMail extends Mailable
{
use Queueable, SerializesModels;
protected $email;
protected $name;
protected $forgot_code;
/**
* Reset after change password successfully
*
* @param string $email
* @param string $name
*/
public function __construct($email, $name)
{
$this->email = $email;
$this->name = $name;
$this->to($email);
$this->subject('Reset Password Successfully');
$this->replyTo(config('mail.from.address'));
}
/**
* Build the message.
*/
public function build(): self
{
return $this->view('admin::mails/reset_password_success', get_object_vars($this));
}
}

View File

@ -0,0 +1,174 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Helper\Cache\CustomThemeCacheHelper;
use App\Helper\Cache\SettingCacheHelper;
use App\Http\Controllers\Controller;
use App\Models\Contact;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Mail\Mailable;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Modules\Admin\app\Emails\ForgotPasswordMail;
use Modules\Admin\app\Emails\ResetPasswordMail;
use Modules\Admin\app\Models\Admin;
use Modules\Admin\app\Models\Banner;
use Modules\Admin\app\Models\CustomTheme;
use Modules\Admin\app\Models\Setting;
use Modules\Admin\app\Rules\PasswordRule;
use Tymon\JWTAuth\Facades\JWTAuth;
class AdminController extends Controller
{
public function __construct()
{
Config::set('auth.guards.api.provider', 'admin');
}
protected function respondWithToken()
{
$token = Admin::getTokenByAuth();
return response()->json([
'expires_in' => JWTAuth::setToken($token)->getPayload()->get('exp'),
'token' => $token,
'user' => auth('admins')->user(),
'status' => true
]);
}
public function login(Request $request)
{
$email = $request->get('email');
$password = $request->get('password');
$admin = Admin::where('email', $email)->first();
if ($admin && Hash::check($password, $admin->password)) {
auth('admins')->login($admin);
return $this->respondWithToken();
}
return response()->json([
'message' => 'Incorrect email or password.',
'status' => false
], 400);
}
public function register()
{
}
public function forgotPassword(Request $request)
{
$this->validate($request, [
'email' => 'required|email'
]);
$admin = Admin::where('email', $request->get('email'))->first();
if ($admin) {
$admin->update([
'forgot_code' => Str::random()
]);
Mail::send(new ForgotPasswordMail(
name: $admin->name,
email: $admin->email,
forgot_code: $admin->forgot_code
));
}
return response()->json([
'status' => true
]);
}
public function resetPassword(Request $request)
{
$this->validate($request, [
'forgot_code' => 'required|exists:admin',
'new_password' => new PasswordRule,
'confirm_password' => 'same:new_password'
]);
$admin = Admin::where('forgot_code', $request->get('forgot_code'))->first();
if ($admin) {
$admin->update([
'password' => bcrypt($request->get('new_password')),
'forgot_code' => null
]);
Mail::send(new ResetPasswordMail(
name: $admin->name,
email: $admin->email,
));
return response()->json([
'status' => true
]);
}
return response()->json([
'status' => false
]);
}
public function changePassword(Request $request)
{
$this->validate($request, [
'password' => 'required',
'new_password' => new PasswordRule,
'confirm_password' => 'same:new_password'
]);
$admin = auth('admins')->user();
if (!Hash::check($request->get('password'), $admin->password)) {
// Fail check old password
return response()->json([
'errors' => [
'password' => [
'The old password is incorrect'
]
],
], 400);
}
$admin->update([
'password' => bcrypt($request->get('new_password')),
'forgot_code' => null
]);
Mail::send(new ResetPasswordMail(
name: $admin->name,
email: $admin->email,
));
return response()->json([
'status' => true
]);
}
public function detail()
{
return response()->json([
'user' => auth('admins')->user(),
'status' => true
]);
}
public function logout()
{
$user = auth('admins')->user();
auth('admins')->logout();
return response()->json([
'status' => true,
'user' => $user
]);
}
}

View File

@ -0,0 +1,152 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Banner;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Modules\Admin\app\Http\Requests\BannerRequest;
class BannerController extends Controller
{
public function all(Request $request)
{
$package = new Banner;
return response()->json([
'data' => $package->get(),
'status' => true,
]);
}
public function create(BannerRequest $request)
{
$banner = new Banner;
if ($request->has('title')) {
$banner->title = $request->get('title');
}
if ($request->has('link')) {
$banner->link = $request->get('link');
}
if ($request->has('is_active')) {
$banner->is_active = in_array(
needle: $request->get('is_active'),
haystack: ['false', '0']
) ? 0 : 1;
}
$banner->save();
if ($request->hasFile('image')) {
$file = $request->file('image');
$filename = $banner->id . '/' . Str::random(20) . '.' . $file->getClientOriginalExtension();
$path = Banner::STORAGE . "/$filename";
Storage::disk('public')->put(
path: $path,
contents: $file->get()
);
$banner->image = $path;
$banner->save();
}
return response()->json([
'data' => $banner,
'status' => true
]);
}
public function update(BannerRequest $request)
{
$id = $request->get('id');
$banner = Banner::find($id);
if ($request->has('title')) {
$banner->title = $request->get('title');
}
if ($request->has('link')) {
$banner->link = $request->get('link');
}
if ($request->has('is_active')) {
$banner->is_active = in_array(
needle: $request->get('is_active'),
haystack: ['false', '0']
) ? 0 : 1;
}
$banner->save();
if ($request->hasFile('image')) {
$file = $request->file('image');
$filename = $banner->id . '/' . Str::random(20) . '.' . $file->getClientOriginalExtension();
$path = Banner::STORAGE . "/$filename";
// delete old file
if (Storage::disk('public')->exists($banner->getRawOriginal('image'))) {
Storage::disk('public')->delete($banner->getRawOriginal('image'));
}
// put a new file
Storage::disk('public')->put(
path: $path,
contents: $file->get()
);
$banner->image = $path;
$banner->save();
}
return response()->json([
'data' => $banner,
'status' => true
]);
}
public function delete(BannerRequest $request)
{
$id = $request->get('id');
Banner::destroy($id);
return response()->json([
'status' => true
]);
}
// Delete multiple packages
public function deletes(BannerRequest $request)
{
$banners = $request->get('banners');
$ids = collect($banners)->pluck('id');
Banner::whereIn('id', $ids)->delete();
return response()->json([
'data' => $ids,
'status' => true
]);
}
// Update multiple banners
public function updates(BannerRequest $request)
{
$banners = $request->get('banners');
$ids = collect($banners)->pluck('id');
foreach ($banners as $bannersRequest) {
// convert to object|array to array
$bannersRequest = collect($bannersRequest)->toArray();
// handle array
$package = Package::find($bannersRequest['id']);
if ($package) {
// exclude id field
unset($package['id']);
$package->update($bannersRequest);
}
}
return response()->json([
'data' => Package::whereIn('id', $ids)->get(),
'status' => true
]);
}
}

View File

@ -0,0 +1,189 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Client;
use App\Traits\HasFilterRequest;
use App\Traits\HasOrderByRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use JsonException;
use Modules\Admin\app\Http\Requests\ClientRequest;
class ClientController extends Controller
{
use HasOrderByRequest;
use HasFilterRequest;
public function __construct()
{
// module check model exist and table
if (!class_exists(Client::class)) {
throw new JsonException("user table not exist");
}
;
}
public function get(Request $request)
{
$client = new Client;
// Order by
$this->orderByRequest($client, $request);
// Filter
$this->filterRequest(
builder: $client,
request: $request,
filterKeys: [
'name' => self::F_TEXT,
'phone' => self::F_TEXT,
'email' => self::F_TEXT,
'company' => self::F_TEXT,
'status' => self::F_BOOLEAN,
'subscribe' => self::F_BOOLEAN,
]
);
$responseData = array_merge(
$client->paginate($request->get('per_page'))->toArray(),
['status' => true]
);
return response()->json($responseData);
}
public function create(ClientRequest $request)
{
$payload = $request->all();
$client = new Client;
$client->name = $request->get('name');
$client->password = bcrypt($request->get('password', Client::DEFAULT_PASSWORD));
$client->email = $request->get('email', '');
$client->email_verified_at = now();
$client->phone = $request->get('phone', '');
$client->country_code = $request->get('country_code', 'AU');
$client->company = $request->get('company', '');
$client->save();
if ($request->hasFile('avatar')) {
$file = $request->file('avatar');
$filename = $client->id . '.' . $file->getClientOriginalExtension();
$path = Client::PATH_AVATAR . "/$filename";
Storage::disk('public')->put($path, $file->get());
$client->avatar = $path;
$client->save();
}
return response()->json([
'data' => $client,
'status' => true
]);
}
public function update(ClientRequest $request)
{
$id = $request->get('id');
$client = Client::find($id);
if ($request->has('name')) {
$client->name = $request->get('name');
}
if ($request->has('password') && !empty($request->get('password'))) {
$client->password = bcrypt($request->get('password'));
}
if ($request->has('email')) {
$client->email = $request->get('email');
}
if ($request->has('phone')) {
$client->phone = $request->get('phone');
}
if ($request->has('company')) {
$client->company = $request->get('company');
}
if ($request->has('country_code')) {
$client->country_code = $request->get('country_code');
}
if ($request->has('status')) {
$client->status = ($request->get('status') == 'true' || $request->get('status') == '1') ? 1 : 0;
}
$client->save();
if ($request->hasFile('avatar')) {
$file = $request->file('avatar');
$filename = $client->id . '.' . $file->getClientOriginalExtension();
$path = Client::PATH_AVATAR . "/$filename";
$avatarOld = Storage::disk('public')->exists($client->avatar);
if ($avatarOld) {
Storage::disk('public')->delete($client->avatar);
}
Storage::disk('public')->put($path, $file->get());
$client->update([
'avatar' => $path
]);
}
return response()->json([
'data' => $client,
'status' => true
]);
}
public function delete(ClientRequest $request)
{
$id = $request->get('id');
Client::destroy($id);
return response()->json([
'status' => true
]);
}
// Delete multiple clients
public function deletes(ClientRequest $request)
{
$clients = $request->get('clients');
$ids = collect($clients)->pluck('id');
Client::whereIn('id', $ids)->delete();
return response()->json([
'data' => $ids,
'status' => true
]);
}
// Update multiple clients
public function updates(ClientRequest $request)
{
$clients = $request->get('clients');
$ids = collect($clients)->pluck('id');
foreach ($clients as $ClientRequest) {
// convert to object|array to array
$ClientRequest = collect($ClientRequest)->toArray();
// handle array
$client = Client::find($ClientRequest['id']);
if ($client) {
// exclude id field
unset($client['id']);
$client->update($ClientRequest);
}
}
return response()->json([
'data' => Client::whereIn('id', $ids)->get(),
'status' => true
]);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Contact;
use App\Traits\HasFilterRequest;
use App\Traits\HasOrderByRequest;
use App\Traits\HasSearchRequest;
use Illuminate\Http\Request;
use Modules\Admin\app\Http\Requests\ContactRequest;
class ContactController extends Controller
{
use HasOrderByRequest;
use HasFilterRequest;
use HasSearchRequest;
public function get(ContactRequest $request)
{
$contact = new Contact;
// Order by
$this->orderByRequest($contact, $request);
$this->searchRequest(
builder: $contact,
value: $request->get('search'),
fields: [
'name',
'phone',
'company',
'email',
]
);
$responseData = array_merge(
$contact->paginate($request->get('per_page'))->toArray(),
['status' => true]
);
return response()->json($responseData);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\HCountry;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use JsonException;
class CountryController extends Controller
{
public function __construct()
{
if (!class_exists(HCountry::class)) {
throw new JsonException('Helper table h_country not exist, please check model and table db.');
}
}
public function all()
{
return response()->json([
'data' => HCountry::getByCache()
->map(function($country) {
$country->flag = asset(HCountry::PUBLIC_PATH . "/$country->code.svg");
return $country;
}),
'status' => true
]);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Modules\Admin\app\Http\Requests\CustomThemeRequest;
use Modules\Admin\app\Models\CustomTheme;
use Tymon\JWTAuth\Claims\Custom;
class CustomThemeController extends Controller
{
public function index(CustomThemeRequest $request)
{
return response()->json([
'data' => CustomTheme::first(),
'status' => true
]);
}
public function updateOrCreate(CustomThemeRequest $request)
{
CustomTheme::updateOrCreate([
'id' => 1
], $request->all());
return response()->json([
'data' => CustomTheme::first(),
'status' => true
]);
}
}

View File

@ -0,0 +1,128 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Contact;
use App\Models\Discount;
use App\Models\Order;
use App\Models\Package;
use App\Models\SerialNumberCheck;
use App\Models\User;
use Modules\Admin\app\Http\Requests\DashboardRequest;
class DashboardController extends Controller
{
public function get()
{
try {
$packageTotal = Package::count();
$discountTotal = Discount::count();
$orderTotal = Order::count();
$clientTotal = User::count();
$contactTotal = Contact::count();
return response()->json([
'data' => [
"packages" => $packageTotal,
"discounts" => $discountTotal,
"orders" => $orderTotal,
"clients" => $clientTotal,
"contacts" => $contactTotal,
],
'status' => true,
]);
} catch (\Exception $e) {
return response()->json([
'error' => $e->getMessage(),
'status' => false,
], 500);
}
}
public function statisticSearchSNByMonth(DashboardRequest $request)
{
$timezoneOffset = $request->input('timezone_offset', '+00:00');
$month = $request->input('month', date('M'));
$year = $request->input('year', date('Y'));
$timezoneOffsetServer = date('P');
$selectDateConvert = "CONVERT_TZ(`created_at`, '$timezoneOffsetServer', '$timezoneOffset')";
$arrCreatedAt = SerialNumberCheck::selectRaw("
$selectDateConvert AS date_convert,
created_at
")->whereRaw("
MONTH($selectDateConvert) = $month AND
YEAR($selectDateConvert) = $year
")->groupBy('date_convert')->pluck('created_at');
$statistic = SerialNumberCheck::whereIn('created_at', $arrCreatedAt)
->orderBy('created_at')
->get([
'keyword',
'status',
'created_at',
])
->groupBy(function ($record) use ($timezoneOffset) {
return $record
->created_at
->setTimezone($timezoneOffset)
->toDateString();
})
->map(function ($records) {
$countPending = collect($records)
->where('status', SerialNumberCheck::STATUS_PENDING)
->count();
$countComplete = collect($records)
->where('status', SerialNumberCheck::STATUS_COMPLETE)
->count();
$countFail = collect($records)
->where('status', SerialNumberCheck::STATUS_FAIL)
->count();
return [
'count' => count($records),
'count_pending' => $countPending,
'count_complete' => $countComplete,
'count_fail' => $countFail,
];
});
return response()->json([
'data' => $statistic,
'status' => true,
]);
}
public function statisticRevenuesByMonth(DashboardRequest $request)
{
$timezoneOffset = $request->input('timezone_offset', '+00:00');
$month = $request->input('month', date('M'));
$year = $request->input('year', date('Y'));
$timezoneOffsetServer = date('P');
$selectDateConvert = "CONVERT_TZ(`updated_at`, '$timezoneOffsetServer', '$timezoneOffset')";
$statusCompleted = Order::STATUS_COMPLETED;
$statisticInMonth = Order::selectRaw("
DATE_FORMAT($selectDateConvert, '%Y-%m-%d') AS date_convert,
SUM(total_price) as total_price,
COUNT(*) as count
")->whereRaw("
MONTH($selectDateConvert) = '$month' AND
YEAR($selectDateConvert) = '$year' AND
status = '$statusCompleted'
")
->groupBy('date_convert')
->orderBy('updated_at')
->get()
->groupBy('date_convert')
->map(function ($record) {
return $record[0];
});
return response()->json([
'data' => $statisticInMonth,
'status' => true,
]);
}
}

View File

@ -0,0 +1,133 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Traits\HasFilterRequest;
use App\Traits\HasOrderByRequest;
use App\Traits\HasSearchRequest;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Modules\Admin\app\Http\Requests\DiscountRequest;
use Modules\Admin\app\Models\Discount;
class DiscountController extends Controller
{
use HasOrderByRequest;
use HasFilterRequest;
use HasSearchRequest;
public function get(DiscountRequest $request)
{
$discount = new Discount;
// Order by
$this->orderByRequest($discount, $request);
// Filter
$this->filterRequest(
builder: $discount,
request: $request,
filterKeys: [
'active_date' => self::F_THAN_EQ_DATETIME,
'expiry' => self::F_LESS_EQ_DATETIME,
'date_used' => self::F_IN_DATETIME,
'code' => self::F_TEXT,
'value' => self::F_TEXT,
'email' => self::F_TEXT,
'discount_type_id' => self::F_NOT_CONTAIN,
'status' => self::F_BOOLEAN,
]
);
$this->searchRequest(
builder: $discount,
value: $request->get('search'),
fields: [
'code',
'email',
'value'
]
);
$responseData = array_merge(
$discount->paginate($request->get('per_page'))->toArray(),
['status' => true]
);
return response()->json($responseData);
}
public function create(DiscountRequest $request)
{
$payload = $request->all();
$discount = Discount::createWithDefault($payload);
return response()->json([
'data' => $discount,
'status' => true
]);
}
public function update(DiscountRequest $request)
{
$id = $request->get('id');
$discount = Discount::find($id);
$payload = $request->all();
if ($discount) {
$discount->update($payload);
}
return response()->json([
'data' => $discount,
'status' => true
]);
}
public function delete(DiscountRequest $request)
{
$id = $request->get('id');
Discount::destroy($id);
return response()->json([
'status' => true
]);
}
// Delete multiple discounts
public function deletes(DiscountRequest $request)
{
$discounts = $request->get('discounts');
$ids = collect($discounts)->pluck('id');
Discount::whereIn('id', $ids)->delete();
return response()->json([
'data' => $ids,
'status' => true
]);
}
// Update multiple discounts
public function updates(DiscountRequest $request)
{
$discounts = $request->get('discounts');
$ids = collect($discounts)->pluck('id');
foreach ($discounts as $discountRequest) {
// convert to object|array to array
$discountRequest = collect($discountRequest)->toArray();
// handle array
$discount = Discount::find($discountRequest['id']);
if ($discount) {
// exclude id field
unset($discount['id']);
$discount->update($discountRequest);
}
}
return response()->json([
'data' => Discount::whereIn('id', $ids)->get(),
'status' => true
]);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Modules\Admin\app\Http\Requests\DiscountTypeRequest;
use Modules\Admin\app\Models\DiscountType;
class DiscountTypeController extends Controller
{
public function all(DiscountTypeRequest $request)
{
return response()->json([
'data' => DiscountType::all(),
'status' => true
]);
}
}

View File

@ -0,0 +1,156 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Events\PaymentCanceled;
use App\Events\PaymentCompleted;
use App\Http\Controllers\Controller;
use App\Models\Client;
use App\Models\Order;
use App\Models\Package;
use App\Traits\HasFilterRequest;
use App\Traits\HasOrderByRequest;
use Illuminate\Http\Request;
use JsonException;
use Modules\Admin\app\Http\Requests\OrderRequest;
use Modules\Admin\app\Models\Discount;
use Modules\Paypal\app\Models\HistoryPayment;
class OrderController extends Controller
{
use HasOrderByRequest;
use HasFilterRequest;
const ORDER_STATUS_COMPLETE = 'COMPLETED';
const ORDER_STATUS_PENDING = 'PENDING';
const ORDER_STATUS_CANCEL = 'CANCELED';
public function __construct()
{
// module check model exist and table
if (!class_exists(Order::class)) {
throw new JsonException("Order table not exist" );
}
;
}
public function get(Request $request)
{
$order = Order::getOrdersWithDiscountAndPackage();
// Order by
$this->orderByRequest($order, $request);
// Filter
$this->filterRequest(
builder: $order,
request: $request,
filterKeys: [
'payment_id' => self::F_TEXT,
'discount' => self::F_TEXT,
'email' => [
'type' => self::F_TEXT,
'column' => 'users.email'
],
'status' => [
'type' => self::F_NOT_CONTAIN,
'column' => 'orders.status'
],
'from_date' => [
'type' => self::F_THAN_EQ_DATETIME,
'column' => 'orders.created_at'
],
'to_date' => [
'type' => self::F_LESS_EQ_DATETIME,
'column' => 'orders.created_at'
],
]
);
$responseData = array_merge(
$order->paginate($request->get('per_page'))->toArray(),
['status' => true]
);
return response()->json($responseData);
}
public function update(OrderRequest $request)
{
$id = $request->input('id');
$order = Order::find($id);
$client = Client::find($order['user_id']);
$package = Package::find($order['package_id']);
$order['status'] = $request->input('status');
if ($request->input('status') == self::ORDER_STATUS_COMPLETE) {
if ($order['discount']) {
// Update discount
$discount = Discount::where('code', $order['discount'])->first();
if ($discount != null) {
$discount['user_id'] = $client->id;
$discount['email'] = $client->email;
$discount['date_used'] = now();
$discount->save();
}
}
// Update point for client
$client->point += (int) $order['point'];
}
$status = -1;
if ($request->input('status') == self::ORDER_STATUS_COMPLETE) {
$status = 1;
event(
new PaymentCompleted(
payment_id: $order['payment_id'],
email: $client->email,
name: $client->name,
type: "PAYPAL",
product_name: $package->title,
point: $order->point,
discount_value: view_price((float) $package->price - (float) $order['total_price']),
total_price: $package->price
)
);
} elseif ($request->input('status') == self::ORDER_STATUS_CANCEL) {
$status = 0;
if ($order['discount']) {
$discount = Discount::where('code', $order['discount'])->first();
$discount['status'] = 1;
$discount->save();
}
event(
new PaymentCanceled(
payment_id: $order['payment_id'],
email: $client->email,
name: $client->name
)
);
} else {
$status = 2;
}
// Create history
HistoryPayment::create([
'payment_id' => $order['payment_id'],
'status' => $status,
'payload' => $request->all(),
'response' =>
[
'order' => $order,
'client' => $client,
'discount' => $discount ?? [],
'message' => "Change by Admin"
]
]);
$order->save();
$client->save();
return response()->json([
'data' => $order,
'status' => true
]);
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Helper\Cache\PackagesCacheHelper;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Modules\Admin\app\Http\Requests\PackageRequest;
use Modules\Admin\app\Models\Package;
class PackageController extends Controller
{
public function all(PackageRequest $request)
{
$package = new Package;
return response()->json([
'data' => $package->get(),
'status' => true,
]);
}
public function create(PackageRequest $request)
{
$package = Package::create($request->all());
PackagesCacheHelper::cleanCachePackages();
return response()->json([
'data' => $package,
'status' => true
]);
}
public function update(PackageRequest $request)
{
$id = $request->get('id');
$package = Package::find($id);
$package->update($request->all());
PackagesCacheHelper::cleanCachePackages();
return response()->json([
'data' => $package,
'status' => true
]);
}
public function delete(PackageRequest $request)
{
$id = $request->get('id');
Package::destroy($id);
PackagesCacheHelper::cleanCachePackages();
return response()->json([
'status' => true
]);
}
// Delete multiple packages
public function deletes(PackageRequest $request)
{
$packages = $request->get('packages');
$ids = collect($packages)->pluck('id');
Package::whereIn('id', $ids)->delete();
PackagesCacheHelper::cleanCachePackages();
return response()->json([
'data' => $ids,
'status' => true
]);
}
// Update multiple packages
public function updates(PackageRequest $request)
{
$packages = $request->get('packages');
$ids = collect($packages)->pluck('id');
foreach ($packages as $packageRequest) {
// convert to object|array to array
$packageRequest = collect($packageRequest)->toArray();
// handle array
$package = Package::find($packageRequest['id']);
if ($package) {
// exclude id field
unset($package['id']);
$package->update($packageRequest);
}
}
PackagesCacheHelper::cleanCachePackages();
return response()->json([
'data' => Package::whereIn('id', $ids)->get(),
'status' => true
]);
}
}

View File

@ -0,0 +1,135 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\SerialNumberCheck;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use App\Traits\HasFilterRequest;
use App\Traits\HasOrderByRequest;
use JsonException;
class SNCheckController extends Controller
{
use HasOrderByRequest;
use HasFilterRequest;
public function __construct()
{
// module check model exist and table
if (!class_exists(SerialNumberCheck::class)) {
throw new JsonException("SerialNumberCheck table not exist");
}
;
}
public function get(Request $request)
{
$history = SerialNumberCheck::getHistoryWithUserInfo();
// Order by
$this->orderByRequest($history, $request);
// Filter
$this->filterRequest(
builder: $history,
request: $request,
filterKeys: [
'keyword' => [
'type' => self::F_TEXT,
'column' => 'serial_number_check.keyword'
],
'email' => [
'type' => self::F_TEXT,
'column' => 'users.email'
],
'status' => [
'type' => self::F_NOT_CONTAIN,
'column' => 'serial_number_check.status'
],
'from_date' => [
'type' => self::F_THAN_EQ_DATETIME,
'column' => 'serial_number_check.created_at'
],
'to_date' => [
'type' => self::F_LESS_EQ_DATETIME,
'column' => 'serial_number_check.created_at'
],
]
);
$responseData = array_merge(
$history->paginate($request->get('per_page'))->toArray(),
['status' => true]
);
return response()->json($responseData);
}
public function showDetail(Request $request)
{
$id = $request->input('id');
$searchRow = SerialNumberCheck::find($id);
if ($searchRow) {
$user = $searchRow->user()->get();
$tracking = $searchRow->tracking()->get();
$trackingDetail = $tracking->map(function ($item) {
return [
'current_point' => $item->current_point,
'use_point' => $item->use_point,
'created_at' => $item->created_at
];
});
$response = [
'data_search' => $searchRow,
'user' => $user[0],
'tracking' => $trackingDetail
];
return response()->json(['data' => $response, 'status' => true]);
}else{
return response()->json(['data' => [], 'status' => false]);
}
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request): RedirectResponse
{
//
}
/**
* Show the specified resource.
*/
public function show($id)
{
return view('admin::show');
}
/**
* Show the form for editing the specified resource.
*/
public function edit($id)
{
return view('admin::edit');
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, $id): RedirectResponse
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy($id)
{
//
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Helper\Cache\SettingCacheHelper;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Storage;
use Modules\Admin\app\Http\Requests\SettingRequest;
use Modules\Admin\app\Models\Setting;
class SettingController extends Controller
{
public function index()
{
return response()->json([
'data' => Setting::first(),
'status' => true
]);
}
public function updateOrCreate(SettingRequest $request)
{
if ($request->has('logo')) {
$file = $request->file('logo');
$path = 'setting/logo.' . $file->getClientOriginalExtension();
// save to local
Storage::disk('public')->put(
path: $path,
contents: $file->getContent()
);
$request->request->set('logo', $path);
}
if ($request->has('favicon')) {
$file = $request->file('favicon');
$path = 'setting/favicon.' . $file->getClientOriginalExtension();
// save to local
Storage::disk('public')->put(
path: $path,
contents: $file->getContent()
);
$request->request->set('favicon', $path);
}
try {
// Only 1 row
Setting::updateOrCreate([
'id' => 1
], $request->request->all());
return response()->json([
'data' => Setting::first(),
'status' => true
]);
} catch (\Exception $e) {
return response()->json([
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'status' => false
]);
}
}
public function clearCache()
{
Artisan::call('key:generate');
Artisan::call('cache:clear');
return response()->json([
'status' => true
]);
}
}

View File

@ -0,0 +1,146 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Traits\HasFilterRequest;
use App\Traits\HasOrderByRequest;
use App\Traits\HasSearchRequest;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Modules\Admin\app\Http\Requests\DiscountRequest;
use Modules\Admin\app\Models\Admin;
use Modules\Admin\app\Models\Discount;
use Modules\Admin\app\Models\Tracking;
class TrackingController extends Controller
{
use HasOrderByRequest;
use HasFilterRequest;
use HasSearchRequest;
public function __construct()
{
// Bỏ qua middleware 'checkApiToken' cho hàm 'myFunction'
$this->middleware('api')->except('create');
}
public function get(Request $request)
{
$tracking = new Tracking;
// Order by
$this->orderByRequest($tracking, $request);
// Filter
$this->filterRequest(
builder: $tracking,
request: $request,
filterKeys: [
'created_at' => self::F_IN_DATETIME,
'time_string' => self::F_TEXT,
'name' => self::F_TEXT,
'status' => self::F_TEXT
]
);
$this->searchRequest(
builder: $tracking,
value: $request->get('search'),
fields: [
'name',
'time_string',
'status'
]
);
$responseData = array_merge(
$tracking->paginate($request->get('per_page'))->toArray(),
['status' => true]
);
return response()->json($responseData);
}
public function create(Request $request)
{
$payload = $request->only(['name', 'time_string', 'status']);
$user = Admin::where('name', $payload['name'])->first();
if($user){
$payload['user_id'] = $user->id;
$tracking = Tracking::create($payload);
return response()->json([
'data' => $tracking,
'status' => true
]);
}
return response()->json([
'data' => "USER NOT EXIST",
'status' => false
]);
}
public function update(DiscountRequest $request)
{
$id = $request->get('id');
$discount = Discount::find($id);
$payload = $request->all();
if ($discount) {
$discount->update($payload);
}
return response()->json([
'data' => $discount,
'status' => true
]);
}
public function delete(DiscountRequest $request)
{
$id = $request->get('id');
Discount::destroy($id);
return response()->json([
'status' => true
]);
}
// Delete multiple discounts
public function deletes(DiscountRequest $request)
{
$discounts = $request->get('discounts');
$ids = collect($discounts)->pluck('id');
Discount::whereIn('id', $ids)->delete();
return response()->json([
'data' => $ids,
'status' => true
]);
}
// Update multiple discounts
public function updates(DiscountRequest $request)
{
$discounts = $request->get('discounts');
$ids = collect($discounts)->pluck('id');
foreach ($discounts as $discountRequest) {
// convert to object|array to array
$discountRequest = collect($discountRequest)->toArray();
// handle array
$discount = Discount::find($discountRequest['id']);
if ($discount) {
// exclude id field
unset($discount['id']);
$discount->update($discountRequest);
}
}
return response()->json([
'data' => Discount::whereIn('id', $ids)->get(),
'status' => true
]);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Modules\Admin\app\Http\Middleware;
use Closure;
use Illuminate\Auth\Middleware\Authenticate as MiddlewareAuthenticate;
use Illuminate\Support\Facades\Config;
use Modules\Admin\app\Models\Admin;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\JWTAuth as JWTAuthJWTAuth;
class AdminMiddleware
{
/**
* Handle an incoming request.
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*/
public function handle($request, Closure $next, ...$guards)
{
if (auth('admins')->check()) {
return $next($request);
} else {
return response()->json([
'Unauthenticated.'
], 401);
}
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Modules\Admin\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class BannerRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
$actionMethod = $this->route()->getActionMethod();
$rules = [
'title' => 'max:255',
'link' => 'nullable|max:255',
'image' => 'file|mimes:jpeg,jpg,png,gif|max:2048',
'is_active' => 'in:true,false',
];
// single
if ($actionMethod === 'create') {
$rules['title'] = 'required|' . $rules['title'];
}
if ($actionMethod === 'update') {
$rules['id'] = 'required|exists:banner,id';
$rules['title'] = 'required|' . $rules['title'];
}
if ($actionMethod === 'delete') {
$rules['id'] = 'required|exists:banner,id';
}
// multiple
if ($actionMethod === 'creates') {
$rules = [
'banners.*.id' => 'required|exists:banner,id',
'banners.*.title' => $rules['title'],
'banners.*.link' => $rules['link'],
'banners.*.image' => $rules['image'],
'banners.*.is_active' => $rules['is_active'],
];
}
if ($actionMethod === 'updates') {
$rules = [
'banners.*.title' => $rules['title'],
'banners.*.link' => $rules['link'],
'banners.*.image' => $rules['image'],
'banners.*.is_active' => $rules['is_active'],
];
}
if ($actionMethod === 'deletes') {
$rules = [
'banners.*.id' => 'required|exists:banner,id'
];
}
return $rules;
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace Modules\Admin\app\Http\Requests;
use App\Models\Client;
use Illuminate\Foundation\Http\FormRequest;
use Modules\Admin\app\Rules\PasswordRule;
use Modules\Admin\app\Rules\PointRule;
class ClientRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
$actionMethod = $this->route()->getActionMethod();
$rules = [
'id' => [
'exists' => 'exists:' . (new Client)->getTable() . ',id'
],
'password' => ['nullable', new PasswordRule],
'name' => 'min:5|max:100',
'email' => 'email|nullable|unique:' . (new Client)->getTable(),
'phone' => 'max:50|nullable',
'company' => 'max:100|nullable',
'point' => [
new PointRule,
'nullable'
],
'avatar' => 'file|mimes:jpeg,jpg,png|nullable',
'country_code' => 'exists:h_country,code|max:2',
];
if ($actionMethod === 'create') {
$rules = array_merge($rules, [
'name' => 'required|' . $rules['name']
]);
}
if ($actionMethod === 'update') {
unset($rules['email'], $rules['avatar']);
}
if ($actionMethod === 'deletes') {
$rules = array_merge($rules, [
'clients.*.id' => 'required|' . $rules['id']['exists'],
]);
}
if ($actionMethod === 'updates') {
$rules = array_merge($rules, [
'clients.*.id' => 'required|' . $rules['id']['exists'],
'clients.*.password' => $rules['password'],
'clients.*.name' => $rules['name'],
'clients.*.email' => $rules['email'],
'clients.*.phone' => $rules['phone'],
'clients.*.company' => $rules['company'],
'clients.*.point' => $rules['point'],
'clients.*.avatar' => $rules['avatar'],
'clients.*.country_code' => $rules['country_code'],
]);
}
if ($actionMethod === 'creates') {
$rules = array_merge($rules, [
'clients.*.name' => 'required|' . $rules['name'],
'clients.*.email' => 'required|' . $rules['email'],
'clients.*.password' => $rules['password'],
'clients.*.phone' => $rules['phone'],
'clients.*.company' => $rules['company'],
'clients.*.point' => $rules['point'],
'clients.*.avatar' => $rules['avatar'],
'clients.*.country_code' => $rules['country_code'],
]);
}
return $rules;
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Modules\Admin\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ContactRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
//
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Modules\Admin\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CustomThemeRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
//
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Modules\Admin\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Modules\Admin\app\Rules\TimezoneOffsetRule;
class DashboardRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
$actionMethod = $this->route()->getActionMethod();
$rules = array();
if (in_array($actionMethod, [
'statisticKeywordsByMonth',
'statisticRevenuesByMonth'
])) {
$rules = [
'timezone_offset' => [
'required',
new TimezoneOffsetRule,
],
'month' => [
'integer',
'between:1,12',
],
'year' => [
'integer'
],
];
}
return $rules;
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,128 @@
<?php
namespace Modules\Admin\app\Http\Requests;
use Closure;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Modules\Admin\app\Models\Discount;
use Modules\Admin\app\Models\DiscountType;
use Modules\Admin\app\Rules\PercentRule;
use Modules\Admin\app\Rules\PriceRule;
class DiscountRequest extends FormRequest
{
protected function checkValue(string $attribute, $value, Closure $fail)
{
function checkPrice(string $attribute, $value, $fail)
{
if (!PriceRule::check($value)) {
$fail(PriceRule::message());
}
}
function checkPercent(string $attribute, $value, $fail)
{
if (!PercentRule::check($value)) {
$fail(PercentRule::message());
}
}
if ($this->has('discounts')) {
foreach ($this->get('discounts') as $discountRequest) {
if ($discountRequest['discount_type_id'] === 1) {
checkPrice($attribute, $value, $fail);
} else if ($discountRequest['discount_type_id'] === 2) {
checkPercent($attribute, $value, $fail);
} else {
$fail("The {$attribute} is invalid.");
}
}
} else {
if ($this->get('discount_type_id') === 1) {
checkPrice($attribute, $value, $fail);
} else if ($this->get('discount_type_id') === 2) {
checkPercent($attribute, $value, $fail);
} else {
$fail("The {$attribute} is invalid.");
}
}
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
$actionMethod = $this->route()->getActionMethod();
$rules = [];
// update single
if ($actionMethod == 'update') {
$rules = [
'id' => 'required|exists:' . (new Discount())->getTable(),
'discount_type_id' => 'exists:' . (new DiscountType)->getTable() . ',id',
'value' => function (string $attribute, $value, Closure $fail) {
$this->checkValue($attribute, $value, $fail);
},
];
}
if ($actionMethod == 'create') {
$rules = [
'discount_type_id' => 'exists:' . (new DiscountType)->getTable() . ',id',
'value' => function (string $attribute, $value, Closure $fail) {
$this->checkValue($attribute, $value, $fail);
},
];
}
// multiple
if ($actionMethod == 'updates') {
$rules = [
'discounts.*.id' => 'required|exists:' . (new Discount())->getTable(),
'discounts.*.discount_type_id' => 'exists:' . (new DiscountType)->getTable() . ',id',
'discounts.*.value' => function (string $attribute, $value, Closure $fail) {
$this->checkValue($attribute, $value, $fail);
},
];
}
if ($actionMethod == 'creates') {
$rules = [
'discounts.*.id' => 'required|exists:' . (new Discount())->getTable(),
'discounts.*.discount_type_id' => 'exists:' . (new DiscountType)->getTable() . ',id',
'discounts.*.value' => function (string $attribute, $value, Closure $fail) {
$this->checkValue($attribute, $value, $fail);
},
];
}
// single
if ($actionMethod == 'delete') {
$rules = [
'id' => 'required|exists:' . (new Discount())->getTable()
];
}
// multiple
if ($actionMethod == 'deletes') {
$rules = [
'discounts.*.id' => 'required|exists:' . (new Discount())->getTable()
];
}
return array_merge([
'status' => 'in:0,1,true,false',
'active_date' => 'size:10', // as timestamp unix
'expiry' => 'size:10', // as timestamp unix
'date_used' => 'size:10', // as timestamp unix
'discounts.*.status' => 'in:0,1,true,false',
], $rules);
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Modules\Admin\app\Http\Requests;
use Closure;
use Illuminate\Foundation\Http\FormRequest;
use Modules\Admin\app\Models\DiscountType;
class DiscountTypeRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
//
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Modules\Admin\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class OrderRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'id' => 'required',
'status' => 'max:20|required',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace Modules\Admin\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Modules\Admin\app\Models\Package;
use Modules\Admin\app\Rules\PointRule;
use Modules\Admin\app\Rules\PriceRule;
class PackageRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
$actionMethod = $this->route()->getActionMethod();
$rules = [];
// single
if ($actionMethod == 'update') {
$rules = [
'id' => 'required|exists:' . (new Package())->getTable(),
'point' => new PointRule,
'price' => new PriceRule,
'title' => 'max:127', // from paypal
'description' => 'max:127'
];
}
if ($actionMethod == 'create') {
$rules = [
'point' => new PointRule,
'price' => new PriceRule
];
}
// multiple
if ($actionMethod == 'updates') {
$rules = [
'packages.*.id' => 'required|exists:' . (new Package())->getTable(),
'packages.*.point' => new PointRule,
'packages.*.price' => new PriceRule,
'packages.*.title' => 'max:127',
'packages.*.description' => 'max:127',
];
}
// single
if ($actionMethod == 'delete') {
$rules = [
'id' => 'required|exists:' . (new Package())->getTable()
];
}
// multiple
if ($actionMethod == 'deletes') {
$rules = [
'packages.*.id' => 'required|exists:' . (new Package())->getTable()
];
}
return array_merge([
'status' => 'in:0,1',
'packages.*.status' => 'in:0,1',
], $rules);
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Modules\Admin\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class SettingRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'page_title' => 'max:255',
'email' => 'max:100',
'phone' => 'max:20',
'meta_title' => 'max:255',
'meta_description' => 'max:255',
'meta_keyword' => 'max:255',
'facebook' => 'max:255',
'twitter' => 'max:255',
'linkedin' => 'max:255',
'license' => 'max:255',
'address' => 'max:255',
'favicon' => 'file|mimes:jpeg,jpg,png,gif',
'logo' => 'file|mimes:jpeg,jpg,png,gif'
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Modules\Admin\app\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Config;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Tymon\JWTAuth\Facades\JWTAuth;
class Admin extends Authenticatable implements JWTSubject
{
use HasApiTokens, HasFactory, Notifiable;
use HasRoles;
public function __construct()
{
$this->table = 'admin';
$this->guarded = [];
$this->hidden = [
'password',
'forgot_code',
];
}
public static function getTokenByAuth(): string|null
{
try {
$id = auth('admins')->user()->id;
return JWTAuth::fromUser(self::find($id));
} catch (\Throwable $th) {
return null;
}
}
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Modules\Admin\app\Models;
use App\Traits\HasCacheModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Modules\Admin\Database\factories\CustomThemeFactory;
class CustomTheme extends Model
{
// use HasFactory;
use HasFactory;
use HasCacheModel;
public function __construct()
{
$this->table = 'custom_theme';
$this->guarded = [];
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Dashboard extends Model
{
use HasFactory;
}

View File

@ -0,0 +1,75 @@
<?php
namespace Modules\Admin\app\Models;
use App\Helper\Constant\RequestConstant;
use App\Traits\HasCacheModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Str;
use Modules\Admin\Database\factories\DiscountFactory;
class Discount extends Model
{
use HasFactory;
use HasCacheModel;
public function __construct()
{
$this->table = 'discount';
$this->guarded = [];
$this->appends = [
'discount_type',
'discount_unit',
];
$this->casts = [
'active_date' => 'datetime',
'expiry' => 'datetime',
'date_used' => 'datetime',
];
}
public static function createWithDefault(array $data): Discount
{
$discount = new self;
$discount = $discount->create(array_merge([
'active_date' => now(),
'code' => Discount::generateCode()
], $data));
return $discount;
}
public static function generateCode()
{
$code = Str::random(6);
if (self::where('code', $code)->count()) {
return static::generateCode();
}
return $code;
}
// custom display get{field_name}Attribute
public function getDiscountTypeAttribute()
{
return DiscountType::getByCache()
->where('id', $this->discount_type_id)
->value('name');
}
// custom display get{field_name}Attribute
public function getDiscountUnitAttribute()
{
return DiscountType::getByCache()
->where('id', $this->discount_type_id)
->value('unit');
}
public function discountType(): BelongsTo
{
return $this->belongsTo(DiscountType::class);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Modules\Admin\app\Models;
use App\Traits\HasCacheModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Modules\Admin\Database\factories\DiscountTypeFactory;
class DiscountType extends Model
{
use HasFactory;
use HasCacheModel;
public function __construct()
{
$this->table = 'discount_type';
$this->guarded = [];
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Modules\Admin\app\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Event;
use App\Traits\HasCacheModel;
class Package extends Model
{
use HasFactory;
use HasCacheModel;
public function __construct()
{
$this->table = 'package';
$this->guarded = [];
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Modules\Admin\app\Models;
use App\Traits\HasCacheModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Modules\Admin\Database\factories\SettingFactory;
class Setting extends Model
{
use HasFactory;
use HasCacheModel;
public function __construct()
{
$this->table = 'setting';
$this->guarded = [];
}
protected function _getKey()
{
$keyLength = strlen(env('APP_KEY'));
return substr(env('APP_KEY'), round($keyLength / 2), $keyLength);
}
// custom display get{file_name}Attribute()
public function getFaviconAttribute($value)
{
if ($value) {
return asset("/storage/$value?key=" . $this->_getKey());
} else {
return null;
}
}
// custom display get{file_name}Attribute()
public function getLogoAttribute($value)
{
if ($value) {
return asset("/storage/$value?key=" . $this->_getKey());
} else {
return null;
}
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Modules\Admin\app\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Event;
use App\Traits\HasCacheModel;
class Tracking extends Model
{
use HasFactory;
use HasCacheModel;
public function __construct()
{
$this->table = 'tracking';
$this->guarded = [];
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace Modules\Admin\app\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AdminServiceProvider extends ServiceProvider
{
protected string $moduleName = 'Admin';
protected string $moduleNameLower = 'admin';
/**
* Boot the application events.
*/
public function boot(): void
{
$this->registerCommands();
$this->registerCommandSchedules();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->loadMigrationsFrom(module_path($this->moduleName, 'database/migrations'));
}
/**
* Register the service provider.
*/
public function register(): void
{
$this->app->register(RouteServiceProvider::class);
}
/**
* Register commands in the format of Command::class
*/
protected function registerCommands(): void
{
// $this->commands([]);
}
/**
* Register command Schedules.
*/
protected function registerCommandSchedules(): void
{
// $this->app->booted(function () {
// $schedule = $this->app->make(Schedule::class);
// $schedule->command('inspire')->hourly();
// });
}
/**
* Register translations.
*/
public function registerTranslations(): void
{
$langPath = resource_path('lang/modules/'.$this->moduleNameLower);
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->moduleNameLower);
$this->loadJsonTranslationsFrom($langPath);
} else {
$this->loadTranslationsFrom(module_path($this->moduleName, 'lang'), $this->moduleNameLower);
$this->loadJsonTranslationsFrom(module_path($this->moduleName, 'lang'));
}
}
/**
* Register config.
*/
protected function registerConfig(): void
{
$this->publishes([module_path($this->moduleName, 'config/config.php') => config_path($this->moduleNameLower.'.php')], 'config');
$this->mergeConfigFrom(module_path($this->moduleName, 'config/config.php'), $this->moduleNameLower);
}
/**
* Register views.
*/
public function registerViews(): void
{
$viewPath = resource_path('views/modules/'.$this->moduleNameLower);
$sourcePath = module_path($this->moduleName, 'resources/views');
$this->publishes([$sourcePath => $viewPath], ['views', $this->moduleNameLower.'-module-views']);
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->moduleNameLower);
$componentNamespace = str_replace('/', '\\', config('modules.namespace').'\\'.$this->moduleName.'\\'.config('modules.paths.generator.component-class.path'));
Blade::componentNamespace($componentNamespace, $this->moduleNameLower);
}
/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [];
}
private function getPublishableViewPaths(): array
{
$paths = [];
foreach (config('view.paths') as $path) {
if (is_dir($path.'/modules/'.$this->moduleNameLower)) {
$paths[] = $path.'/modules/'.$this->moduleNameLower;
}
}
return $paths;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Modules\Admin\app\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
/**
* The module namespace to assume when generating URLs to actions.
*/
protected string $moduleNamespace = 'Modules\Admin\app\Http\Controllers';
/**
* Called before routes are registered.
*
* Register any model bindings or pattern based filters.
*/
public function boot(): void
{
parent::boot();
}
/**
* Define the routes for the application.
*/
public function map(): void
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*/
protected function mapWebRoutes(): void
{
Route::middleware('web')
->namespace($this->moduleNamespace)
->group(module_path('Admin', '/routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*/
protected function mapApiRoutes(): void
{
Route::prefix('api')
->middleware('api')
->namespace($this->moduleNamespace)
->group(module_path('Admin', '/routes/api.php'));
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Modules\Admin\app\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password;
class PasswordRule implements ValidationRule
{
/**
* Run the validation rule.
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$isLength = strlen($value) >= 8;
$isUppercase = preg_match('/[A-Z]/', $value);
$isNumber = preg_match('/[0-9]/', $value);
$isSymbol = preg_match('/\!|\@|\#|\$|\%|\^|\&|\*|\-/', $value);
if (!$isLength) {
$fail('Password must be at least 8 characters large.');
}
if (!$isUppercase) {
$fail('Password must have capital letters.');
}
if (!$isNumber) {
$fail('Password must have number.');
}
if (!$isSymbol) {
$fail('Password must have symbol ! @ # $ % ^ & * -');
}
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Modules\Admin\app\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class PercentRule implements ValidationRule
{
static public function check($value): bool
{
$percent = (float) $value;
return ((1 <= $percent) && ($percent <= 99));
}
static public function message(): string
{
return 'The percent must be from 1% to 100%.';
}
/**
* Run the validation rule.
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (!$this->check($value)) {
$fail($this->message());
}
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Modules\Admin\app\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class PointRule implements ValidationRule
{
static public function check($value): bool
{
$point = (int) $value;
return ((0 <= $point) && ($point <= 1000000000));
}
static public function message(): string
{
return 'The point must be from 0 to 1,000,000,000';
}
/**
* Run the validation rule.
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (!$this->check($value)) {
$fail($this->message());
}
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Modules\Admin\app\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class PriceRule implements ValidationRule
{
static public function check($value): bool
{
$price = (float) $value;
return ((1 <= $price) && ($price <= 1000000));
}
static public function message(): string
{
return 'The price must be from 1$ to 1,000,000$.';
}
/**
* Run the validation rule.
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (!$this->check($value)) {
$fail($this->message());
}
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Modules\Admin\app\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class TimezoneOffsetRule implements ValidationRule
{
public static function check($value)
{
$pattern = '/^[+-]\d{2}:\d{2}$/';
return preg_match($pattern, $value) === 1;
}
static public function message(): string
{
return "Your timezone offset is incorrectly transmitted. Please format as follows: '+00:00', '+11:00'!";
}
/**
* Run the validation rule.
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (!$this->check($value)) {
$fail($this->message());
}
}
}

View File

@ -0,0 +1,31 @@
{
"name": "nwidart/admin",
"description": "",
"authors": [
{
"name": "Nicolas Widart",
"email": "n.widart@gmail.com"
}
],
"extra": {
"laravel": {
"providers": [],
"aliases": {
}
}
},
"autoload": {
"psr-4": {
"Modules\\Admin\\": "",
"Modules\\Admin\\App\\": "app/",
"Modules\\Admin\\Database\\Factories\\": "database/factories/",
"Modules\\Admin\\Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Modules\\Admin\\Tests\\": "tests/"
}
}
}

View File

View File

@ -0,0 +1,5 @@
<?php
return [
'name' => 'Admin',
];

View File

@ -0,0 +1,73 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('admin', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('forgot_code')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
DB::table('admin')->insert([
[
'name' => 'Kai',
'email' => 'kai.t@apactech.io',
'password' => bcrypt('Pay@2023'),
],
[
'name' => 'Joseph',
'email' => 'joseph.le@apactech.io',
'password' => bcrypt('Pay@2023'),
],
[
'name' => 'Rose',
'email' => 'rose.h@apactech.io',
'password' => bcrypt('Pay@2023'),
],
[
'name' => 'Huynh',
'email' => 'huynh220592@gmail.com',
'password' => bcrypt('Pay@2023'),
],
[
'name' => 'Admin',
'email' => 'admin@apactech.io',
'password' => bcrypt('Pay@2023'),
],
[
'name' => 'Thuan',
'email' => 'alan.ha@apactech.io',
'password' => bcrypt('Pay@2023'),
],
[
'name' => 'Jason',
'email' => 'jason@apactech.io',
'password' => bcrypt('Pay@2023'),
],
]);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('admin');
}
};

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('setting', function (Blueprint $table) {
$table->id();
$table->string('page_title')->nullable();
$table->string('email', 100)->nullable();
$table->string('phone', 20)->nullable();
$table->string('meta_title')->nullable();
$table->string('meta_description')->nullable();
$table->string('meta_keyword')->nullable();
$table->string('facebook')->nullable();
$table->string('twitter')->nullable();
$table->string('linkedin')->nullable();
$table->string('license')->nullable();
$table->string('address')->nullable();
$table->string('favicon')->nullable();
$table->string('logo')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('setting');
}
};

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('custom_theme', function (Blueprint $table) {
$table->id();
$table->longText('css')->nullable();
$table->longText('js')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('custom_theme');
}
};

View File

View File

@ -0,0 +1,11 @@
{
"name": "Admin",
"alias": "admin",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Admin\\app\\Providers\\AdminServiceProvider"
],
"files": []
}

View File

@ -0,0 +1,15 @@
{
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.5",
"sass": "^1.69.5",
"postcss": "^8.3.7",
"vite": "^4.0.0"
}
}

View File

@ -0,0 +1,7 @@
@extends('admin::layouts.master')
@section('content')
<h1>Hello World</h1>
<p>Module: {!! config('admin.name') !!}</p>
@endsection

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Admin Module - {{ config('app.name', 'Laravel') }}</title>
<meta name="description" content="{{ $description ?? '' }}">
<meta name="keywords" content="{{ $keywords ?? '' }}">
<meta name="author" content="{{ $author ?? '' }}">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
{{-- Vite CSS --}}
{{-- {{ module_vite('build-admin', 'resources/assets/sass/app.scss') }} --}}
</head>
<body>
@yield('content')
{{-- Vite JS --}}
{{-- {{ module_vite('build-admin', 'resources/assets/js/app.js') }} --}}
</body>

View File

@ -0,0 +1,249 @@
@php
$data = [
'title' => 'Forgot Your Password',
'name' => $name,
'body' => "Not to worry, we got you! Let's get you new a password.",
'footer_detail' => "This email was sent $email.",
'url_reset' => env('ADMIN_URL') . "/forgot/reset-password?code=$forgot_code&email=$email"
];
@endphp
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>{{ $data['title'] }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
/**
* Google webfonts. Recommended to include the .woff version for cross-client compatibility.
*/
@media screen {
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v10/ODelI1aHBYDBqgeIAH2zlBM0YzuT7MdOe03otPbuUS0.woff) format('woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v10/toadOcfmlt9b38dHJxOBGFkQc6VGVFSmCnC_l7QZG60.woff) format('woff');
}
}
/**
* Avoid browser level font resizing.
* 1. Windows Mobile
* 2. iOS / OSX
*/
body,
table,
td,
a {
-ms-text-size-adjust: 100%; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove extra space added to tables and cells in Outlook.
*/
table,
td {
mso-table-rspace: 0pt;
mso-table-lspace: 0pt;
}
/**
* Better fluid images in Internet Explorer.
*/
img {
-ms-interpolation-mode: bicubic;
}
/**
* Remove blue links for iOS devices.
*/
a[x-apple-data-detectors] {
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
color: inherit !important;
text-decoration: none !important;
}
/**
* Fix centering issues in Android 4.4.
*/
div[style*="margin: 16px 0;"] {
margin: 0 !important;
}
body {
width: 100% !important;
height: 100% !important;
padding: 0 !important;
margin: 0 !important;
}
/**
* Collapse table borders to avoid space between cells.
*/
table {
border-collapse: collapse !important;
}
a {
color: #1a82e2;
}
img {
height: auto;
line-height: 100%;
text-decoration: none;
border: 0;
outline: none;
}
</style>
</head>
<body style="background-color: #e9ecef;">
<!-- start body -->
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<!-- start logo -->
<tr>
<td align="center" bgcolor="#e9ecef">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<tr>
<td align="center" valign="top" style="padding: 36px 24px;">
<a href="{{env('ADMIN_URL')}}" target="_blank" style="display: inline-block;">
<img src="{{asset(\App\Models\Setting::getByCache()->value('logo'))}}" alt="Logo" border="0" width="100" style="display: block; width: 100px; max-width: 100px; min-width: 48px;">
</a>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- end logo -->
<!-- start hero -->
<tr>
<td align="center" bgcolor="#e9ecef">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<tr>
<td align="left" bgcolor="#ffffff" style="padding: 36px 24px 0; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; border-top: 3px solid #d4dadf;">
<h1 style="margin: 0; font-size: 32px; font-weight: 700; letter-spacing: -1px; line-height: 48px;">{{ $data['title'] }}</h1>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- end hero -->
<!-- start copy block -->
<tr>
<td align="center" bgcolor="#e9ecef">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<!-- start copy -->
<tr>
<td align="left" bgcolor="#ffffff" style="padding: 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px;">
<p style="margin: 0;">{{ $data['body'] }}</p>
</td>
</tr>
<!-- end copy -->
<!-- start button -->
<tr>
<td align="left" bgcolor="#ffffff">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td align="center" bgcolor="#ffffff" style="padding: 12px;">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td align="center" bgcolor="#1a82e2" style="border-radius: 6px;">
<a href="{{ $data['url_reset'] }}" target="_blank" style="display: inline-block; padding: 16px 36px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; color: #ffffff; text-decoration: none; border-radius: 6px;">Reset Password</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- end button -->
<!-- start copy -->
<tr>
<td align="left" bgcolor="#ffffff" style="padding: 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; border-bottom: 3px solid #d4dadf">
<p style="margin: 0;">Cheers,<br> {{ $data['name'] }}</p>
</td>
</tr>
<!-- end copy -->
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- end copy block -->
<!-- start footer -->
<tr>
<td align="center" bgcolor="#e9ecef" style="padding: 24px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<!-- start unsubscribe -->
<tr>
<td align="center" bgcolor="#e9ecef" style="padding: 12px 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; color: #666;">
<p style="margin: 0;">{{ $data['footer_detail'] }}</p>
</td>
</tr>
<!-- end unsubscribe -->
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- end footer -->
</table>
<!-- end body -->
</body>
</html>

View File

@ -0,0 +1,230 @@
@php
$setting = \Modules\Admin\app\Models\Setting::first();
$data = [
'title' => 'Reset Password Successfully',
'name' => $name,
'body' => "We wanted to let you know that your password has been successfully reset. Your account is now secured with a new password. <hr>
If you did not initiate this password reset or have any concerns about your account security, please contact our support team immediately at " . config('mail.from.address') . " or reply to this email.",
'footer_detail' => "This email was sent $email.",
];
@endphp
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>{{ $data['title'] }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
/**
* Google webfonts. Recommended to include the .woff version for cross-client compatibility.
*/
@media screen {
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v10/ODelI1aHBYDBqgeIAH2zlBM0YzuT7MdOe03otPbuUS0.woff) format('woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v10/toadOcfmlt9b38dHJxOBGFkQc6VGVFSmCnC_l7QZG60.woff) format('woff');
}
}
/**
* Avoid browser level font resizing.
* 1. Windows Mobile
* 2. iOS / OSX
*/
body,
table,
td,
a {
-ms-text-size-adjust: 100%; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove extra space added to tables and cells in Outlook.
*/
table,
td {
mso-table-rspace: 0pt;
mso-table-lspace: 0pt;
}
/**
* Better fluid images in Internet Explorer.
*/
img {
-ms-interpolation-mode: bicubic;
}
/**
* Remove blue links for iOS devices.
*/
a[x-apple-data-detectors] {
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
color: inherit !important;
text-decoration: none !important;
}
/**
* Fix centering issues in Android 4.4.
*/
div[style*="margin: 16px 0;"] {
margin: 0 !important;
}
body {
width: 100% !important;
height: 100% !important;
padding: 0 !important;
margin: 0 !important;
}
/**
* Collapse table borders to avoid space between cells.
*/
table {
border-collapse: collapse !important;
}
a {
color: #1a82e2;
}
img {
height: auto;
line-height: 100%;
text-decoration: none;
border: 0;
outline: none;
}
</style>
</head>
<body style="background-color: #e9ecef;">
<!-- start body -->
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<!-- start logo -->
<tr>
<td align="center" bgcolor="#e9ecef">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<tr>
<td align="center" valign="top" style="padding: 36px 24px;">
<a href="{{env('ADMIN_URL')}}" target="_blank" style="display: inline-block;">
<img src="{{asset(\App\Models\Setting::getByCache()->value('logo'))}}" alt="Logo" border="0" width="100" style="display: block; width: 100px; max-width: 100px; min-width: 48px;">
</a>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- end logo -->
<!-- start hero -->
<tr>
<td align="center" bgcolor="#e9ecef">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<tr>
<td align="left" bgcolor="#ffffff" style="padding: 36px 24px 0; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; border-top: 3px solid #d4dadf;">
<h1 style="margin: 0; font-size: 32px; font-weight: 700; letter-spacing: -1px; line-height: 48px;">{{ $data['title'] }}</h1>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- end hero -->
<!-- start copy block -->
<tr>
<td align="center" bgcolor="#e9ecef">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<!-- start copy -->
<tr>
<td align="left" bgcolor="#ffffff" style="padding: 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px;">
<p style="margin: 0;">{!! $data['body'] !!}</p>
</td>
</tr>
<!-- end copy -->
<!-- start copy -->
<tr>
<td align="left" bgcolor="#ffffff" style="padding: 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; border-bottom: 3px solid #d4dadf">
<p style="margin: 0;">Cheers,<br> {{ $data['name'] }}</p>
</td>
</tr>
<!-- end copy -->
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- end copy block -->
<!-- start footer -->
<tr>
<td align="center" bgcolor="#e9ecef" style="padding: 24px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<!-- start unsubscribe -->
<tr>
<td align="center" bgcolor="#e9ecef" style="padding: 12px 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; color: #666;">
<p style="margin: 0;">{{ $data['footer_detail'] }}</p>
</td>
</tr>
<!-- end unsubscribe -->
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- end footer -->
</table>
<!-- end body -->
</body>
</html>

View File

View File

@ -0,0 +1,154 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Admin\app\Http\Controllers\AdminController;
use Modules\Admin\app\Http\Controllers\BannerController;
use Modules\Admin\app\Http\Controllers\ClientController;
use Modules\Admin\app\Http\Controllers\ContactController;
use Modules\Admin\app\Http\Controllers\CountryController;
use Modules\Admin\app\Http\Controllers\CustomThemeController;
use Modules\Admin\app\Http\Controllers\DashboardController;
use Modules\Admin\app\Http\Controllers\DiscountController;
use Modules\Admin\app\Http\Controllers\DiscountTypeController;
use Modules\Admin\app\Http\Controllers\OrderController;
use Modules\Admin\app\Http\Controllers\PackageController;
use Modules\Admin\app\Http\Controllers\SNCheckController;
use Modules\Admin\app\Http\Controllers\SettingController;
use Modules\Admin\app\Http\Controllers\TrackingController;
use Modules\Admin\app\Http\Middleware\AdminMiddleware;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::middleware('api')
->prefix('v1/admin')
->group(function () {
// NOTE not login
Route::group([], function () {
Route::post('login', [AdminController::class, 'login']);
Route::post('reset-password', [AdminController::class, 'resetPassword']);
Route::get('forgot-password', [AdminController::class, 'forgotPassword']);
});
// NOTE after login
Route::group([
'middleware' => AdminMiddleware::class,
], function () {
Route::get('logout', [AdminController::class, 'logout']);
Route::get('detail', [AdminController::class, 'detail']);
Route::post('change-password', [AdminController::class, 'changePassword']);
Route::group([
'prefix' => 'setting',
], function () {
Route::get('/', [SettingController::class, 'index']);
Route::post('/', [SettingController::class, 'updateOrCreate']);
Route::get('/clear-cache', [SettingController::class, 'clearCache']);
});
Route::group([
'prefix' => 'package',
], function () {
Route::get('/all', [PackageController::class, 'all']);
Route::post('/create', [PackageController::class, 'create']);
Route::post('/update', [PackageController::class, 'update']);
Route::get('/delete', [PackageController::class, 'delete']);
Route::post('/updates', [PackageController::class, 'updates']);
Route::post('/deletes', [PackageController::class, 'deletes']);
});
Route::group([
'prefix' => 'discount',
], function () {
Route::get('/get', [DiscountController::class, 'get']);
Route::post('/create', [DiscountController::class, 'create']);
Route::post('/update', [DiscountController::class, 'update']);
Route::get('/delete', [DiscountController::class, 'delete']);
Route::post('/updates', [DiscountController::class, 'updates']);
Route::post('/deletes', [DiscountController::class, 'deletes']);
});
Route::group([
'prefix' => 'client',
], function () {
Route::get('/get', [ClientController::class, 'get']);
Route::post('/create', [ClientController::class, 'create']);
Route::post('/update', [ClientController::class, 'update']);
Route::get('/delete', [ClientController::class, 'delete']);
Route::post('/updates', [ClientController::class, 'updates']);
Route::post('/deletes', [ClientController::class, 'deletes']);
});
Route::group([
'prefix' => 'banner',
], function () {
Route::get('/all', [BannerController::class, 'all']);
Route::post('/create', [BannerController::class, 'create']);
Route::post('/update', [BannerController::class, 'update']);
Route::get('/delete', [BannerController::class, 'delete']);
Route::post('/updates', [BannerController::class, 'updates']);
Route::post('/deletes', [BannerController::class, 'deletes']);
});
Route::group([
'prefix' => 'order',
], function () {
Route::get('/get', [OrderController::class, 'get']);
Route::post('/update', [OrderController::class, 'update']);
});
Route::group([
'prefix' => 'sn-check-history',
], function () {
Route::get('/get', [SNCheckController::class, 'get']);
Route::get('/show-detail', [SNCheckController::class, 'showDetail']);
});
Route::group([
'prefix' => 'discount-type',
], function () {
Route::get('/all', [DiscountTypeController::class, 'all']);
});
Route::group([
'prefix' => 'custom-theme',
], function () {
Route::get('/', [CustomThemeController::class, 'index']);
Route::post('/', [CustomThemeController::class, 'updateOrCreate']);
});
Route::group([
'prefix' => 'h-country',
], function () {
Route::get('/', [CountryController::class, 'all']);
});
Route::group([
'prefix' => 'contact',
], function () {
Route::get('/get', [ContactController::class, 'get']);
});
Route::group([
'prefix' => 'dashboard',
], function () {
Route::get('/get', [DashboardController::class, 'get']);
Route::get('/statistics-search-sn-by-month', [DashboardController::class, 'statisticSearchSNByMonth']);
Route::get('/statistics-revenues-by-month', [DashboardController::class, 'statisticRevenuesByMonth']);
});
});
});
Route::group([
'prefix' => 'v1/admin/tracking',
], function () {
Route::get('/', [TrackingController::class, 'get']);
Route::post('/scan-create', [TrackingController::class, 'create']);
// Route::get('/clear-cache', [SettingController::class, 'clearCache']);
});

View File

@ -0,0 +1,19 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Admin\app\Http\Controllers\AdminController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::group([], function () {
Route::resource('admin', AdminController::class)->names('admin');
});

View File

@ -0,0 +1,26 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
build: {
outDir: '../../public/build-admin',
emptyOutDir: true,
manifest: true,
},
plugins: [
laravel({
publicDirectory: '../../public',
buildDirectory: 'build-admin',
input: [
__dirname + '/resources/assets/sass/app.scss',
__dirname + '/resources/assets/js/app.js'
],
refresh: true,
}),
],
});
//export const paths = [
// 'Modules/$STUDLY_NAME$/resources/assets/sass/app.scss',
// 'Modules/$STUDLY_NAME$/resources/assets/js/app.js',
//];

View File

@ -0,0 +1,175 @@
/*
# API version V1
> [Base URL: http://localhost:8000/api/v1/*]
Purpose to handle authentication with [JWT](https://jwt-auth.readthedocs.io/en/develop/quick-start/)
*/
/*
## Response
### 200
Success:
```js
{
"data": [] | {},
"status": true
}
```
Fail:
```js
{
"data": [] | {},
"status": false
}
```
### 203
* On/Off for button switch:
* Success/Fail:
```js
{
// no content
}
```
### 400
* Validate fail.
* Fail:
```js
{
"attribute": [
"The :attribute field is required.",
...
]
...
}
```
### 401
* Unauthentication, not login.
* Fail:
```js
{
"message": "Token has expired"
}
```
### 403
* Permission denied.
* Fail:
```js
{
"message": "..."
}
```
### 404
* Page not found.
* Fail:
```js
{
"message": "The route {path} could not be found."
}
```
### 500
* Server error.
* Fail:
```js
{
"message": "..."
}
```
### 503
* Server maintain.
* Fail:
```js
{
"message": "..."
}
```
*/
@url=http://localhost:8000
@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAvYXBpL3YxL2xvZ2luIiwiaWF0IjoxNzAwMTIyNzkwLCJleHAiOjE3MDAxMjYzOTAsIm5iZiI6MTcwMDEyMjc5MCwianRpIjoiT1FyWW9mazkwWXJSTnlyWSIsInN1YiI6IjEiLCJwcnYiOiI5Yjk4MmUxNDA4MzM2MmVjMmViZjE4MDQxM2NmNGVlMGFjM2VkY2U4In0.VT1-FwvSdMsbHiJ6-dQfoH3LfF2_14GxzgYbQWdRATc
{{
exports.defaultHeaders = {
"Content-Type": "application/json",
"Accept": "application/json"
}
}}
###
# @name logout
POST {{url}}/api/v1/logout
...defaultHeaders
Authorization: Bearer {{token}}
HTTP/1.1 200 - OK
host: localhost:8000
date: Thu, 16 Nov 2023 08:20:13 GMT, Thu, 16 Nov 2023 08:20:13 GMT
connection: close
x-powered-by: PHP/8.2.11
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 60
x-ratelimit-remaining: 57
phpdebugbar-id: X29245e892030a261594af408e13086a3
access-control-allow-origin: *
###
# @name login
POST {{url}}/api/v1/login
...defaultHeaders
{
"email": "admin@apactech.io",
"password": "admin123"
}
HTTP/1.1 200 - OK
date: Wed, 15 Nov 2023 07:53:31 GMT
server: Apache/2.4.58 (Unix)
x-powered-by: PHP/8.1.25
cache-control: no-cache, private
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
phpdebugbar-id: Xf53438d0027647cec2211e26f376006b
access-control-allow-origin: *
connection: close
transfer-encoding: chunked
content-type: application/json
###
# @name register
POST {{url}}/api/v1/register
...defaultHeaders
{
"name": "kai.t",
"email": "kai.t@apachtech.io",
"password": "kai.t123"
}
HTTP/1.1 400 - Bad Request
date: Wed, 15 Nov 2023 08:15:31 GMT
server: Apache/2.4.58 (Unix)
x-powered-by: PHP/8.1.25
cache-control: no-cache, private
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
phpdebugbar-id: X740ed6622628ede300ed7be424860462
access-control-allow-origin: *
connection: close
transfer-encoding: chunked
content-type: application/json; charset=utf-8
###
# @name get-users
GET {{url}}/api/v1/users
...defaultHeaders
Authorization: Bearer {{token}}
HTTP/1.1 401 - Unauthorized
date: Wed, 15 Nov 2023 08:15:35 GMT
server: Apache/2.4.58 (Unix)
vary: Authorization
x-powered-by: PHP/8.1.25
www-authenticate: jwt-auth
cache-control: no-cache, private
x-ratelimit-limit: 60
x-ratelimit-remaining: 58
phpdebugbar-id: X9d6118350a6e5a23aea55d495a056cc6
access-control-allow-origin: *
connection: close
transfer-encoding: chunked
content-type: application/json

View File

@ -0,0 +1,101 @@
<?php
namespace Modules\Auth\app\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Hash;
class AuthController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view('auth::index');
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('auth::create');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request): RedirectResponse
{
//
}
/**
* Show the specified resource.
*/
public function show($id)
{
return view('auth::show');
}
/**
* Show the form for editing the specified resource.
*/
public function edit($id)
{
return view('auth::edit');
}
/**
* Update the specified resource in storage.
*/
public function updatePassword(Request $request)
{
//
$messages = array(
'email.required' => 'Email là bắt buộc.',
'email.email' => 'Email không đúng định dạng.',
'password.required' => 'Mật khẩu là bắt buộc.',
'current_password.required' => 'Mật khẩu là bắt buộc.',
'current_password.min' => 'Mật khẩu phải có ít nhất 6 ký tự.',
'password.min' => 'Mật khẩu phải có ít nhất 6 ký tự.',
'password.confirmed' => 'Mật khẩu không trùng khớp.',
'password_confirmation.required' => 'Mật khẩu xác thực là bắt buộc.',
);
$validateData = [
'email' => 'required|email',
'current_password' => 'required|min:6',
'password' => 'required|min:6|confirmed',
'password_confirmation' => 'required'
];
$request->validate($validateData, $messages);
$user = User::where('email', $request->email)->first();
if (Hash::check($request->current_password, $user->password) && $user !== null) {
$user->password = bcrypt($request->password);
$user->save();
return response()->json(['status' => true, 'mess' => 'Change password success!'], Response::HTTP_OK);
}else{
return response()->json(['status' => false, 'mess' => 'Current password incorrect'], Response::HTTP_OK);
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy($id)
{
//
}
}

Some files were not shown because too many files have changed in this diff Show More