API & Client - update registration activation on user register/delete

+ minor refactor on administration (fix #15)
This commit is contained in:
Sam 2020-05-02 22:00:11 +02:00
parent e28d609013
commit 5d7b63f7aa
10 changed files with 193 additions and 10 deletions

View File

@ -1,4 +1,6 @@
from fittrackee_api import db from fittrackee_api import db
from flask import current_app
from sqlalchemy.event import listens_for
from ..users.models import User from ..users.models import User
@ -26,3 +28,25 @@ class AppConfig(db.Model):
"max_zip_file_size": self.max_zip_file_size, "max_zip_file_size": self.max_zip_file_size,
"max_users": self.max_users, "max_users": self.max_users,
} }
def update_app_config():
config = AppConfig.query.first()
if config:
current_app.config[
'is_registration_enabled'
] = config.is_registration_enabled
@listens_for(User, 'after_insert')
def on_user_insert(mapper, connection, user):
@listens_for(db.Session, 'after_flush', once=True)
def receive_after_flush(session, context):
update_app_config()
@listens_for(User, 'after_delete')
def on_user_delete(mapper, connection, old_user):
@listens_for(db.Session, 'after_flush', once=True)
def receive_after_flush(session, context):
update_app_config()

View File

@ -62,7 +62,6 @@ def app_config():
config.max_single_file_size = 1048576 config.max_single_file_size = 1048576
config.max_zip_file_size = 10485760 config.max_zip_file_size = 10485760
config.max_users = 0 config.max_users = 0
config.registration = False
db.session.add(config) db.session.add(config)
db.session.commit() db.session.commit()
return config return config

View File

@ -836,3 +836,70 @@ def test_update_user_invalid_picture(app, user_1):
assert data['status'] == 'fail' assert data['status'] == 'fail'
assert data['message'] == 'File extension not allowed.' assert data['message'] == 'File extension not allowed.'
assert response.status_code == 400 assert response.status_code == 400
def test_it_disables_registration_on_user_registration(
app_no_config, app_config, user_1_admin, user_2
):
app_config.max_users = 3
client = app_no_config.test_client()
client.post(
'/api/auth/register',
data=json.dumps(
dict(
username='sam',
email='sam@test.com',
password='12345678',
password_conf='12345678',
)
),
content_type='application/json',
)
response = client.post(
'/api/auth/register',
data=json.dumps(
dict(
username='new',
email='new@test.com',
password='12345678',
password_conf='12345678',
)
),
content_type='application/json',
)
assert response.status_code == 403
data = json.loads(response.data.decode())
assert data['status'] == 'error'
assert data['message'] == 'Error. Registration is disabled.'
def test_it_does_not_disable_registration_on_user_registration(
app_no_config, app_config, user_1_admin, user_2,
):
app_config.max_users = 4
client = app_no_config.test_client()
client.post(
'/api/auth/register',
data=json.dumps(
dict(
username='sam',
email='sam@test.com',
password='12345678',
password_conf='12345678',
)
),
content_type='application/json',
)
response = client.post(
'/api/auth/register',
data=json.dumps(
dict(
username='new',
email='new@test.com',
password='12345678',
password_conf='12345678',
)
),
content_type='application/json',
)
assert response.status_code == 201

View File

@ -1252,3 +1252,70 @@ def test_admin_can_not_delete_its_own_account_if_no_other_admin(
'You can not delete your account, no other user has admin rights.' 'You can not delete your account, no other user has admin rights.'
in data['message'] in data['message']
) )
def test_it_enables_registration_on_user_delete(
app_no_config, app_config, user_1_admin, user_2, user_3
):
app_config.max_users = 3
client = app_no_config.test_client()
resp_login = client.post(
'/api/auth/login',
data=json.dumps(dict(email='admin@example.com', password='12345678')),
content_type='application/json',
)
client.delete(
'/api/users/toto',
headers=dict(
Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token']
),
)
response = client.post(
'/api/auth/register',
data=json.dumps(
dict(
username='justatest',
email='test@test.com',
password='12345678',
password_conf='12345678',
)
),
content_type='application/json',
)
assert response.status_code == 201
def test_it_does_not_enable_registration_on_user_delete(
app_no_config, app_config, user_1_admin, user_2, user_3
):
app_config.max_users = 2
client = app_no_config.test_client()
resp_login = client.post(
'/api/auth/login',
data=json.dumps(dict(email='admin@example.com', password='12345678')),
content_type='application/json',
)
client.delete(
'/api/users/toto',
headers=dict(
Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token']
),
)
response = client.post(
'/api/auth/register',
data=json.dumps(
dict(
username='justatest',
email='test@test.com',
password='12345678',
password_conf='12345678',
)
),
content_type='application/json',
)
assert response.status_code == 403
data = json.loads(response.data.decode())
assert data['status'] == 'error'
assert data['message'] == 'Error. Registration is disabled.'

