mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-05 02:42:26 -05:00
@@ -1,11 +1,17 @@
|
||||
.column-one-quarter {
|
||||
@include grid-column(1/4);
|
||||
}
|
||||
|
||||
.column-three-quarters {
|
||||
@include grid-column(3/4);
|
||||
}
|
||||
|
||||
|
||||
.column-one-eighth {
|
||||
@include grid-column(1/8);
|
||||
}
|
||||
|
||||
.column-seven-eighths {
|
||||
@include grid-column(7/8);
|
||||
}
|
||||
|
||||
.bottom-gutter {
|
||||
margin-bottom: $gutter;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
.email-message {
|
||||
|
||||
margin-bottom: $gutter;
|
||||
border: 1px solid $border-colour;
|
||||
|
||||
&-subject {
|
||||
@include bold-19;
|
||||
border-bottom: 1px solid $border-colour;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
&-body {
|
||||
border-bottom: 1px solid $white;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
max-height: 103px;
|
||||
}
|
||||
|
||||
&-name {
|
||||
@include bold-19;
|
||||
margin: 50px 0 10px 0;
|
||||
margin: 20px 0 10px 0;
|
||||
}
|
||||
|
||||
&-subject,
|
||||
&-from {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
&-from {
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid $border-colour;
|
||||
}
|
||||
|
||||
&-body {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: $gutter-half 0 0 0;
|
||||
margin: 0 0 $gutter 0;
|
||||
clear: both;
|
||||
border-top: 1px solid $border-colour;
|
||||
border-bottom: 1px solid $border-colour;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
border-radius: 5px;
|
||||
white-space: normal;
|
||||
margin: 0 0 $gutter 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.sms-message-wrapper-with-radio {
|
||||
@@ -53,3 +54,8 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.sms-message-from {
|
||||
@include bold-19;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -85,3 +85,7 @@
|
||||
.textbox-help-link {
|
||||
margin: 5px 0 0 0;
|
||||
}
|
||||
|
||||
.textbox-right-aligned {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from flask import url_for
|
||||
from app import notifications_api_client
|
||||
from notifications_python_client.errors import HTTPError
|
||||
from app.utils import BrowsableItem
|
||||
|
||||
|
||||
@@ -26,6 +27,16 @@ def get_service_by_id(id_):
|
||||
return notifications_api_client.get_service(id_)
|
||||
|
||||
|
||||
def get_service_by_id_or_404(id_):
|
||||
try:
|
||||
return get_service_by_id(id_)
|
||||
except HTTPError as e:
|
||||
if e.status_code == 404:
|
||||
abort(404)
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
def get_services(user_id=None):
|
||||
if user_id:
|
||||
return notifications_api_client.get_services({'user_id': str(user_id)})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from datetime import datetime
|
||||
from notifications_python_client import HTTPError
|
||||
|
||||
from sqlalchemy.orm import load_only
|
||||
|
||||
@@ -47,9 +48,15 @@ def activate_user(user):
|
||||
|
||||
|
||||
def is_email_unique(email_address):
|
||||
if user_api_client.get_user_by_email(email_address):
|
||||
return False
|
||||
return True
|
||||
try:
|
||||
if user_api_client.get_user_by_email(email_address):
|
||||
return False
|
||||
return True
|
||||
except HTTPError as ex:
|
||||
if ex.status_code == 404:
|
||||
return True
|
||||
else:
|
||||
raise ex
|
||||
|
||||
|
||||
def request_password_reset(user):
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from flask import render_template, redirect, session, url_for
|
||||
import re
|
||||
|
||||
from flask import render_template, request, redirect, session, url_for
|
||||
from flask_login import login_required, current_user
|
||||
from app.main import main
|
||||
from app.main.dao import services_dao, users_dao
|
||||
@@ -15,13 +17,33 @@ def add_service():
|
||||
else:
|
||||
heading = 'Add a new service'
|
||||
if form.validate_on_submit():
|
||||
user = users_dao.get_user_by_id(session['user_id'])
|
||||
service_id = services_dao.insert_new_service(form.name.data, user.id)
|
||||
session['service_name'] = form.name.data
|
||||
return redirect(url_for('main.service_dashboard', service_id=service_id))
|
||||
return redirect(url_for('main.add_from_address'))
|
||||
else:
|
||||
return render_template(
|
||||
'views/add-service.html',
|
||||
form=form,
|
||||
heading=heading
|
||||
)
|
||||
|
||||
|
||||
@main.route("/confirm-add-service", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_from_address():
|
||||
if request.method == 'POST':
|
||||
user = users_dao.get_user_by_id(session['user_id'])
|
||||
service_id = services_dao.insert_new_service(session['service_name'], user.id)
|
||||
return redirect(url_for('main.service_dashboard', service_id=service_id))
|
||||
else:
|
||||
return render_template(
|
||||
'views/add-from-address.html',
|
||||
service_name=session['service_name'],
|
||||
from_address="{}@notifications.service.gov.uk".format(_email_safe(session['service_name']))
|
||||
)
|
||||
|
||||
|
||||
def _email_safe(string):
|
||||
return "".join([
|
||||
character.lower() if character.isalnum() or character == "." else ""
|
||||
for character in re.sub("\s+", ".", string.strip())
|
||||
])
|
||||
|
||||
@@ -26,7 +26,6 @@ def register():
|
||||
return redirect(url_for('main.choose_service'))
|
||||
|
||||
form = RegisterUserForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
if users_dao.is_email_unique(form.email_address.data):
|
||||
try:
|
||||
|
||||
@@ -127,6 +127,7 @@ def check_sms(service_id, upload_id):
|
||||
template_id = upload_data.get('template_id')
|
||||
raw_template = templates_dao.get_service_template_or_404(service_id, template_id)['data']
|
||||
upload_result = _get_rows(contents, raw_template)
|
||||
print(upload_result)
|
||||
template = Template(
|
||||
raw_template,
|
||||
values=upload_result['rows'][0] if upload_result['valid'] else {},
|
||||
|
||||
@@ -24,7 +24,7 @@ class NotificationsAdminAPIClient(NotificationsAPIClient):
|
||||
"name": service_name,
|
||||
"active": active,
|
||||
"limit": limit,
|
||||
"users": [user_id],
|
||||
"user_id": user_id,
|
||||
"restricted": restricted
|
||||
}
|
||||
return self.post("/service", data)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% macro email_message(subject, body, name=None, edit_link=None) %}
|
||||
{% macro email_message(subject, body, name=None, edit_link=None, from_name=None, from_address=None) %}
|
||||
{% if name %}
|
||||
<h3 class="email-message-name">
|
||||
{% if edit_link %}
|
||||
@@ -9,9 +9,30 @@
|
||||
</h3>
|
||||
{% endif %}
|
||||
<div class="email-message">
|
||||
<div class="email-message-subject">
|
||||
{{ subject }}
|
||||
</div>
|
||||
{% if from_name and from_address %}
|
||||
<div class="email-message-from">
|
||||
<div class="grid-row">
|
||||
<div class="column-one-eighth">
|
||||
<span class="form-hint">From</span>
|
||||
</div>
|
||||
<div class="column-seven-eighths">
|
||||
{{ from_name }} <{{ from_address }}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if subject %}
|
||||
<div class="email-message-subject">
|
||||
<div class="grid-row">
|
||||
<div class="column-one-eighth">
|
||||
<span class="form-hint">Subject</span>
|
||||
</div>
|
||||
<div class="column-seven-eighths">
|
||||
{{ subject }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="email-message-body">
|
||||
{{ body|nl2br }}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% macro sms_message(
|
||||
body, recipient=None, name=None, id=None, edit_link=None
|
||||
body, recipient=None, name=None, id=None, edit_link=None, from=None
|
||||
) %}
|
||||
{% if name %}
|
||||
<h3 class="sms-message-name">
|
||||
@@ -11,6 +11,11 @@
|
||||
</h3>
|
||||
{% endif %}
|
||||
<div class="sms-message-wrapper{% if input_name %}-with-radio{% endif %}">
|
||||
{% if from %}
|
||||
<span class="sms-message-from">
|
||||
{{ from }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{{ body }}
|
||||
</div>
|
||||
{% if recipient %}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
autofocus=False,
|
||||
help_link=None,
|
||||
help_link_text=None,
|
||||
width='2-3'
|
||||
width='2-3',
|
||||
suffix=None
|
||||
) %}
|
||||
<div class="form-group{% if field.errors %} error{% endif %}" {% if autofocus %}data-module="autofocus"{% endif %}>
|
||||
<label class="form-label" for="{{ field.name }}">
|
||||
@@ -22,9 +23,12 @@
|
||||
{% endif %}
|
||||
</label>
|
||||
{{ field(**{
|
||||
'class': 'form-control form-control-{} textbox-highlight-textbox'.format(width) if highlight_tags else 'form-control form-control-{}'.format(width),
|
||||
'class': 'form-control form-control-{} textbox-highlight-textbox'.format(width) if highlight_tags else 'form-control form-control-{} {}'.format(width, 'textbox-right-aligned' if suffix else ''),
|
||||
'data-module': 'highlight-tags' if highlight_tags else ''
|
||||
}) }}
|
||||
{% if suffix %}
|
||||
<span>{{ suffix }}</span>
|
||||
{% endif %}
|
||||
{% if help_link and help_link_text %}
|
||||
<p class="textbox-help-link">
|
||||
<a href='{{ help_link }}'>{{ help_link_text }}</a>
|
||||
|
||||
85
app/templates/email/email_template_1.html
Normal file
85
app/templates/email/email_template_1.html
Normal file
@@ -0,0 +1,85 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||||
<meta content="utf-8" http-equiv="encoding">
|
||||
<!-- This disables auto detection of phone numbers in some clients. Remove if not needed. -->
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<title>Page title</title>
|
||||
</head>
|
||||
<body style="font-family: Helvetica, Arial, sans-serif;font-size: 16px;margin: 0;color:#0b0c0c">
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td width="100%" height="53px" bgcolor="#0b0c0c">
|
||||
<table width="580" cellpadding="0" cellspacing="0" border="0" align="center">
|
||||
<tr>
|
||||
<td width="100%" bgcolor="#0b0c0c" valign="middle">
|
||||
<!-- This asset should be hosted by the service -->
|
||||
<img src="http://govuk-prototyping.herokuapp.com/hmrc-email/govuk_logotype_email.png" alt="GOV.UK" border="0">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#FFFFFF" style="margin-bottom: 19px">
|
||||
<tr>
|
||||
<td width="100%">
|
||||
<table width="580" cellpadding="0" cellspacing="0" border="0" align="center">
|
||||
<tr>
|
||||
<td width="75%">
|
||||
|
||||
<h1 style="font-size: 36px; line-height: 1.315789474; font-weight: 700; margin: 19px 0 38px 0;">
|
||||
This is the page title
|
||||
</h1>
|
||||
|
||||
<p style="font-size: 19px; line-height: 1.315789474; margin: 30px 0 30px 0;">
|
||||
Dear A Name,
|
||||
</p>
|
||||
|
||||
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
|
||||
This is the first paragraph.
|
||||
</p>
|
||||
|
||||
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
|
||||
This is the second paragraph. It is longer than the first and wraps on to two lines.
|
||||
</p>
|
||||
|
||||
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
|
||||
This paragraph includes a line-break:<br/>
|
||||
This is the line after the line-break.
|
||||
</p>
|
||||
|
||||
<p style="border-left: 10px #dee0e2 solid; padding: 14px 0 14px 14px; font-size: 19px; line-height: 1.315789474; margin: 0 0 19px 0;">
|
||||
This is an inset paragraph used for data or callouts<br/>
|
||||
Application reference: 123456789<br/>
|
||||
Date received: 15 March 2015<br/>
|
||||
</p>
|
||||
|
||||
<h2 style="font-size: 24px; line-height: 1.315789474; font-weight: 700; margin: 38px 0 19px 0;">
|
||||
Second level heading</h2>
|
||||
<h3 style="font-size: 19px; line-height: 1.315789474; font-weight: 700; margin: 38px 0 19px 0;">
|
||||
Third level heading
|
||||
</h3>
|
||||
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
|
||||
Intro before a url:
|
||||
<!-- URLs have white-space: nowrap so that they don't wrap -->
|
||||
<a style="white-space: nowrap" href="www.gov.uk/service-start-page">www.gov.uk/service-start-page</a>
|
||||
</p>
|
||||
|
||||
<p style="font-weight: 700; font-size: 19px; line-height: 1.315789474; margin: 38px 0 38px 0;">
|
||||
Department or agency name
|
||||
</p>
|
||||
|
||||
</td>
|
||||
<td width="25%"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
95
app/templates/email/email_template_2.html
Normal file
95
app/templates/email/email_template_2.html
Normal file
@@ -0,0 +1,95 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>Page title</title>
|
||||
|
||||
<style>
|
||||
@media only screen and (min-device-width: 581px) {
|
||||
.content {
|
||||
width: 580px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="font-family: Helvetica, Arial, sans-serif;font-size: 16px;margin: 0;color:#0b0c0c">
|
||||
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<table width="580" align="center" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td width="100%" height="53px" bgcolor="#0b0c0c">
|
||||
<table width="580" cellpadding="0" cellspacing="0" border="0" align="center">
|
||||
<tr>
|
||||
<td width="100%" bgcolor="#0b0c0c" valign="middle">
|
||||
<!-- This asset should be hosted by the service -->
|
||||
<img src="http://govuk-prototyping.herokuapp.com/hmrc-email/govuk_logotype_email.png" alt="GOV.UK" border="0">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<table class="content" align="center" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width: 580px;">
|
||||
<tr>
|
||||
<td>
|
||||
<h1 style="font-size: 36px; line-height: 1.315789474; font-weight: 700; margin: 19px 0 38px 0;">
|
||||
This is the page title
|
||||
</h1>
|
||||
|
||||
<p style="font-size: 19px; line-height: 1.315789474; margin: 30px 0 30px 0;">
|
||||
Dear A Name,
|
||||
</p>
|
||||
|
||||
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
|
||||
This is the first paragraph.
|
||||
</p>
|
||||
|
||||
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
|
||||
This is the second paragraph. It is longer than the first and wraps on to two lines.
|
||||
</p>
|
||||
|
||||
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
|
||||
This paragraph includes a line-break:<br/>
|
||||
This is the line after the line-break.
|
||||
</p>
|
||||
|
||||
<p style="border-left: 10px #dee0e2 solid; padding: 14px 0 14px 14px; font-size: 19px; line-height: 1.315789474; margin: 0 0 19px 0;">
|
||||
This is an inset paragraph used for data or callouts<br/>
|
||||
Application reference: 123456789<br/>
|
||||
Date received: 15 March 2015<br/>
|
||||
</p>
|
||||
|
||||
<h2 style="font-size: 24px; line-height: 1.315789474; font-weight: 700; margin: 38px 0 19px 0;">
|
||||
Second level heading</h2>
|
||||
<h3 style="font-size: 19px; line-height: 1.315789474; font-weight: 700; margin: 38px 0 19px 0;">
|
||||
Third level heading
|
||||
</h3>
|
||||
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
|
||||
Intro before a url:
|
||||
<!-- URLs have white-space: nowrap so that they don't wrap -->
|
||||
<a style="white-space: nowrap" href="www.gov.uk/service-start-page">www.gov.uk/service-start-page</a>
|
||||
</p>
|
||||
|
||||
<p style="font-weight: 700; font-size: 19px; line-height: 1.315789474; margin: 38px 0 38px 0;">
|
||||
Department or agency name
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
42
app/templates/views/add-from-address.html
Normal file
42
app/templates/views/add-from-address.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{% extends "withoutnav_template.html" %}
|
||||
{% from "components/textbox.html" import textbox %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
{% from "components/sms-message.html" import sms_message %}
|
||||
{% from "components/email-message.html" import email_message %}
|
||||
|
||||
{% block page_title %}
|
||||
Preview your service name – GOV.UK Notify
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<h1 class="heading-large">
|
||||
Preview your service name
|
||||
</h1>
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
{{ sms_message(
|
||||
"{}: we received your payment, thank you".format(service_name),
|
||||
name="Text message",
|
||||
recipient='Sent from 40604'
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class='column-two-thirds'>
|
||||
{{ email_message(
|
||||
subject="We received your payment, thank you",
|
||||
body="Dear Alice Smith,\n\nThank you for…",
|
||||
from_name=service_name,
|
||||
from_address=from_address,
|
||||
name="Email",
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
{{page_footer('Looks good', back_link=url_for(".add_service"))}}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@@ -12,27 +12,21 @@
|
||||
<div class="column-two-thirds">
|
||||
|
||||
<h1 class="heading-large">
|
||||
{{ heading }}
|
||||
When people receive notifications, who should they be from?
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
Users will see your service name when they receive messages through GOV.UK
|
||||
Notify:
|
||||
Be specific. Remember that there might be other people in your
|
||||
organisation using GOV.UK Notify.
|
||||
</p>
|
||||
|
||||
<ul class="list-bullet bottom-gutter">
|
||||
<li>
|
||||
at the start of every text message, eg ‘Vehicle tax: we received your
|
||||
payment, thank you’
|
||||
</li>
|
||||
<li>
|
||||
as your email sender name
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<form autocomplete="off" method="post">
|
||||
|
||||
{{ textbox(form.name, hint="You can change this later") }}
|
||||
|
||||
|
||||
{{ page_footer('Continue') }}
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -31,6 +31,45 @@ def test_should_add_service_and_redirect_to_next_page(app_,
|
||||
url_for('main.add_service'),
|
||||
data={'name': 'testing the post'})
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for('main.add_from_address', _external=True)
|
||||
|
||||
|
||||
def test_should_confirm_add_service(
|
||||
app_,
|
||||
mock_login,
|
||||
mock_get_services,
|
||||
api_user_active,
|
||||
mock_get_user,
|
||||
mock_get_user_by_email
|
||||
):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(api_user_active)
|
||||
with client.session_transaction() as session:
|
||||
session['service_name'] = 'Renew Your Pet Passport'
|
||||
response = client.get(url_for('main.add_from_address'))
|
||||
assert response.status_code == 200
|
||||
assert 'Preview your service name' in response.get_data(as_text=True)
|
||||
assert 'Renew Your Pet Passport' in response.get_data(as_text=True)
|
||||
assert 'renew.your.pet.passport@notifications.service.gov.uk' in response.get_data(as_text=True)
|
||||
|
||||
|
||||
def test_should_add_service_after_confirmation(
|
||||
app_,
|
||||
mock_login,
|
||||
mock_create_service,
|
||||
mock_get_services,
|
||||
api_user_active,
|
||||
mock_get_user,
|
||||
mock_get_user_by_email
|
||||
):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(api_user_active)
|
||||
with client.session_transaction() as session:
|
||||
session['service_name'] = 'Renew Your Pet Passport'
|
||||
response = client.post(url_for('main.add_from_address'))
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for('main.service_dashboard', service_id=101, _external=True)
|
||||
assert mock_create_service.called
|
||||
assert mock_get_services.called
|
||||
|
||||
Reference in New Issue
Block a user