Skip to content

Commit

Permalink
nicer signup form
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusandra committed Sep 25, 2024
1 parent 60adeb7 commit d43fb19
Show file tree
Hide file tree
Showing 23 changed files with 346 additions and 158 deletions.
3 changes: 0 additions & 3 deletions backend/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,4 @@ def create_app(config: Optional[Config] = None):
from app.api import api as api_blueprint
app.register_blueprint(api_blueprint, url_prefix='/api')

from app.views import views as views_blueprint
app.register_blueprint(views_blueprint, url_prefix='/')

return app
1 change: 1 addition & 0 deletions backend/app/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .log import *
from .login import *
from .repositories import *
from .signup import *
from .settings import *
from .templates import *
from .misc import *
10 changes: 5 additions & 5 deletions backend/app/api/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ def login():
if User.query.first() is None:
return jsonify({'error': 'Please register a user first!'}), 404

username = request.json.get('username', None)
email = request.json.get('email', None)
password = request.json.get('password', None)

if username is not None and password is not None:
user = User.query.filter_by(username=username).first()
if email is not None and password is not None:
user = User.query.filter_by(email=email).first()
if user is None or not user.check_password(password):
return jsonify({'error': 'Invalid username or password'}), 401
return jsonify({'error': 'Invalid email or password'}), 401
login_user(user, remember=True)
return jsonify({'success': True})
return jsonify({'error': 'Please specify an username and a password'}), 401
return jsonify({'error': 'Please specify an email and a password'}), 401

@api.route('/logout', methods=['POST'])
@login_required
Expand Down
42 changes: 42 additions & 0 deletions backend/app/api/signup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# backend/app/api/signup.py

from flask import request, jsonify
from flask_login import login_user
from app import db
from . import api
from app.models.user import User

@api.route('/signup', methods=['POST'])
def signup():
# Check if there is already a user registered
if User.query.first() is not None:
return jsonify({'error': 'Only one user is allowed. Please login!'}), 400

data = request.get_json()
if not data:
return jsonify({'error': 'Invalid input'}), 400

email = data.get('email')
password = data.get('password')
password2 = data.get('password2')

errors = {}

if not email:
errors['email'] = 'Email is required.'
if not password:
errors['password'] = 'Password is required.'
if password != password2:
errors['password2'] = 'Passwords do not match.'
if User.query.filter_by(email=email).first():
errors['email'] = 'Please use a different email address.'

if errors:
return jsonify({'errors': errors}), 400

user = User(email=email)
user.set_password(password)
db.session.add(user)
db.session.commit()
login_user(user, remember=True)
return jsonify({'success': True}), 201
10 changes: 5 additions & 5 deletions backend/app/api/tests/test_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@

class TestLogin(BaseTestCase):
def init_user(self):
self.user = User(username='test', email='me@test.com')
self.user = User(email='me@test.com')
self.user.set_password('banana')
db.session.add(self.user)
db.session.commit()

def test_login_valid(self):
data = {'username': 'test', 'password': 'banana'}
data = {'email': 'me@test.com', 'password': 'banana'}
response = self.client.post('/api/login', json=data)
assert response.status_code == 200

def test_login_invalid_user(self):
data = {'username': 'notfound', 'password': 'banana'}
data = {'email': 'notfound', 'password': 'banana'}
response = self.client.post('/api/login', json=data)
assert response.status_code == 401

def test_login_invalid_pass(self):
data = {'username': 'test', 'password': 'notvalid'}
data = {'email': 'me@test.com', 'password': 'notvalid'}
response = self.client.post('/api/login', json=data)
assert response.status_code == 401

Expand All @@ -32,7 +32,7 @@ def test_post_login_access(self):
assert response.json == {"error": "Unauthorized"}

# login
data = {'username': 'test', 'password': 'banana'}
data = {'email': 'me@test.com', 'password': 'banana'}
response = self.client.post('/api/login', json=data)
assert response.status_code == 200

Expand Down
65 changes: 65 additions & 0 deletions backend/app/api/tests/test_signup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from app.tests.base import BaseTestCase
from app import db
from app.models.user import User

class SignupTestCase(BaseTestCase):
def init_user(self):
pass

def test_successful_signup(self):
"""Test signing up with valid data."""
response = self.client.post('/api/signup', json={
'email': 'test@example.com',
'password': 'password123',
'password2': 'password123'
})
# self.assertEqual(response.status_code, 201)
data = response.get_json()
self.assertTrue(data.get('success'))

# Ensure the user is created in the database
user = User.query.filter_by(email='test@example.com').first()
self.assertIsNotNone(user)
self.assertTrue(user.check_password('password123'))

def test_signup_with_missing_data(self):
"""Test signup with missing email and password fields."""
response = self.client.post('/api/signup', json={})
self.assertEqual(response.status_code, 400)
data = response.get_json()
self.assertEqual(data['error'], 'Invalid input')