View File

@ -3,6 +3,7 @@ import FitTrackeeApi from '../fitTrackeeApi/auth'
import { history } from '../index' import { history } from '../index'
import { generateIds } from '../utils' import { generateIds } from '../utils'
import { getOrUpdateData, setError, updateLanguage } from './index' import { getOrUpdateData, setError, updateLanguage } from './index'
import { getAppData } from './application'
const AuthError = message => ({ type: 'AUTH_ERROR', message }) const AuthError = message => ({ type: 'AUTH_ERROR', message })
@ -50,6 +51,9 @@ export const loginOrRegister = (target, formData) => dispatch =>
.then(ret => { .then(ret => {
if (ret.status === 'success') { if (ret.status === 'success') {
window.localStorage.setItem('authToken', ret.auth_token) window.localStorage.setItem('authToken', ret.auth_token)
if (target === 'register') {
dispatch(getAppData('config'))
}
return dispatch(getProfile()) return dispatch(getProfile())
} }
return dispatch(AuthError(ret.message)) return dispatch(AuthError(ret.message))
@ -138,6 +142,7 @@ export const deleteUser = (username, isAdmin = false) => dispatch =>
FitTrackeeGenericApi.deleteData('users', username) FitTrackeeGenericApi.deleteData('users', username)
.then(ret => { .then(ret => {
if (ret.status === 204) { if (ret.status === 204) {
dispatch(getAppData('config'))
if (isAdmin) { if (isAdmin) {
history.push('/admin/users') history.push('/admin/users')
} else { } else {

View File

@ -82,8 +82,15 @@ class AdminApplication extends React.Component {
> >
{t( {t(
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
'administration:Max. number of active users (if 0, no limitation)' 'administration:Max. number of active users'
)} )}
<sup>
<i
className="fa fa-question-circle"
aria-hidden="true"
title={t('administration:if 0, no limitation')}
/>
</sup>
: :
</label> </label>
<input <input
@ -197,7 +204,6 @@ class AdminApplication extends React.Component {
export default connect( export default connect(
state => ({ state => ({
appConfig: state.application.config,
message: state.message, message: state.message,
}), }),
dispatch => ({ dispatch => ({

View File

@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'
import AdminStats from './AdminStats' import AdminStats from './AdminStats'
export default function AdminDashboard(props) { export default function AdminDashboard(props) {
const { t } = props const { appConfig, t } = props
return ( return (
<div className="card activity-card"> <div className="card activity-card">
<div className="card-header"> <div className="card-header">
@ -27,7 +27,15 @@ export default function AdminDashboard(props) {
{t( {t(
'administration:Update application configuration ' + 'administration:Update application configuration ' +
'(maximum number of registered users, maximum files size).' '(maximum number of registered users, maximum files size).'
)}{' '} )}
<br />
<strong>
{t(
`administration:Registration is currently ${
appConfig.is_registration_enabled ? 'enabled' : 'disabled'
}.`
)}
</strong>
</dd> </dd>
<br /> <br />
<dt> <dt>

View File

@ -11,7 +11,7 @@ import AdminUsers from './AdminUsers'
import NotFound from './../Others/NotFound' import NotFound from './../Others/NotFound'
function Admin(props) { function Admin(props) {
const { t, user } = props const { appConfig, t, user } = props
return ( return (
<> <>
<Helmet> <Helmet>
@ -23,12 +23,12 @@ function Admin(props) {
<Route <Route
exact exact
path="/admin" path="/admin"
render={() => <AdminDashboard t={t} />} render={() => <AdminDashboard appConfig={appConfig} t={t} />}
/> />
<Route <Route
exact exact
path="/admin/application" path="/admin/application"
render={() => <AdminApplication t={t} />} render={() => <AdminApplication appConfig={appConfig} t={t} />}
/> />
<Route <Route
exact exact
@ -52,6 +52,7 @@ function Admin(props) {
export default withTranslation()( export default withTranslation()(
connect(state => ({ connect(state => ({
appConfig: state.application.config,
user: state.user, user: state.user,
}))(Admin) }))(Admin)
) )

View File

@ -13,14 +13,17 @@
"Enable/disable sports.": "Enable/disable sports.", "Enable/disable sports.": "Enable/disable sports.",
"FitTrackee administration": "FitTrackee administration", "FitTrackee administration": "FitTrackee administration",
"id": "id", "id": "id",
"if 0, no limitation": "if 0, no limitation",
"Image": "Image", "Image": "Image",
"Label": "Label", "Label": "Label",
"Max. number of active users (if 0, no limitation)": "Max. number of active users", "Max. number of active users": "Max. number of active users",
"Max. files of zip archive": "Max. files of zip archive", "Max. files of zip archive": "Max. files of zip archive",
"Max. size of uploaded files": "Max. size of uploaded files", "Max. size of uploaded files": "Max. size of uploaded files",
"Max. size of uploaded files (in Mb)": "Max. size of uploaded files (in Mb)", "Max. size of uploaded files (in Mb)": "Max. size of uploaded files (in Mb)",
"Max. size of zip archive": "Max. size of zip archive", "Max. size of zip archive": "Max. size of zip archive",
"Max. size of zip archive (in Mb)": "Max. size of zip archive (in Mb)", "Max. size of zip archive (in Mb)": "Max. size of zip archive (in Mb)",
"Registration is currently disabled.": "Registration is currently disabled.",
"Registration is currently enabled.": "Registration is currently enabled.",
"Remove admin rights": "Remove admin rights", "Remove admin rights": "Remove admin rights",
"Sports": "Sports", "Sports": "Sports",
"Update application configuration (maximum number of registered users, maximum files size).": "Update application configuration (maximum number of registered users, maximum files size).", "Update application configuration (maximum number of registered users, maximum files size).": "Update application configuration (maximum number of registered users, maximum files size).",

View File

@ -13,14 +13,17 @@
"Enable/disable sports.": "Activer/désactiver des sports.", "Enable/disable sports.": "Activer/désactiver des sports.",
"FitTrackee administration": "Administration de FitTrackee", "FitTrackee administration": "Administration de FitTrackee",
"id": "id", "id": "id",
"if 0, no limitation": "si égal à 0, pas limite d'inscription",
"Image": "Image", "Image": "Image",
"Label": "Label", "Label": "Label",
"Max. number of active users (if 0, no limitation)": "Nombre maximum d'utilisateurs actifs", "Max. number of active users": "Nombre maximum d'utilisateurs actifs",
"Max. files of zip archive": "Nombre max. de fichiers dans une archive zip", "Max. files of zip archive": "Nombre max. de fichiers dans une archive zip",
"Max. size of uploaded files": "Taille max. des fichiers", "Max. size of uploaded files": "Taille max. des fichiers",
"Max. size of uploaded files (in Mb)": "Taille max. des fichiers (en Mo)", "Max. size of uploaded files (in Mb)": "Taille max. des fichiers (en Mo)",
"Max. size of zip archive": "Taille max. des archives zip", "Max. size of zip archive": "Taille max. des archives zip",
"Max. size of zip archive (in Mb)": "Taille max. des archives zip (en Mo)", "Max. size of zip archive (in Mb)": "Taille max. des archives zip (en Mo)",
"Registration is currently disabled.": "Les inscriptions sont actuellement désactivées",
"Registration is currently enabled.": "Les inscriptions sont actuellement activées",
"Remove admin rights": "Retirer des droits d'admin", "Remove admin rights": "Retirer des droits d'admin",
"Sports": "Sports", "Sports": "Sports",
"Update application configuration (maximum number of registered users, maximum files size).": "Configurer l'application (nombre maximum d'utilisateurs inscrits, taille maximale des fichers).", "Update application configuration (maximum number of registered users, maximum files size).": "Configurer l'application (nombre maximum d'utilisateurs inscrits, taille maximale des fichers).",