Compare commits
61 Commits
971f3f6c92
...
bobby_dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7dc64b4cc | ||
|
|
3497d2f4a9 | ||
|
|
dab7734185 | ||
|
|
20796ce757 | ||
|
|
d4eb35406c | ||
|
|
15e591169a | ||
|
|
a78cf241dd | ||
|
|
e842b12957 | ||
|
|
345fa62865 | ||
|
|
be1ae7262a | ||
|
|
244cd7c383 | ||
|
|
585c863f66 | ||
|
|
59519ad43f | ||
|
|
9de5bcc122 | ||
|
|
fa5c897d99 | ||
|
|
14c415389e | ||
|
|
24d18e6a30 | ||
|
|
a6e97f25b0 | ||
|
|
5450ac31d2 | ||
|
|
a3dd0cc941 | ||
|
|
5cbd822c33 | ||
|
|
eff9eccf1e | ||
|
|
4e83b961c0 | ||
|
|
35af98676f | ||
|
|
bc2c961d5b | ||
|
|
70de300837 | ||
|
|
9aa3fa9c2a | ||
|
|
281336b4c2 | ||
|
|
d1286fa6bf | ||
|
|
716590e718 | ||
|
|
36674c67f7 | ||
|
|
6361444cb8 | ||
|
|
816dd0367a | ||
|
|
6f8257d1d8 | ||
|
|
3461a7281e | ||
|
|
6d55254013 | ||
|
|
694e74eb0c | ||
|
|
0a7b910c46 | ||
|
|
6aa5c602d2 | ||
|
|
514f9a96e2 | ||
|
|
f13b7d8d4e | ||
|
|
d41af6a628 | ||
|
|
ec1fc86c25 | ||
|
|
1513c8609b | ||
|
|
82e85c488e | ||
|
|
575c35691e | ||
|
|
2613e926c7 | ||
|
|
de964c4620 | ||
|
|
9e6c995e59 | ||
|
|
5e85aea844 | ||
|
|
edc1f394db | ||
|
|
6b64a44242 | ||
|
|
6e3df95b1e | ||
|
|
7cecbf90b7 | ||
|
|
090e806b16 | ||
|
|
dce1bd46a7 | ||
|
|
167c379c6b | ||
|
|
d8b121b6a5 | ||
|
|
d52a35c9e3 | ||
|
|
83224bc92a | ||
|
|
77cc6a0940 |
77
README.md
Normal file
77
README.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Installation Guide for Digest Backend
|
||||
|
||||
Follow these steps to set up the Digest Backend project on your local machine.
|
||||
|
||||
## Cloning the Repository
|
||||
|
||||
1. Clone the repository:
|
||||
```sh
|
||||
$ git clone https://github.com/WDI-Ideas/digest_backend.git
|
||||
$ cd digest_backend
|
||||
```
|
||||
|
||||
## Setting Up the Environment
|
||||
|
||||
2. Install `virtualenv` if you haven't already:
|
||||
```sh
|
||||
$ pip install virtualenv
|
||||
```
|
||||
|
||||
3. Create a virtual environment:
|
||||
```sh
|
||||
$ virtualenv venv
|
||||
```
|
||||
|
||||
4. Activate the virtual environment:
|
||||
|
||||
**Windows:**
|
||||
```sh
|
||||
venv\Scripts\activate
|
||||
```
|
||||
|
||||
**Mac/Linux:**
|
||||
```sh
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
## Installing Dependencies
|
||||
|
||||
5. Install the required dependencies using pip:
|
||||
```sh
|
||||
$ pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Configuring the Environment Variables
|
||||
|
||||
6. Copy and rename the env.example file to .env and set all the required variables. Ensure there are no spaces around the =:
|
||||
```sh
|
||||
$ cp env.example .env
|
||||
```
|
||||
|
||||
## Migrating the Database
|
||||
|
||||
7. Run the following command to create the database tables:
|
||||
```sh
|
||||
$ python manage.py migrate
|
||||
```
|
||||
|
||||
## Pre-Populating the Database
|
||||
|
||||
8. Run the following command to pre-populate the database with custom command:
|
||||
```sh
|
||||
$ python manage.py load_iam_fixture
|
||||
```
|
||||
|
||||
## Running the Server
|
||||
|
||||
9. Run the following command to start the server:
|
||||
```sh
|
||||
$ python manage.py runserver
|
||||
```
|
||||
|
||||
Now, your server should be up and running. Access it in your browser at http://127.0.0.1:8000/.
|
||||
|
||||
## Note :
|
||||
- Make sure you have Python and pip installed on your system.
|
||||
- If you encounter any issues, ensure that all dependencies are properly installed and the environment variables are correctly configured.
|
||||
- Always activate the virtual environment before running any Python or Django commands.
|
||||
@@ -3,7 +3,7 @@ from django.contrib.auth.hashers import make_password
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import UniqueValidator
|
||||
|
||||
from module_iam.models import IAmPrincipal
|
||||
from module_iam.models import AppVersion, IAmPrincipal
|
||||
from module_project import constants
|
||||
|
||||
# class BasePasswordSerializer(serializers.Serializer):
|
||||
@@ -107,3 +107,13 @@ class PasswordResetSerializer(serializers.ModelSerializer):
|
||||
instance.password = make_password(new_password)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
class AppVersionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AppVersion
|
||||
fields = [
|
||||
"device_type",
|
||||
"version",
|
||||
"force_upgrade",
|
||||
"recommend_upgrade",
|
||||
]
|
||||
@@ -19,6 +19,5 @@ urlpatterns = [
|
||||
path('apple-signin/', views.AppleSignin.as_view(), name='apple_signin'),
|
||||
|
||||
path('version-check/', views.VersionCheck.as_view(), name='version_check'),
|
||||
path('app-version/', views.VersionCheck.as_view(), name='version_check'),
|
||||
|
||||
]
|
||||
|
||||
@@ -15,7 +15,7 @@ from module_project import constants
|
||||
from module_project.service import EmailService, SMSService
|
||||
from module_project.utils import ApiResponse
|
||||
|
||||
from .serializers import (LoginSerializer, OtpVerificationSerializer,
|
||||
from .serializers import (AppVersionSerializer, LoginSerializer, OtpVerificationSerializer,
|
||||
PasswordResetSerializer, RegistrationSerializer)
|
||||
from .utils import (AuthService, GoogleAuthService,
|
||||
authticate_with_otp_and_passsword, blacklist_token,
|
||||
@@ -139,7 +139,7 @@ class OtpRequestView(APIView):
|
||||
return ApiResponse.error(
|
||||
message=constants.EMAIL_REQUIRED, errors=constants.EMAIL_REQUIRED
|
||||
)
|
||||
print(f"email auth username: {settings.EMAIL_HOST_USER}")
|
||||
print(f"email auth username: {settings.DEFAULT_FROM_EMAIL}")
|
||||
email = request.data.get("email")
|
||||
|
||||
principal = get_principal_by_email(email=email)
|
||||
@@ -161,7 +161,7 @@ class OtpRequestView(APIView):
|
||||
email_service = EmailService(
|
||||
subject="Password Reset Request",
|
||||
to=principal.email,
|
||||
from_email=settings.EMAIL_HOST_USER,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
)
|
||||
|
||||
# Send the email
|
||||
@@ -369,30 +369,18 @@ class AppleSignin(APIView):
|
||||
|
||||
|
||||
class VersionCheck(APIView):
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
model = AppVersion
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# app_version = request.GET.get('appVersion')
|
||||
print("app version is called")
|
||||
device_type = request.GET.get('deviceType')
|
||||
|
||||
if not device_type:
|
||||
return ApiResponse.error(message=constants.FAILURE, errors="App version and device type is required")
|
||||
return ApiResponse.error(message=constants.FAILURE, errors="device type is required")
|
||||
# Query the database to retrieve the upgrade flags based on the app version
|
||||
try:
|
||||
version = self.model.objects.filter(device_type=device_type).last()
|
||||
except self.model.DoesNotExist:
|
||||
version = None
|
||||
version = self.model.objects.filter(device_type=device_type).last()
|
||||
version_data = AppVersionSerializer(version)
|
||||
|
||||
if version:
|
||||
upgrade_flags = {
|
||||
'forceUpgrade': version.force_upgrade,
|
||||
'recommendUpgrade': version.recommend_upgrade,
|
||||
}
|
||||
else:
|
||||
upgrade_flags = {
|
||||
'forceUpgrade': False,
|
||||
'recommendUpgrade': False,
|
||||
}
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=upgrade_flags)
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=version_data.data)
|
||||
18
module_iam/migrations/0010_alter_appversion_device_type.py
Normal file
18
module_iam/migrations/0010_alter_appversion_device_type.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2024-05-21 14:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('module_iam', '0009_alter_appversion_device_type_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='appversion',
|
||||
name='device_type',
|
||||
field=models.CharField(choices=[('ios', 'ios'), ('android', 'android')], max_length=10, verbose_name='Device Type (ios / android)'),
|
||||
),
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
from functools import wraps
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.shortcuts import render
|
||||
from . import models
|
||||
from django.db.models import Q
|
||||
# import logging
|
||||
@@ -69,7 +70,7 @@ class ResourcePermissionRequiredMixin:
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not self.has_resource_permission(request.user, self.resource):
|
||||
# logger.warning(f"Permission denied for user {request.user} accessing {self.resource}:{self.action}")
|
||||
raise PermissionDenied("You do not have permission to access this resource.")
|
||||
return render(request, "module_iam/permission_denied.html")
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
@@ -80,7 +81,7 @@ class ResourcePermissionRequiredMixin:
|
||||
instance = cls()
|
||||
instance.resource = resource
|
||||
if not instance.has_resource_permission(request.user, instance.resource):
|
||||
raise PermissionDenied("You do not have permission to access this resource.")
|
||||
return render(request, "module_iam/permission_denied.html")
|
||||
return view_func(request, *args, **kwargs)
|
||||
return _wrapped_view
|
||||
return decorator
|
||||
|
||||
@@ -32,7 +32,7 @@ if READ_DOT_ENV_FILE:
|
||||
SECRET_KEY = env.str("SECRET_KEY")
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = env.bool("DJANGO_DEBUG", True)
|
||||
DEBUG = env.bool("DJANGO_DEBUG", False)
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
@@ -216,6 +216,7 @@ EMAIL_HOST_USER = env.str("EMAIL_HOST_USER")
|
||||
EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD")
|
||||
EMAIL_PORT = env.str("EMAIL_PORT")
|
||||
EMAIL_USE_TLS = True
|
||||
DEFAULT_FROM_EMAIL = env.str("DEFAULT_FROM_EMAIL")
|
||||
|
||||
ONESIGNAL_APP_ID = env.str("ONESIGNAL_APP_ID")
|
||||
ONESIGNAL_REST_API_KEY = env.str("ONESIGNAL_REST_API_KEY")
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
import colorlog
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
|
||||
# DEBUG = True
|
||||
DEBUG = False
|
||||
ALLOWED_HOSTS = ["admin.eatwithdigest.com"]
|
||||
|
||||
# CORS_ALLOWED_ORIGINS = [
|
||||
|
||||
@@ -17,8 +17,18 @@ Including another URLconf
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.shortcuts import render
|
||||
from django.urls import include, path
|
||||
|
||||
def handler404(request, exception):
|
||||
if request.user.is_authenticated:
|
||||
print("True")
|
||||
return render(request, '404.html')
|
||||
else:
|
||||
print("False")
|
||||
return render(request, '1404.html')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
|
||||
@@ -40,8 +50,13 @@ urlpatterns = [
|
||||
path('api/notification/', include("module_notification.api.urls")),
|
||||
]
|
||||
|
||||
# handler404 = handler404
|
||||
|
||||
if settings.DEBUG:
|
||||
import debug_toolbar
|
||||
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
urlpatterns += [path('__debug__/', include(debug_toolbar.urls))]
|
||||
|
||||
if not settings.DEBUG:
|
||||
handler404 = handler404
|
||||
|
||||
@@ -115,7 +115,7 @@ class ContactUsReplyView(LoginRequiredMixin, generic.View):
|
||||
email_service = EmailService(
|
||||
subject=f"Reply of your inquiry - {instance.subject}",
|
||||
to=instance.email_address,
|
||||
from_email=settings.EMAIL_HOST_USER,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
)
|
||||
email_service.set_text_body(message)
|
||||
email_service.send()
|
||||
@@ -162,7 +162,7 @@ def ContactUsLandingView(request):
|
||||
support_team_service = EmailService(
|
||||
subject=f"New contact us - {subject}",
|
||||
to=email,
|
||||
from_email=settings.EMAIL_HOST_USER,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
)
|
||||
|
||||
support_team_service.load_template(
|
||||
@@ -174,7 +174,7 @@ def ContactUsLandingView(request):
|
||||
thankyou_service = EmailService(
|
||||
subject=f"New contact us - {subject}",
|
||||
to=email,
|
||||
from_email=settings.EMAIL_HOST_USER,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
)
|
||||
|
||||
thankyou_service.load_template(
|
||||
|
||||
BIN
static/src/assets/img/404-page.gif
Normal file
BIN
static/src/assets/img/404-page.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
61
templates/1404.html
Normal file
61
templates/1404.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no">
|
||||
<title>Digest</title>
|
||||
{% load static %}
|
||||
<link rel="icon" type="image/x-icon" href="../src/assets/img/favicon.ico"/>
|
||||
<!-- BEGIN GLOBAL MANDATORY STYLES -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Nunito:400,600,700" rel="stylesheet">
|
||||
<link href="{% static 'src/bootstrap/css/bootstrap.min.css' %}" rel="stylesheet" type="text/css" />
|
||||
|
||||
<link href="{% static 'layouts/collapsible-menu/css/light/plugins.css' %}" rel="stylesheet" type="text/css" />
|
||||
<link href="{% static 'src/assets/css/light/pages/error/error.css' %}" rel="stylesheet" type="text/css" />
|
||||
|
||||
<!-- END GLOBAL MANDATORY STYLES -->
|
||||
|
||||
<style>
|
||||
body.dark .theme-logo.light-element {
|
||||
display: none;
|
||||
}
|
||||
.theme-logo.light-element {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.error-gif{
|
||||
background-image: url(https://cdn.dribbble.com/users/722246/screenshots/3066818/404-page.gif);
|
||||
height: 600px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body class="text-center">.
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-4 mr-auto mt-5 text-md-left text-center">
|
||||
{% comment %} <a href="index.html" class="ml-md-5">
|
||||
<img alt="image-404" src="../src/assets/img/logo.svg" class="dark-element theme-logo">
|
||||
<img alt="image-404" src="../src/assets/img/logo2.svg" class="light-element theme-logo">
|
||||
</a> {% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid error-content">
|
||||
<div class="">
|
||||
<h2>Ooops!</h2>
|
||||
<h3 class="m-1">The page you requested was not found!</h3>
|
||||
<a href="{% url 'module_auth:login'%}" class="btn btn-dark m-1">Login Page</a>
|
||||
<div class="error-gif"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN GLOBAL MANDATORY SCRIPTS -->
|
||||
<script src="{% static 'src/bootstrap/js/bootstrap.bundle.min.js' %}"></script>
|
||||
<!-- END GLOBAL MANDATORY SCRIPTS -->
|
||||
</body>
|
||||
</html>
|
||||
36
templates/404.html
Normal file
36
templates/404.html
Normal file
@@ -0,0 +1,36 @@
|
||||
{% extends 'base_structure/layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div id="content-wrapper">
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- Breadcrumbs-->
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'module_iam:dashboard'%}">Dashboard</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active">404 Error</li>
|
||||
</ol>
|
||||
|
||||
<!-- Page Content -->
|
||||
<h1 class="display-1">404</h1>
|
||||
<p class="lead">Page not found. You can
|
||||
<a class="btn" href="javascript:history.back()">go back</a>
|
||||
to the previous page, or
|
||||
<a class="btn" href="{% url 'module_iam:dashboard'%}">return home</a>.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
<div class="shadow-bottom"></div>
|
||||
<ul class="list-unstyled menu-categories" id="accordionExample">
|
||||
<li class="menu {% if page_name == iam_constants_context.RESOURCE_MANAGE_DASHBOARD %}active{% endif %}">
|
||||
<li class="menu {% if page_name == iam_constants_context.RESOURCE_MANAGE_DASHBOARD or not page_name%}active{% endif %}">
|
||||
<a href="{% url 'module_iam:dashboard'%}" aria-expanded="false" class="dropdown-toggle">
|
||||
<div class="">
|
||||
<span class="material-symbols-outlined">dashboard</span>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<p>Hello {{name}},</p>
|
||||
<p>It looks like you've requested a password reset for your account.</p>
|
||||
<p>To reset your password, please use the following secret code:</p>
|
||||
<p><strong>{{ code }}</strong></p>
|
||||
<h2><strong>{{ code }}</strong></h2>
|
||||
<p>If you didn't request a password reset, you can safely ignore this email.</p>
|
||||
<p>Thank you,</p>
|
||||
<p>Team Digest</p>
|
||||
|
||||
@@ -401,13 +401,14 @@ var mealUrl = "{% url 'module_activity:meal_detail' pk=0 %}"
|
||||
var medicationUrl = "{% url 'module_activity:medication_detail' pk=0 %}"
|
||||
var bowelUrl = "{% url 'module_activity:bowel_detail' pk=0 %}"
|
||||
var symptomUrl = "{% url 'module_activity:meal_symptom_detail' pk=0 %}"
|
||||
|
||||
var reportUrl = "{% url 'module_activity:report_data' principal_id=obj.id %}?date_range=date_range_data"
|
||||
|
||||
// Entry point
|
||||
$(document).ready(function() {
|
||||
tableName = $('#table');
|
||||
//dataTableInstance = initializeDataTable(tableName, activityMainUrl);
|
||||
|
||||
|
||||
var selectedDays = $('input[name="dateFilter"]:checked').val();
|
||||
getReportData(selectedDays);
|
||||
|
||||
@@ -538,7 +539,6 @@ function filterActivityData(dateRange){
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
function getReportData(days){
|
||||
console.log("data range is ", days)
|
||||
var url = days ? reportUrl.replace("date_range_data", days) : reportUrl
|
||||
|
||||
25
templates/module_iam/permission_denied.html
Normal file
25
templates/module_iam/permission_denied.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% extends 'base_structure/layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div id="content-wrapper">
|
||||
<div class="container-fluid text-center">
|
||||
<h1 class="w3-jumbo w3-animate-top w3-center"><code>Access Denied</code></h1>
|
||||
<hr class="w3-border-white w3-animate-left" style="margin:auto;width:50%">
|
||||
<h3 class="w3-center w3-animate-right">You dont have permission to view this site.</h3>
|
||||
<h3 class="w3-center w3-animate-zoom">🚫🚫🚫🚫</h3>
|
||||
<h6 class="w3-center w3-animate-zoom">error code:403 forbidden</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -68,6 +68,7 @@
|
||||
</div>
|
||||
|
||||
<!-- modal for View Feedback Question Answer -->
|
||||
|
||||
<div class="modal fade" id="feedbackmodal" tabindex="-1" role="dialog" aria-labelledby="feedbackmodalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" >
|
||||
@@ -126,7 +127,7 @@ function initializeDataTable(tableName, mainUrl) {
|
||||
{
|
||||
data: "comment",
|
||||
render: function (data, type, row) {
|
||||
return `<button class="btn btn-primary view-comment" data-comment="${data}">View</button>`;
|
||||
return `<button class="btn btn-primary view-comment" data-comment="${data}">View</button>`;
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user