def test_signup_with_password_mismatch(self):
"""Test signup when passwords do not match."""
response = self.client.post('/api/signup', json={
'email': 'test@example.com',
'password': 'password123',
'password2': 'differentpassword'
})
self.assertEqual(response.status_code, 400)
data = response.get_json()
self.assertIn('errors', data)
self.assertIn('password2', data['errors'])
self.assertEqual(data['errors']['password2'], 'Passwords do not match.')

def test_signup_only_one_user(self):
"""Test signup with an email that is already in use."""
# Create an existing user
user = User(email='test@example.com')
user.set_password('password123')
db.session.add(user)
db.session.commit()
response = self.client.post('/api/signup', json={
'email': 'test@example.com',
'password': 'newpassword',
'password2': 'newpassword'
})
self.assertEqual(response.status_code, 400)
data = response.get_json()
self.assertIn('error', data)
self.assertEqual(data['error'], 'Only one user is allowed. Please login!')

def test_signup_with_invalid_input(self):
"""Test signup with invalid JSON input."""
response = self.client.post('/api/signup', data="Not a JSON")
self.assertEqual(response.status_code, 415)
1 change: 0 additions & 1 deletion backend/app/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(150), unique=True)
email = db.Column(db.String(120), unique=True)
password = db.Column(db.String(128))

Expand Down
12 changes: 6 additions & 6 deletions backend/app/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def setUp(self):
self.init_tests()

def init_user(self):
self.create_user("tester", "test@example.com", "testpassword")
login_response = self.login("tester", "testpassword")
self.create_user( "test@example.com", "testpassword")
login_response = self.login("test@example.com", "testpassword")
assert login_response.status_code == 200, (login_response.status_code, login_response.data)

def init_tests(self):
Expand All @@ -34,19 +34,19 @@ def init_tests(self):
def tearDown(self):
self.app_context.pop()

def create_user(self, username, email, password):
def create_user(self, email, password):
try:
user = User(username=username, email=email)
user = User(email=email)
user.set_password(password)
db.session.add(user)
db.session.commit()
except:
db.session.rollback()
raise

def login(self, username, password):
def login(self, email, password):
return self.client.post('/api/login', json=dict(
username=username,
email=email,
password=password
), follow_redirects=True)

Expand Down
5 changes: 0 additions & 5 deletions backend/app/views/__init__.py

This file was deleted.

35 changes: 22 additions & 13 deletions backend/app/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
import io
import json

from flask import Flask, current_app, flash, redirect, url_for, request, jsonify
from flask import Flask, current_app, flash, redirect, request, jsonify
from flask_login import current_user
from app import login_manager

def has_first_user():
from app.models import User
return User.query.first() is not None

def setup_base_routes(app: Flask):
@login_manager.user_loader
def load_user(user_id):
Expand All @@ -17,26 +21,31 @@ def unauthorized():
if request.is_json or request.path.startswith('/api/'):
return jsonify({'error': 'Unauthorized'}), 401

from app.models import User # Import here to avoid circular dependencies
if User.query.first() is None:
flash('Please register the first user!')
return redirect(url_for('views.register'))
else:
if has_first_user():
flash('Please login!')
return redirect('/login')
else:
flash('Please register the first user!')
return redirect('/signup')

@app.errorhandler(404)
def not_found(e):
if request.is_json or request.path.startswith('/api/'):
return jsonify({'error': 'Not found'}), 404
if not current_user.is_authenticated and not request.path.startswith('/login'):
from app.models import User # Import here to avoid circular dependencies
if User.query.first() is None:
flash('Please register the first user!')
return redirect(url_for('views.register'))
if not current_user.is_authenticated:
if request.path.startswith('/signup'):
if has_first_user():
return redirect('/login')
elif request.path.startswith('/login'):
if not has_first_user():
return redirect('/signup')
else:
flash('Please login!')
return redirect('/login')
if has_first_user():
flash('Please login!')
return redirect('/login')
else:
flash('Please register the first user!')
return redirect('/signup')
return current_app.send_static_file('index.html')

@app.before_request
Expand Down
43 changes: 0 additions & 43 deletions backend/app/views/register.py

This file was deleted.

32 changes: 32 additions & 0 deletions backend/migrations/versions/c355701b0e9a_remove_username.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""remove username
Revision ID: c355701b0e9a
Revises: 701d50ac6812
Create Date: 2024-09-25 21:00:04.138591
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'c355701b0e9a'
down_revision = '701d50ac6812'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.drop_column('username')

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.add_column(sa.Column('username', sa.VARCHAR(length=150), nullable=True))

# ### end Alembic commands ###
29 changes: 0 additions & 29 deletions backend/templates/login.html

This file was deleted.

Loading

0 comments on commit d43fb19

Please sign in to comment.