diff --git a/CHANGELOG.md b/CHANGELOG.md index df5bbd71..e3e85605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Split DrawMap commponents into Layers and DrawTools [#57](https://github.com/azavea/iow-boundary-tool/pull/57) - Make `Roles` enum an `IntEnum` subclass [#74](https://github.com/azavea/iow-boundary-tool/pull/74) - Drop `Role` table and refactor as choice field on User [#117](https://github.com/azavea/iow-boundary-tool/pull/117) +- Return user information from login endpoint [#136](https://github.com/azavea/iow-boundary-tool/pull/136) ### Fixed diff --git a/src/app/src/components/NavBar.js b/src/app/src/components/NavBar.js index 0b976f88..6266f304 100644 --- a/src/app/src/components/NavBar.js +++ b/src/app/src/components/NavBar.js @@ -14,7 +14,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { ArrowLeftIcon, CogIcon, LogoutIcon } from '@heroicons/react/outline'; import apiClient from '../api/client'; import { API_URLS, NAVBAR_HEIGHT } from '../constants'; -import { logout, setSelectedUtility } from '../store/authSlice'; +import { logout, setUtilityByPwsid } from '../store/authSlice'; const NAVBAR_VARIANTS = { DRAW: 'draw', @@ -102,31 +102,33 @@ function ExitButton({ variant }) { function UtilityControl({ variant }) { const dispatch = useDispatch(); - // placeholders - const utilities = ['Raleigh City of', 'Azavea Test Utility']; - const selectedUtility = - useSelector(state => state.auth.selectedUtility) || utilities[0]; + const utilities = useSelector(state => state.auth.user.utilities); + const utility = useSelector(state => state.auth.utility); - return variant === NAVBAR_VARIANTS.SUBMISSION && utilities.length > 1 ? ( + if (!utility) { + return null; + } + + return variant === NAVBAR_VARIANTS.SUBMISSION && utilities?.length > 1 ? ( ) : ( - {selectedUtility} + {utility.name} ); } diff --git a/src/app/src/components/PrivateRoute.js b/src/app/src/components/PrivateRoute.js index f006ecf0..cac764ad 100644 --- a/src/app/src/components/PrivateRoute.js +++ b/src/app/src/components/PrivateRoute.js @@ -7,7 +7,7 @@ import { API_URLS } from '../constants'; import { login, setLocationBeforeAuth } from '../store/authSlice'; export default function PrivateRoute({ children }) { - const signedIn = useSelector(state => state.auth.signedIn); + const user = useSelector(state => state.auth.user); const location = useLocation(); const navigate = useNavigate(); const dispatch = useDispatch(); @@ -17,12 +17,12 @@ export default function PrivateRoute({ children }) { // Log in this user if they have an authenticated session // Handles losing Redux state on refresh useEffect(() => { - if (!signedIn) { + if (!user) { dispatch(setLocationBeforeAuth(location)); apiClient .get(API_URLS.LOGIN) - .then(() => { - dispatch(login()); + .then(({ data: user }) => { + dispatch(login(user)); }) .catch(() => { if (!locationIsLogin) { @@ -30,7 +30,7 @@ export default function PrivateRoute({ children }) { } }); } - }, [signedIn, location, locationIsLogin, navigate, dispatch]); + }, [user, location, locationIsLogin, navigate, dispatch]); return locationIsLogin ? null : children; } diff --git a/src/app/src/components/Submissions/List.js b/src/app/src/components/Submissions/List.js index ffd72600..738e7cb5 100644 --- a/src/app/src/components/Submissions/List.js +++ b/src/app/src/components/Submissions/List.js @@ -113,7 +113,9 @@ function TableRows() { } } -function TableRow({ boundary: { id, location, pwsid, last_modified, status } }) { +function TableRow({ + boundary: { id, location, pwsid, last_modified, status }, +}) { const navigate = useNavigate(); return ( diff --git a/src/app/src/pages/Login.js b/src/app/src/pages/Login.js index 7eb3cf07..f8854b78 100644 --- a/src/app/src/pages/Login.js +++ b/src/app/src/pages/Login.js @@ -22,8 +22,8 @@ export default function Login() { email, password, }) - .then(() => { - dispatch(login()); + .then(({ data: user }) => { + dispatch(login(user)); }) .catch(apiError => { if (apiError.response?.status === API_STATUSES.REDIRECT) { @@ -34,20 +34,20 @@ export default function Login() { }); }; - const signedIn = useSelector(state => state.auth.signedIn); + const user = useSelector(state => state.auth.user); const locationBeforeAuth = useSelector( state => state.auth.locationBeforeAuth ); // Upon successful sign in, redirect if specified (e.g. by /login route) useEffect(() => { - if (signedIn) { + if (user) { navigate(locationBeforeAuth, { replace: true }); } if (forgotPassword) { navigate('/forgot'); } - }, [navigate, signedIn, forgotPassword, locationBeforeAuth]); + }, [navigate, user, forgotPassword, locationBeforeAuth]); return ( diff --git a/src/app/src/store/authSlice.js b/src/app/src/store/authSlice.js index 96f102b2..d1a05f76 100644 --- a/src/app/src/store/authSlice.js +++ b/src/app/src/store/authSlice.js @@ -1,42 +1,37 @@ import { createSlice } from '@reduxjs/toolkit'; const initialState = { - signedIn: false, locationBeforeAuth: '/welcome', - utilities: [], - selectedUtility: null, + user: false, + utility: null, }; export const authSlice = createSlice({ name: 'auth', initialState, reducers: { - login: state => { - state.signedIn = true; + login: (state, { payload: user }) => { + state.user = user; + + if (user.utilities) { + state.utility = user.utilities[0]; + } }, logout: state => { - state.signedIn = false; + state.user = false; }, setLocationBeforeAuth: (state, { payload: location }) => { if (!location.pathname.startsWith('/login')) { state.locationBeforeAuth = location; } }, - setUtilities: (state, { payload: utilities }) => { - state.utilities = utilities; - }, - setSelectedUtility: (state, { payload: selectedUtility }) => { - state.selectedUtility = selectedUtility; + setUtilityByPwsid: (state, { payload: pwsid }) => { + state.utility = state.user.utilities.find(u => u.pwsid === pwsid); }, }, }); -export const { - login, - logout, - setLocationBeforeAuth, - setUtilities, - setSelectedUtility, -} = authSlice.actions; +export const { login, logout, setLocationBeforeAuth, setUtilityByPwsid } = + authSlice.actions; export default authSlice.reducer; diff --git a/src/django/api/models/__init__.py b/src/django/api/models/__init__.py index e67667f3..9c6899f8 100644 --- a/src/django/api/models/__init__.py +++ b/src/django/api/models/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa: F401 -from .user import User, EmailAsUsernameUserManager +from .user import User, EmailAsUsernameUserManager, Roles from .utility import Utility from .state import State from .boundary import Boundary diff --git a/src/django/api/serializers/__init__.py b/src/django/api/serializers/__init__.py index e69de29b..a632fa7f 100644 --- a/src/django/api/serializers/__init__.py +++ b/src/django/api/serializers/__init__.py @@ -0,0 +1,5 @@ +# flake8: noqa: F401 +from .user import UserSerializer +from .utility import UtilitySerializer +from .state import StateIDSerializer +from .boundary import BoundaryListSerializer, BoundaryDetailSerializer diff --git a/src/django/api/serializers/state.py b/src/django/api/serializers/state.py new file mode 100644 index 00000000..c7af3e48 --- /dev/null +++ b/src/django/api/serializers/state.py @@ -0,0 +1,11 @@ +from rest_framework.serializers import ModelSerializer + +from ..models import State + + +class StateIDSerializer(ModelSerializer): + """Serializes State as its two letter abbreviation""" + + class Meta: + model = State + fields = ["id"] diff --git a/src/django/api/serializers/user.py b/src/django/api/serializers/user.py new file mode 100644 index 00000000..e9cf99f2 --- /dev/null +++ b/src/django/api/serializers/user.py @@ -0,0 +1,12 @@ +from rest_framework.serializers import ModelSerializer + +from ..models import User +from .utility import UtilitySerializer + + +class UserSerializer(ModelSerializer): + utilities = UtilitySerializer(many=True) + + class Meta: + model = User + fields = ("email", "role", "utilities") diff --git a/src/django/api/serializers/utility.py b/src/django/api/serializers/utility.py new file mode 100644 index 00000000..d5059a51 --- /dev/null +++ b/src/django/api/serializers/utility.py @@ -0,0 +1,12 @@ +from rest_framework.serializers import ModelSerializer + +from ..models import Utility +from .state import StateIDSerializer + + +class UtilitySerializer(ModelSerializer): + state = StateIDSerializer + + class Meta: + model = Utility + fields = "__all__" diff --git a/src/django/api/views/auth.py b/src/django/api/views/auth.py index 3a3d0b12..32eea1d2 100644 --- a/src/django/api/views/auth.py +++ b/src/django/api/views/auth.py @@ -9,6 +9,8 @@ from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode +from ..serializers import UserSerializer + class Login(LoginView): permission_classes = (AllowAny,) @@ -34,13 +36,13 @@ def post(self, request, *args, **kwargs): login(request, user) - return Response(status=status.HTTP_200_OK) + return Response(UserSerializer(user).data) def get(self, request, *args, **kwargs): if not request.user.is_active: raise AuthenticationFailed("Unable to sign in") - return Response(status=status.HTTP_204_NO_CONTENT) + return Response(UserSerializer(request.user).data) class Logout(LogoutView): diff --git a/src/django/api/views/boundary.py b/src/django/api/views/boundary.py index f85d77bf..2b991a71 100644 --- a/src/django/api/views/boundary.py +++ b/src/django/api/views/boundary.py @@ -5,11 +5,9 @@ from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated -from ..models.user import Roles -from ..models.boundary import Boundary -from ..models.submission import Submission +from ..models import Boundary, Roles, Submission -from ..serializers.boundary import BoundaryListSerializer, BoundaryDetailSerializer +from ..serializers import BoundaryListSerializer, BoundaryDetailSerializer def get_boundary_queryset_for_user(user):