diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index f2ef2d9e04e6fd7b69f8be59e9ef11cb9032f40a..60cdf044913aa5c24b336edad86eda1621122065 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.17 on 2019-11-15 14:04 +# Generated by Django 1.11.17 on 2019-11-15 20:12 from __future__ import unicode_literals import core.lib.validators @@ -37,9 +37,9 @@ class Migration(migrations.Migration): ('status', models.IntegerField(default=0)), ('logo', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_company_logo_file_path, validators=[core.lib.validators.validate_image_file_extension])), ('address', models.CharField(blank=True, max_length=1000, null=True)), - ('category', models.CharField(default=b'Belum ada kategori perusahaan', max_length=140)), + ('category', models.CharField(default='Belum ada kategori perusahaan', max_length=140)), ('size', models.CharField(blank=True, default=0, max_length=10, null=True)), - ('website', models.CharField(default=b'Belum ada link website', max_length=100)), + ('website', models.CharField(default='Belum ada link website', max_length=100)), ('linkedin_url', models.URLField(blank=True, null=True)), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], @@ -52,7 +52,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', models.DateTimeField(auto_now_add=True)), - ('title', models.CharField(blank=True, default=b'', max_length=100)), + ('title', models.CharField(blank=True, default='', max_length=100)), ('content', models.TextField()), ('companyId', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Company')), ], @@ -67,16 +67,16 @@ class Migration(migrations.Migration): ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), ('npm', models.IntegerField(unique=True, validators=[core.lib.validators.validate_npm])), - ('resume', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_resume_file_path, validators=[django.core.validators.FileExtensionValidator([b'pdf'])])), + ('resume', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_resume_file_path, validators=[django.core.validators.FileExtensionValidator(['pdf'])])), ('sertifikat', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_sertifikat_file_path, validators=[core.lib.validators.validate_document_file_extension])), - ('phone_number', models.CharField(blank=True, db_index=True, max_length=100, null=True, validators=[django.core.validators.RegexValidator(b'^0\\d{1,11}$')])), + ('phone_number', models.CharField(blank=True, db_index=True, max_length=100, null=True, validators=[django.core.validators.RegexValidator('^0\\d{1,11}$')])), ('gender', models.CharField(blank=True, max_length=30, null=True)), ('birth_place', models.CharField(blank=True, max_length=30, null=True)), ('birth_date', models.DateField(blank=True, null=True)), ('major', models.CharField(blank=True, max_length=30, null=True)), ('batch', models.CharField(blank=True, max_length=4, null=True)), ('show_transcript', models.BooleanField(default=False)), - ('photo', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_photo_file_path, validators=[django.core.validators.FileExtensionValidator([b'jpg', b'jpeg', b'png'])])), + ('photo', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_photo_file_path, validators=[django.core.validators.FileExtensionValidator(['jpg', 'jpeg', 'png'])])), ('self_description', models.CharField(blank=True, db_index=True, max_length=500, null=True)), ('portfolio_link', models.URLField(blank=True, null=True)), ('linkedin_url', models.URLField(blank=True, null=True)), @@ -91,10 +91,11 @@ class Migration(migrations.Migration): ('latest_work', models.CharField(blank=True, max_length=100, null=True)), ('latest_work_desc', models.TextField(blank=True, null=True)), ('github_url', models.URLField(blank=True, null=True)), + ('gitlab_url', models.URLField(blank=True, null=True)), ('intro', models.CharField(blank=True, max_length=50, null=True)), - ('expected_salary', models.CharField(blank=True, max_length=10, null=True, validators=[django.core.validators.RegexValidator(b'^\\d{0,10}$')])), + ('expected_salary', models.CharField(blank=True, max_length=10, null=True, validators=[django.core.validators.RegexValidator('^\\d{0,10}$')])), ('job_seeking_status', models.CharField(blank=True, max_length=30, null=True)), - ('student_gpa', models.FloatField(blank=True, db_column=b'student_gpa', default=1.0, null=True, validators=[core.lib.validators.validate_student_gpa])), + ('student_gpa', models.FloatField(blank=True, db_column='student_gpa', default=1.0, null=True, validators=[core.lib.validators.validate_student_gpa])), ('volunteer', models.CharField(blank=True, max_length=100, null=True)), ('awards', models.CharField(blank=True, max_length=100, null=True)), ('projects', models.CharField(blank=True, max_length=100, null=True)), @@ -102,7 +103,7 @@ class Migration(migrations.Migration): ('languages', models.CharField(blank=True, max_length=100, null=True)), ('seminar', models.CharField(blank=True, max_length=100, null=True)), ('interests', models.CharField(blank=True, max_length=100, null=True)), - ('dependants', models.IntegerField(blank=True, db_column=b'dependants', default=0, null=True)), + ('dependants', models.IntegerField(blank=True, db_column='dependants', default=0, null=True)), ], options={ 'ordering': ['-updated'], @@ -162,7 +163,7 @@ class Migration(migrations.Migration): name='ReasonRejected', fields=[ ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='core.Application')), - ('reason', models.TextField(default=b'Tidak memenuhi kualifikasi perusahaan.')), + ('reason', models.TextField(default='Tidak memenuhi kualifikasi perusahaan.')), ], ), migrations.AddField( diff --git a/core/migrations/0002_student_gitlab_url.py b/core/migrations/0002_student_gitlab_url.py deleted file mode 100644 index 60485a4fff90267e7a4ea1cffc7321c36f78fbb0..0000000000000000000000000000000000000000 --- a/core/migrations/0002_student_gitlab_url.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.17 on 2019-11-15 17:55 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='student', - name='gitlab_url', - field=models.URLField(blank=True, null=True), - ), - ] diff --git a/core/tests/test_accounts.py b/core/tests/test_accounts.py index d7dc82dcbc605ff2394a4c4e02e327b4d2f99b7e..5d9000aa51fe928af8d7868a3381a84123ec5ff7 100755 --- a/core/tests/test_accounts.py +++ b/core/tests/test_accounts.py @@ -4,7 +4,10 @@ from rest_framework import status from rest_framework.test import APIClient, APITestCase from django.contrib.auth.models import User from django.core.exceptions import ValidationError -from StringIO import StringIO +try: + from StringIO import StringIO ## for Python 2 +except ImportError: + from io import StringIO ## for Python 3 from core.models.accounts import Company, Supervisor, Student, get_current_age from core.tests.mocks import (mock_csui_oauth_verify, mock_csui_ldap_student, mock_csui_ldap_lecturer, mock_csui_ldap_fail, mock_csui_siak_student) @@ -117,43 +120,43 @@ class ProfileUpdateTests(APITestCase): student_id = response.data.get('student').get('id') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'self_description': 'I am very happy'}, format='multipart') + response = self.client.patch(url, {'self_description': 'I am very happy'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('self_description'), 'I am very happy') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'linkedin_url': 'https://www.linkedin.com/in/jojo/'}, format='multipart') + response = self.client.patch(url, {'linkedin_url': 'https://www.linkedin.com/in/jojo/'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('linkedin_url'), 'https://www.linkedin.com/in/jojo/') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'linkedin_url': 'https://linkedin.com/in/jojo/'}, format='multipart') + response = self.client.patch(url, {'linkedin_url': 'https://linkedin.com/in/jojo/'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('linkedin_url'), 'https://linkedin.com/in/jojo/') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'linkedin_url': 'https://id.linkedin.com/in/jojo/'}, format='multipart') + response = self.client.patch(url, {'linkedin_url': 'https://id.linkedin.com/in/jojo/'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('linkedin_url'), 'https://id.linkedin.com/in/jojo/') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'linkedin_url': 'https://www.linkedin.com/jojo/'}, format='multipart') + response = self.client.patch(url, {'linkedin_url': 'https://www.linkedin.com/jojo/'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.data.get('linkedin_url'), None) - response = self.client.patch(url, {'hackerrank_url': 'https://www.hackerrank.com/james'}, format='multipart') + response = self.client.patch(url, {'hackerrank_url': 'https://www.hackerrank.com/james'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('hackerrank_url'), 'https://www.hackerrank.com/james') - response = self.client.patch(url, {'hackerrank_url': 'https://www.hackerank.com/james'}, format='multipart') + response = self.client.patch(url, {'hackerrank_url': 'https://www.hackerank.com/james'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.data.get('hackerrank_url'), None) - response = self.client.patch(url, {'github_url': 'https://github.com/bob'}, format='multipart') + response = self.client.patch(url, {'github_url': 'https://github.com/bob'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('github_url'), 'https://github.com/bob') - response = self.client.patch(url, {'github_url': 'https://gitlab.com/bob'}, format='multipart') + response = self.client.patch(url, {'github_url': 'https://gitlab.com/bob'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.data.get('github_url'), None) @@ -247,12 +250,12 @@ class ProfileUpdateTests(APITestCase): new_student = Student.objects.create(user=new_user, npm="1212121212") url = '/api/students/' + str(new_student.pk) + "/profile/" - response = self.client.patch(url, {'phone_number': '08123123123'}, format='multipart') + response = self.client.patch(url, {'phone_number': '08123123123'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) url = '/api/students/' + str(student_id) + "/profile/" data = self._create_test_file('./assets/pdf/pdf-coba.pdf') - response = self.client.patch(url, data, format='multipart') + response = self.client.patch(url, data, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_200_OK) url = '/api/students/' + str(student_id) + "/profile/" @@ -261,69 +264,69 @@ class ProfileUpdateTests(APITestCase): self.assertEqual(response.status_code, status.HTTP_415_UNSUPPORTED_MEDIA_TYPE) url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'latest_work': 'Teaching assistant at Fasilkom UI'}, format='multipart') + response = self.client.patch(url, {'latest_work': 'Teaching assistant at Fasilkom UI'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('latest_work'), 'Teaching assistant at Fasilkom UI') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'latest_work_desc': 'Evaluate weekly assignment for 15 students'}, format='multipart') + response = self.client.patch(url, {'latest_work_desc': 'Evaluate weekly assignment for 15 students'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('latest_work_desc'), 'Evaluate weekly assignment for 15 students') url = '/api/students/' + str(student_id) + '/profile/' - response = self.client.patch(url, {'intro': 'Saya tertarik dengan dunia front-end development'}, format='multipart') + response = self.client.patch(url, {'intro': 'Saya tertarik dengan dunia front-end development'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('intro'), 'Saya tertarik dengan dunia front-end development') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'awards': 'Juara 2 UIUX Gemastik 2019'}, format='multipart') + response = self.client.patch(url, {'awards': 'Juara 2 UIUX Gemastik 2019'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('awards'), 'Juara 2 UIUX Gemastik 2019') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'projects': 'Ow-Jek - Android Mobile Project'}, format='multipart') + response = self.client.patch(url, {'projects': 'Ow-Jek - Android Mobile Project'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('projects'), 'Ow-Jek - Android Mobile Project') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'certification': 'TOEFL'}, format='multipart') + response = self.client.patch(url, {'certification': 'TOEFL'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('certification'), 'TOEFL') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'languages': 'Indonesia, Inggris'}, format='multipart') + response = self.client.patch(url, {'languages': 'Indonesia, Inggris'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('languages'), 'Indonesia, Inggris') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'seminar': 'Seminar CompFest'}, format='multipart') + response = self.client.patch(url, {'seminar': 'Seminar CompFest'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('seminar'), 'Seminar CompFest') failing_desc = "a" * 501 url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'self_description': failing_desc}, format='multipart') + response = self.client.patch(url, {'self_description': failing_desc}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'student_gpa': '4'}, format='multipart') + response = self.client.patch(url, {'student_gpa': '4'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'student_gpa': '8'}, format='multipart') + response = self.client.patch(url, {'student_gpa': '8'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - response = self.client.patch(url, {'interests': 'Machine Learning'}, format='multipart') + response = self.client.patch(url, {'interests': 'Machine Learning'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('interests'), 'Machine Learning') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'skills': ''}, format='multipart') + response = self.client.patch(url, {'skills': ''}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(response.data.get('skills'), '') url = '/api/students/' + str(student_id) + "/profile/" - response = self.client.patch(url, {'skills': '1231231231'}, format='multipart') + response = self.client.patch(url, {'skills': '1231231231'}, format='multipart', encoding='utf-8') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @requests_mock.Mocker() diff --git a/core/tests/test_profile_accounts.py b/core/tests/test_profile_accounts.py index a4ac0a69a39b57086bc453f56fa77692f606e5e2..c79daf28b1043d704f293f5c3729fc34b0368922 100644 --- a/core/tests/test_profile_accounts.py +++ b/core/tests/test_profile_accounts.py @@ -4,7 +4,7 @@ from django.contrib.auth.models import User from rest_framework.test import APITestCase from core.models.accounts import get_current_age, get_display_name -from core.views.accounts import determine_first_name_last_name +from core.views.accounts.utils import determine_first_name_last_name class ProfileAccountsTests(APITestCase): diff --git a/core/views/accounts.py b/core/views/accounts.py deleted file mode 100755 index a0d056fea90f551e8e3f3ab148e2d7cf59122919..0000000000000000000000000000000000000000 --- a/core/views/accounts.py +++ /dev/null @@ -1,330 +0,0 @@ -import requests -import re -from django.contrib.auth import authenticate, login -from django.contrib.auth.models import User -from django.shortcuts import get_object_or_404 -from rest_framework import viewsets, status -from rest_framework.decorators import list_route, detail_route -from rest_framework.exceptions import PermissionDenied -from rest_framework.parsers import FormParser, MultiPartParser -from rest_framework.permissions import AllowAny -from rest_framework.permissions import IsAdminUser, IsAuthenticated -from rest_framework.response import Response -from .sso_login import get_access_token, verify_user, get_riwayat_user, get_summary_user - -from core.lib.permissions import IsAdminOrStudent, IsAdminOrSelfOrReadOnly, IsAdminOrCompany, IsAdminOrSupervisor, \ - IsAdminOrSupervisorOrCompanyOrSelf -from core.models.accounts import Student, Company, Supervisor -from core.serializers.accounts import BasicUserSerializer, UserSerializer, StudentSerializer, CompanySerializer, \ - SupervisorSerializer, RegisterSerializer, StudentUpdateSerializer, CompanyUpdateSerializer -from kape import settings - - -class UserViewSet(viewsets.ModelViewSet): - queryset = User.objects.all() - serializer_class = UserSerializer - permission_classes = [IsAdminUser] - - @list_route(methods=['get'], permission_classes=[IsAuthenticated]) - def me(self, request): - """ - Get current user's details - """ - user = self.request.user - serializer = BasicUserSerializer(user, context={"request": request}) - return Response(serializer.data) - - def get_permissions(self): - if self.action == "update" or self.action == "partial_update": - return [IsAdminOrSelfOrReadOnly(), IsAuthenticated()] - if self.action == "create": - return [AllowAny()] - return super(UserViewSet, self).get_permissions() - - -class StudentViewSet(viewsets.ModelViewSet): - queryset = Student.objects.all() - serializer_class = StudentSerializer - permission_classes = [IsAdminUser] - - def get_permissions(self): - if self.action == "update" or self.action == "partial_update": - return [IsAdminOrSelfOrReadOnly(), IsAdminOrStudent()] - if self.action == "list": - return [IsAuthenticated(), IsAdminOrSupervisor()] - if self.action == "retrieve": - return [IsAuthenticated(), IsAdminOrSupervisorOrCompanyOrSelf()] - return super(StudentViewSet, self).get_permissions() - - @detail_route(methods=['patch'], permission_classes=[IsAdminOrStudent], - serializer_class=StudentUpdateSerializer, parser_classes=(MultiPartParser, FormParser,)) - def profile(self, request, pk=None): - """ - Update student {student_id}'s profile information - --- - """ - user = self.get_object() - github_url_pattern = "^(https:\/\/www\.|https:\/\/)(github.com\/)([a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38})(\/)?$" - linkedin_url_pattern = "^(https:\/\/www\.|https:\/\/|https:\/\/id\.)(linkedin.com)(\/in)(\/[a-zA-Z0-9]*)(\/)?$" - hackerrank_url_pattern = "^(https:\/\/www\.)(hackerrank.com)(\/[a-zA-Z0-9]*)(\/)?$" - - if 'pdf_sertifikat' in request.data.keys(): - if request.data['pdf_sertifikat'].content_type == 'application/pdf': - user.sertifikat = request.data['pdf_sertifikat'] - user.save() - return Response({}, status=status.HTTP_200_OK) - else: - return Response({}, status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE) - - if 'pdf_file' in request.data.keys(): - if request.data['pdf_file'].content_type == 'application/pdf': - user.ui_ux_portofolio = request.data['pdf_file'] - user.save() - return Response({}, status=status.HTTP_200_OK) - else: - return Response({}, status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE) - - if 'github_url' in request.data.keys() and not re.search(github_url_pattern, request.data['github_url']): - return Response({'Error': "Pastikan link github yang anda tulis benar. (Berpola : https://github.com/<username>"}, status=status.HTTP_400_BAD_REQUEST) - - if 'linkedin_url' in request.data.keys() and not re.search(linkedin_url_pattern, request.data['linkedin_url']): - return Response({'Error': "Pastikan link linkedin yang anda tulis benar. (Berpola : https://linkedin.com/in/<username>"}, status=status.HTTP_400_BAD_REQUEST) - - if 'hackerrank_url' in request.data.keys() and not re.search(hackerrank_url_pattern, request.data['hackerrank_url']): - return Response({'Error': "Pastikan link hackerrank yang anda tulis benar. (Berpola : https://hackerrank.com/<username>"}, status=status.HTTP_400_BAD_REQUEST) - - serializer = self.serializer_class(user, data=request.data, partial=True) - if serializer.is_valid(): - if serializer.validated_data.get('skills') is not None and serializer.validated_data.get('skills').isdigit(): - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - serializer.save() - return Response(serializer.data, status=status.HTTP_202_ACCEPTED) - else: - serializer = self.serializer_class( - user, data=request.data, partial=True) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_202_ACCEPTED) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - @detail_route(methods=['get'], permission_classes=[IsAdminOrStudent]) - def transcript(self, request, pk): - """ - Get student {student_id}'s academic transcript - --- - """ - student = get_object_or_404(Student.objects.all(), pk=pk) - - if hasattr(request.user, 'student') and request.user.student.pk != student.pk: - raise PermissionDenied( - "You are not allowed to see other student's transcript") - - if student.show_transcript: - access_token = get_access_token( - credentials["user"], credentials["password"]) - ver_user = verify_user(access_token) - response = get_riwayat_user( - access_token, ver_user['identity_number']) - return Response({'name': student.full_name, 'transcript': response.json()}, status=status.HTTP_200_OK) - else: - return Response({'name': student.full_name, 'error': 'student does not allow transcript to be shown'}, - status=status.HTTP_200_OK) - - -class CompanyViewSet(viewsets.ModelViewSet): - queryset = Company.objects.all() - serializer_class = CompanySerializer - permission_classes = [IsAdminOrSelfOrReadOnly, IsAdminOrCompany] - filter_fields = ('status',) - - @detail_route(methods=['patch'], permission_classes=[IsAdminOrCompany], - serializer_class=CompanyUpdateSerializer, parser_classes=(MultiPartParser, FormParser,)) - def profile(self, request, pk=None): - """ - Update company's profile information - --- - """ - user = self.get_object() - serializer = self.serializer_class( - user, data=request.data, partial=True) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_202_ACCEPTED) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - -class SupervisorViewSet(viewsets.ModelViewSet): - queryset = Supervisor.objects.all() - serializer_class = SupervisorSerializer - permission_classes = [IsAdminOrSelfOrReadOnly, IsAdminOrSupervisor] - - -def determine_first_name_last_name(name): - name = name.split(" ") - if len(name) == 1: - return name[0], "" - - elif len(name) > 3: - return " ".join(name[0:2]), name[-1] - - else: - return " ".join(name[0:-1]), name[-1] - - -class LoginViewSet(viewsets.GenericViewSet): - permission_classes = (AllowAny, ) - serializer_class = UserSerializer - queryset = User.objects.all() - - def create(self, request): - """ - Authentication for user by means of logging in - --- - parameters: - - name: body - description: JSON object containing three strings: username, password and login-type. login-type should be either 'sso-ui' or 'company'. - required: true - type: string - paramType: body - """ - username = request.data.get('username') - password = request.data.get('password') - login_type = request.data.get('login-type') - if username is None or password is None or login_type is None: - return Response(status=status.HTTP_400_BAD_REQUEST) - if login_type == "sso-ui": - return self.login_via_sso(request, username, password) - elif login_type == "company": - user = authenticate(username=username, password=password) - if user is not None: - login(request, user) - serializer = UserSerializer(user, context={'request': request}) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - return Response(status=status.HTTP_401_UNAUTHORIZED) - else: - return Response(status=status.HTTP_400_BAD_REQUEST) - - def login_via_sso(self, request, username, password): - r = requests.post('https://api.cs.ui.ac.id/authentication/ldap/v2/', - data={"username": username, "password": password}) - resp = r.json() - if resp.get('state') != 0: - # create user - name = resp.get('nama') - first_name, last_name = determine_first_name_last_name(name) - user, created = User.objects.get_or_create( - username=username, - defaults={ - 'email': username + "@ui.ac.id", - 'first_name': first_name, - 'last_name': last_name - } - ) - user.set_password(password) - user.save() - login(request, user) - if created: - if resp.get('nama_role') == "mahasiswa": - access_token = get_access_token(username, password) - ver_user = verify_user(access_token) - resp_student_detail = get_summary_user( - access_token, ver_user['identity_number']) - student = Student.objects.create( - user=user, - npm=resp.get("kodeidentitas"), - birth_place=resp_student_detail.get('kota_lahir'), - birth_date=resp_student_detail.get('tgl_lahir'), - major=resp_student_detail.get( - 'program')[0].get('nm_org'), - batch=resp_student_detail.get( - 'program')[0].get('angkatan') - ) - student.save() - else: - supervisor = Supervisor.objects.create( - user=user, - nip=resp.get("kodeidentitas") - ) - supervisor.save() - serializer = UserSerializer(user, context={'request': request}) - return Response(serializer.data, status=status.HTTP_201_CREATED) - serializer = UserSerializer(user, context={'request': request}) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - return Response(status=status.HTTP_401_UNAUTHORIZED) - - -class CompanyRegisterViewSet(viewsets.GenericViewSet): - permission_classes = (AllowAny,) - serializer_class = RegisterSerializer - queryset = Company.objects.all() - parser_classes = (MultiPartParser, FormParser,) - - def create(self, request): - """ - Create a new company user - --- - parameters: - - name: username - description: username of the new account - required: true - type: string - - name: password - description: password of the new account - required: true - type: string - - name: email - description: email address of the new account - required: true - type: string - - name: name - description: the new company's name - required: true - type: string - - name: description - description: description of the new company - required: true - type: string - - name: logo - description: logo of the new company - required: false - type: image - - name: address - description: address of the new account - required: false - type: string - """ - data = {} - for attr in ['password', 'email', 'name', 'description', 'logo', 'address', 'category', 'size','website']: - data[attr] = request.data.get(attr) - if data[attr] is None: - return Response({'error': attr+' is required'}, status=status.HTTP_400_BAD_REQUEST) - - user, created = User.objects.get_or_create( - username=data['email'], - email=data['email'], - first_name=data['name'], - last_name="" - ) - if created: - user.set_password(data['password']) - company = Company.objects.create( - user=user, - description=data['description'], - logo=data['logo'], - address=data['address'], - category=data['category'], - size=data['size'], - website=data['website'] - ) - user.save() - company.save() - serializer = self.serializer_class( - user, context={'request': request}) - login(request, user) - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - return Response({'error': 'Company with email '+data['email']+' already exist'}, status=status.HTTP_409_CONFLICT) diff --git a/core/views/accounts/README.md b/core/views/accounts/README.md new file mode 100644 index 0000000000000000000000000000000000000000..94d71e0cdca070845d176c89b64c1b598830a872 --- /dev/null +++ b/core/views/accounts/README.md @@ -0,0 +1,3 @@ +# Notes: +Whenever you add new python script in this folder, don't forget to register the module in __init__.py. + diff --git a/core/views/accounts/__init__.py b/core/views/accounts/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e4cf11e666affc86c1a0a31632238400bb3ae76d --- /dev/null +++ b/core/views/accounts/__init__.py @@ -0,0 +1,6 @@ +from .company import CompanyViewSet +from .login import LoginViewSet +from .registration import CompanyRegisterViewSet +from .student import StudentViewSet +from .supervisor import SupervisorViewSet +from .user import UserViewSet \ No newline at end of file diff --git a/core/views/accounts/company.py b/core/views/accounts/company.py new file mode 100644 index 0000000000000000000000000000000000000000..c4abca1aa0b5ddab75c1e02a43c5f17e4cc3fa48 --- /dev/null +++ b/core/views/accounts/company.py @@ -0,0 +1,29 @@ +from rest_framework import viewsets, status +from rest_framework.decorators import detail_route +from rest_framework.response import Response +from rest_framework.parsers import FormParser, MultiPartParser +from core.lib.permissions import IsAdminOrSelfOrReadOnly, IsAdminOrCompany +from core.models.accounts import Company +from core.serializers.accounts import CompanySerializer, CompanyUpdateSerializer + +class CompanyViewSet(viewsets.ModelViewSet): + queryset = Company.objects.all() + serializer_class = CompanySerializer + permission_classes = [IsAdminOrSelfOrReadOnly, IsAdminOrCompany] + filter_fields = ('status',) + + @detail_route(methods=['patch'], permission_classes=[IsAdminOrCompany], + serializer_class=CompanyUpdateSerializer, parser_classes=(MultiPartParser, FormParser,)) + def profile(self, request, pk=None): + """ + Update company's profile information + --- + """ + user = self.get_object() + serializer = self.serializer_class( + user, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/core/views/accounts/login.py b/core/views/accounts/login.py new file mode 100644 index 0000000000000000000000000000000000000000..deabd3193e2b7548ef22d2486449ef7230f6b4ac --- /dev/null +++ b/core/views/accounts/login.py @@ -0,0 +1,94 @@ +import requests + +from django.contrib.auth import authenticate, login +from django.contrib.auth.models import User +from rest_framework import viewsets, status +from rest_framework.permissions import AllowAny +from rest_framework.response import Response +from core.views.sso_login import get_access_token, verify_user, get_riwayat_user, get_summary_user +from .utils import determine_first_name_last_name +from core.models.accounts import Student, Supervisor +from core.serializers.accounts import UserSerializer + +class LoginViewSet(viewsets.GenericViewSet): + permission_classes = (AllowAny, ) + serializer_class = UserSerializer + queryset = User.objects.all() + + def create(self, request): + """ + Authentication for user by means of logging in + --- + parameters: + - name: body + description: JSON object containing three strings: username, password and login-type. login-type should be either 'sso-ui' or 'company'. + required: true + type: string + paramType: body + """ + username = request.data.get('username') + password = request.data.get('password') + login_type = request.data.get('login-type') + if username is None or password is None or login_type is None: + return Response(status=status.HTTP_400_BAD_REQUEST) + if login_type == "sso-ui": + return self.login_via_sso(request, username, password) + elif login_type == "company": + user = authenticate(username=username, password=password) + if user is not None: + login(request, user) + serializer = UserSerializer(user, context={'request': request}) + return Response(serializer.data, status=status.HTTP_200_OK) + else: + return Response(status=status.HTTP_401_UNAUTHORIZED) + else: + return Response(status=status.HTTP_400_BAD_REQUEST) + + def login_via_sso(self, request, username, password): + r = requests.post('https://api.cs.ui.ac.id/authentication/ldap/v2/', + data={"username": username, "password": password}) + resp = r.json() + if resp.get('state') != 0: + # create user + name = resp.get('nama') + first_name, last_name = determine_first_name_last_name(name) + user, created = User.objects.get_or_create( + username=username, + defaults={ + 'email': username + "@ui.ac.id", + 'first_name': first_name, + 'last_name': last_name + } + ) + user.set_password(password) + user.save() + login(request, user) + if created: + if resp.get('nama_role') == "mahasiswa": + access_token = get_access_token(username, password) + ver_user = verify_user(access_token) + resp_student_detail = get_summary_user( + access_token, ver_user['identity_number']) + student = Student.objects.create( + user=user, + npm=resp.get("kodeidentitas"), + birth_place=resp_student_detail.get('kota_lahir'), + birth_date=resp_student_detail.get('tgl_lahir'), + major=resp_student_detail.get( + 'program')[0].get('nm_org'), + batch=resp_student_detail.get( + 'program')[0].get('angkatan') + ) + student.save() + else: + supervisor = Supervisor.objects.create( + user=user, + nip=resp.get("kodeidentitas") + ) + supervisor.save() + serializer = UserSerializer(user, context={'request': request}) + return Response(serializer.data, status=status.HTTP_201_CREATED) + serializer = UserSerializer(user, context={'request': request}) + return Response(serializer.data, status=status.HTTP_200_OK) + else: + return Response(status=status.HTTP_401_UNAUTHORIZED) diff --git a/core/views/accounts/registration.py b/core/views/accounts/registration.py new file mode 100644 index 0000000000000000000000000000000000000000..5562e0332fcc22f19a7c2d3d77af1aa250ef6211 --- /dev/null +++ b/core/views/accounts/registration.py @@ -0,0 +1,80 @@ +from django.contrib.auth.models import User +from django.contrib.auth import login +from rest_framework import viewsets, status +from rest_framework.parsers import FormParser, MultiPartParser +from rest_framework.permissions import AllowAny +from rest_framework.response import Response +from core.models.accounts import Company +from core.serializers.accounts import RegisterSerializer + +class CompanyRegisterViewSet(viewsets.GenericViewSet): + permission_classes = (AllowAny,) + serializer_class = RegisterSerializer + queryset = Company.objects.all() + parser_classes = (MultiPartParser, FormParser,) + + def create(self, request): + """ + Create a new company user + --- + parameters: + - name: username + description: username of the new account + required: true + type: string + - name: password + description: password of the new account + required: true + type: string + - name: email + description: email address of the new account + required: true + type: string + - name: name + description: the new company's name + required: true + type: string + - name: description + description: description of the new company + required: true + type: string + - name: logo + description: logo of the new company + required: false + type: image + - name: address + description: address of the new account + required: false + type: string + """ + data = {} + for attr in ['password', 'email', 'name', 'description', 'logo', 'address', 'category', 'size','website']: + data[attr] = request.data.get(attr) + if data[attr] is None: + return Response({'error': attr+' is required'}, status=status.HTTP_400_BAD_REQUEST) + + user, created = User.objects.get_or_create( + username=data['email'], + email=data['email'], + first_name=data['name'], + last_name="" + ) + if created: + user.set_password(data['password']) + company = Company.objects.create( + user=user, + description=data['description'], + logo=data['logo'], + address=data['address'], + category=data['category'], + size=data['size'], + website=data['website'] + ) + user.save() + company.save() + serializer = self.serializer_class( + user, context={'request': request}) + login(request, user) + return Response(serializer.data, status=status.HTTP_201_CREATED) + else: + return Response({'error': 'Company with email '+data['email']+' already exist'}, status=status.HTTP_409_CONFLICT) \ No newline at end of file diff --git a/core/views/accounts/student.py b/core/views/accounts/student.py new file mode 100644 index 0000000000000000000000000000000000000000..1b45e68fae6b8b3e691d303a94751dc87ac611af --- /dev/null +++ b/core/views/accounts/student.py @@ -0,0 +1,101 @@ +import re + +from rest_framework import viewsets, status +from rest_framework.decorators import detail_route +from rest_framework.parsers import FormParser, MultiPartParser +from rest_framework.permissions import IsAdminUser, IsAuthenticated +from rest_framework.response import Response +from core.lib.permissions import IsAdminOrStudent +from core.models.accounts import Student +from core.lib.permissions import IsAdminOrStudent, IsAdminOrSelfOrReadOnly, IsAdminOrSupervisor, \ + IsAdminOrSupervisorOrCompanyOrSelf +from core.serializers.accounts import StudentSerializer, StudentUpdateSerializer + +class StudentViewSet(viewsets.ModelViewSet): + queryset = Student.objects.all() + serializer_class = StudentSerializer + permission_classes = [IsAdminUser] + + def get_permissions(self): + if self.action == "update" or self.action == "partial_update": + return [IsAdminOrSelfOrReadOnly(), IsAdminOrStudent()] + if self.action == "list": + return [IsAuthenticated(), IsAdminOrSupervisor()] + if self.action == "retrieve": + return [IsAuthenticated(), IsAdminOrSupervisorOrCompanyOrSelf()] + return super(StudentViewSet, self).get_permissions() + + @detail_route(methods=['patch'], permission_classes=[IsAdminOrStudent], + serializer_class=StudentUpdateSerializer, parser_classes=(MultiPartParser, FormParser,)) + def profile(self, request, pk=None): + """ + Update student {student_id}'s profile information + --- + """ + user = self.get_object() + github_url_pattern = "^(https:\/\/www\.|https:\/\/)(github.com\/)([a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38})(\/)?$" + linkedin_url_pattern = "^(https:\/\/www\.|https:\/\/|https:\/\/id\.)(linkedin.com)(\/in)(\/[a-zA-Z0-9]*)(\/)?$" + hackerrank_url_pattern = "^(https:\/\/www\.)(hackerrank.com)(\/[a-zA-Z0-9]*)(\/)?$" + + if 'pdf_sertifikat' in request.data.keys(): + if request.data['pdf_sertifikat'].content_type == 'application/pdf': + user.sertifikat = request.data['pdf_sertifikat'] + user.save() + return Response({}, status=status.HTTP_200_OK) + else: + return Response({}, status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE) + + if 'pdf_file' in request.data.keys(): + if request.data['pdf_file'].content_type == 'application/pdf': + user.ui_ux_portofolio = request.data['pdf_file'] + user.save() + return Response({}, status=status.HTTP_200_OK) + else: + return Response({}, status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE) + + if 'github_url' in request.data.keys() and not re.search(github_url_pattern, request.data['github_url']): + return Response({'Error': "Pastikan link github yang anda tulis benar. (Berpola : https://github.com/<username>"}, status=status.HTTP_400_BAD_REQUEST) + + if 'linkedin_url' in request.data.keys() and not re.search(linkedin_url_pattern, request.data['linkedin_url']): + return Response({'Error': "Pastikan link linkedin yang anda tulis benar. (Berpola : https://linkedin.com/in/<username>"}, status=status.HTTP_400_BAD_REQUEST) + + if 'hackerrank_url' in request.data.keys() and not re.search(hackerrank_url_pattern, request.data['hackerrank_url']): + return Response({'Error': "Pastikan link hackerrank yang anda tulis benar. (Berpola : https://hackerrank.com/<username>"}, status=status.HTTP_400_BAD_REQUEST) + + serializer = self.serializer_class(user, data=request.data, partial=True) + if serializer.is_valid(): + if serializer.validated_data.get('skills') is not None and serializer.validated_data.get('skills').isdigit(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + serializer.save() + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) + else: + serializer = self.serializer_class( + user, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @detail_route(methods=['get'], permission_classes=[IsAdminOrStudent]) + def transcript(self, request, pk): + """ + Get student {student_id}'s academic transcript + --- + """ + student = get_object_or_404(Student.objects.all(), pk=pk) + + if hasattr(request.user, 'student') and request.user.student.pk != student.pk: + raise PermissionDenied( + "You are not allowed to see other student's transcript") + + if student.show_transcript: + access_token = get_access_token( + credentials["user"], credentials["password"]) + ver_user = verify_user(access_token) + response = get_riwayat_user( + access_token, ver_user['identity_number']) + return Response({'name': student.full_name, 'transcript': response.json()}, status=status.HTTP_200_OK) + else: + return Response({'name': student.full_name, 'error': 'student does not allow transcript to be shown'}, + status=status.HTTP_200_OK) \ No newline at end of file diff --git a/core/views/accounts/supervisor.py b/core/views/accounts/supervisor.py new file mode 100644 index 0000000000000000000000000000000000000000..da9b65a0618b16e0750e44f6d910afffad943f74 --- /dev/null +++ b/core/views/accounts/supervisor.py @@ -0,0 +1,9 @@ +from rest_framework import viewsets +from core.lib.permissions import IsAdminOrSelfOrReadOnly, IsAdminOrSupervisor +from core.models.accounts import Supervisor +from core.serializers.accounts import SupervisorSerializer + +class SupervisorViewSet(viewsets.ModelViewSet): + queryset = Supervisor.objects.all() + serializer_class = SupervisorSerializer + permission_classes = [IsAdminOrSelfOrReadOnly, IsAdminOrSupervisor] diff --git a/core/views/accounts/user.py b/core/views/accounts/user.py new file mode 100644 index 0000000000000000000000000000000000000000..1262b8b60b943ea316d54e0da8662e3b64a107b5 --- /dev/null +++ b/core/views/accounts/user.py @@ -0,0 +1,27 @@ +from django.contrib.auth.models import User +from rest_framework import viewsets +from rest_framework.decorators import list_route +from rest_framework.response import Response +from rest_framework.permissions import IsAdminUser, IsAuthenticated +from core.serializers.accounts import UserSerializer, BasicUserSerializer + +class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer + permission_classes = [IsAdminUser] + + @list_route(methods=['get'], permission_classes=[IsAuthenticated]) + def me(self, request): + """ + Get current user's details + """ + user = self.request.user + serializer = BasicUserSerializer(user, context={"request": request}) + return Response(serializer.data) + + def get_permissions(self): + if self.action == "update" or self.action == "partial_update": + return [IsAdminOrSelfOrReadOnly(), IsAuthenticated()] + if self.action == "create": + return [AllowAny()] + return super(UserViewSet, self).get_permissions() diff --git a/core/views/accounts/utils.py b/core/views/accounts/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..430939c11cbe5bdfd6575cc1abec61ed5e2bf08b --- /dev/null +++ b/core/views/accounts/utils.py @@ -0,0 +1,10 @@ +def determine_first_name_last_name(name): + name = name.split(" ") + if len(name) == 1: + return name[0], "" + + elif len(name) > 3: + return " ".join(name[0:2]), name[-1] + + else: + return " ".join(name[0:-1]), name[-1] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 11d3570574a0ed694d32d833be6b11e5323a66c9..acbca8d581e48a09bc0d65aa0dc7a78ed03ed3bb 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1750,7 +1750,7 @@ "@babel/plugin-proposal-do-expressions": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-do-expressions/-/plugin-proposal-do-expressions-7.6.0.tgz", - "integrity": "sha512-qJDaoBDbLySwU1tG0jbAomOwz8W1PEiiiK0iLQAnHLr4PYIMVX4ltDGkj3uAKx4HDs1WJ0tozGW1zAQjuTIiWg==", + "integrity": "sha1-GSlT/thiDRPRKmH2je/Sb0EFkZM=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1770,7 +1770,7 @@ "@babel/plugin-proposal-export-default-from": { "version": "7.5.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.5.2.tgz", - "integrity": "sha512-wr9Itk05L1/wyyZKVEmXWCdcsp/e185WUNl6AfYZeEKYaUPPvHXRDqO5K1VH7/UamYqGJowFRuCv30aDYZawsg==", + "integrity": "sha1-LArC3MNuOyRD/q0sPF/HlvsbUUU=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1780,7 +1780,7 @@ "@babel/plugin-proposal-export-namespace-from": { "version": "7.5.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.5.2.tgz", - "integrity": "sha512-TKUdOL07anjZEbR1iSxb5WFh810KyObdd29XLFLGo1IDsSuGrjH3ouWSbAxHNmrVKzr9X71UYl2dQ7oGGcRp0g==", + "integrity": "sha1-zNXtBbBtcAaI/x2wGp3ScVXg0qA=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1801,7 +1801,7 @@ "@babel/plugin-proposal-json-strings": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", - "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "integrity": "sha1-Vo7MRGxhSK5rJn8CVREwiR4p8xc=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1811,7 +1811,7 @@ "@babel/plugin-proposal-logical-assignment-operators": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.2.0.tgz", - "integrity": "sha512-0w797xwdPXKk0m3Js74hDi0mCTZplIu93MOSfb1ZLd/XFe3abWypx1QknVk0J+ohnsjYpvjH4Gwfo2i3RicB6Q==", + "integrity": "sha1-ilzqbEKnyHRGlZ4C//X60BLFb1c=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1821,7 +1821,7 @@ "@babel/plugin-proposal-nullish-coalescing-operator": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.4.4.tgz", - "integrity": "sha512-Amph7Epui1Dh/xxUxS2+K22/MUi6+6JVTvy3P58tja3B6yKTSjwwx0/d83rF7551D6PVSSoplQb8GCwqec7HRw==", + "integrity": "sha1-QcNg1ZSB2I4M46P4N98QEhp2mzk=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1831,7 +1831,7 @@ "@babel/plugin-proposal-numeric-separator": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.2.0.tgz", - "integrity": "sha512-DohMOGDrZiMKS7LthjUZNNcWl8TAf5BZDwZAH4wpm55FuJTHgfqPGdibg7rZDmont/8Yg0zA03IgT6XLeP+4sg==", + "integrity": "sha1-ZGhU2vTNIv1nM/YHYBOpNjEEQ6w=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1861,7 +1861,7 @@ "@babel/plugin-proposal-optional-chaining": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.6.0.tgz", - "integrity": "sha512-kj4gkZ6qUggkprRq3Uh5KP8XnE1MdIO0J7MhdDX8+rAbB6dJ2UrensGIS+0NPZAaaJ1Vr0PN6oLUgXMU1uMcSg==", + "integrity": "sha1-6b8fm5uhDHfAMwgtp18Gg4kEGvg=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1871,7 +1871,7 @@ "@babel/plugin-proposal-pipeline-operator": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-pipeline-operator/-/plugin-proposal-pipeline-operator-7.5.0.tgz", - "integrity": "sha512-HFYuu/yGnkn69ligXxU0ohOVvQDsMNOUJs/c4PYLUVS6ntCYOyGmRQQaSYJARJ9rvc7/ulZKIzxd4wk91hN63A==", + "integrity": "sha1-QQDsVe9PakwkkLX1pPKiLfonLAY=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1881,7 +1881,7 @@ "@babel/plugin-proposal-throw-expressions": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.2.0.tgz", - "integrity": "sha512-adsydM8DQF4i5DLNO4ySAU5VtHTPewOtNBV3u7F4lNMPADFF9bWQ+iDtUUe8+033cYCUz+bFlQdXQJmJOwoLpw==", + "integrity": "sha1-LZ5FLTcPE5AA5R22XQqF3GDGRzk=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1928,7 +1928,7 @@ "@babel/plugin-syntax-dynamic-import": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", - "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "integrity": "sha1-acFZ/69JmBIhYa2OvF5tH1XfhhI=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" @@ -1964,7 +1964,7 @@ "@babel/plugin-syntax-import-meta": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.2.0.tgz", - "integrity": "sha512-Hq6kFSZD7+PHkmBN8bCpHR6J8QEoCuEV/B38AIQscYjgMZkGlXB7cHNFzP5jR4RCh5545yP1ujHdmO7hAgKtBA==", + "integrity": "sha1-IzPvS4dVU6O80ek/jrwJ9bkhOkA=", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" @@ -3011,7 +3011,7 @@ "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "integrity": "sha1-086gTWsBeyiUrWkED+yLYj60vVI=", "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -3261,7 +3261,7 @@ "axios": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", - "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "integrity": "sha1-jgm/89kSLhM/e4EByPvdAO09Krg=", "requires": { "follow-redirects": "1.5.10", "is-buffer": "^2.0.2" @@ -3313,7 +3313,7 @@ "babel-core": { "version": "6.26.3", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "integrity": "sha1-suLwnjQtDwyI4vAuBneUEl51wgc=", "dev": true, "requires": { "babel-code-frame": "^6.26.0", @@ -3340,7 +3340,7 @@ "babel-eslint": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", - "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", + "integrity": "sha1-gaLGab4PIF4ZRi/tJILTPkaHqIo=", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -3401,7 +3401,7 @@ "babel-loader": { "version": "8.0.6", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", - "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", + "integrity": "sha1-4zvbbzYrA/S7FBoMIauHxQG3Dfs=", "dev": true, "requires": { "find-cache-dir": "^2.0.0", @@ -3428,7 +3428,7 @@ "loader-utils": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "integrity": "sha1-H/XcaRHJ8KBiUxpMBLYJQGEIwsc=", "dev": true, "requires": { "big.js": "^5.2.2", @@ -4399,7 +4399,7 @@ "compression-webpack-plugin": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-3.0.0.tgz", - "integrity": "sha512-ls+oKw4eRbvaSv/hj9NmctihhBcR26j76JxV0bLRLcWhrUBdQFgd06z/Kgg7exyQvtWWP484wZxs0gIUX3NO0Q==", + "integrity": "sha1-CX0uTZXDoUy1yO0giZAJq1ubvKA=", "requires": { "cacache": "^11.2.0", "find-cache-dir": "^3.0.0", @@ -5500,7 +5500,7 @@ "eslint-config-airbnb": { "version": "18.0.1", "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.0.1.tgz", - "integrity": "sha512-hLb/ccvW4grVhvd6CT83bECacc+s4Z3/AEyWQdIT2KeTsG9dR7nx1gs7Iw4tDmGKozCNHFn4yZmRm3Tgy+XxyQ==", + "integrity": "sha1-o6dMwptGQTtglpZQJTgd+PuQhVk=", "dev": true, "requires": { "eslint-config-airbnb-base": "^14.0.0", @@ -5573,7 +5573,7 @@ "eslint-plugin-import": { "version": "2.18.2", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", - "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==", + "integrity": "sha1-AvEYC5Cwd7M9RHoXojJs60AKzrY=", "dev": true, "requires": { "array-includes": "^3.0.3", @@ -5602,7 +5602,7 @@ "resolve": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "integrity": "sha1-P8ZEo1yEpIVUYJ/ybsUrZvpXffY=", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -5613,7 +5613,7 @@ "eslint-plugin-jsx-a11y": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", - "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", + "integrity": "sha1-uHKgnV3lGvcKl9se6n3JMwQ3CKo=", "dev": true, "requires": { "@babel/runtime": "^7.4.5", @@ -5630,7 +5630,7 @@ "eslint-plugin-react": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz", - "integrity": "sha512-GacBAATewhhptbK3/vTP09CbFrgUJmBSaaRcWdbQLFvUZy9yVcQxigBNHGPU/KE2AyHpzj3AWXpxoMTsIDiHug==", + "integrity": "sha1-mSjk8+ISLtO6altW0DA7o+QdjAk=", "dev": true, "requires": { "array-includes": "^3.0.3", @@ -5647,7 +5647,7 @@ "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", "dev": true, "requires": { "esutils": "^2.0.2" @@ -5667,7 +5667,7 @@ "eslint-plugin-react-hooks": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz", - "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==" + "integrity": "sha1-YhC21aNyBfC5KFj4laToJwIKfQQ=" }, "eslint-scope": { "version": "5.0.0", @@ -5705,7 +5705,7 @@ "eslint-watch": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/eslint-watch/-/eslint-watch-6.0.1.tgz", - "integrity": "sha512-tWT6gQQWzGVn4KMs6ZAdG+nkNNQHg+c6wGdw/mwhp+jWyA7OWEOLxQnRhrP34ddJjpgDafhHOHBNtpVOtXXv4g==", + "integrity": "sha1-4Jg5FlinwUjeyhcq0glQZa2gU1k=", "dev": true, "requires": { "chokidar": "^3.1.1", @@ -5742,7 +5742,7 @@ "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", "dev": true, "requires": { "ms": "^2.1.1" @@ -6255,7 +6255,7 @@ "fetch-mock": { "version": "5.13.1", "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-5.13.1.tgz", - "integrity": "sha512-eWUo2KI4sRGnRu8tKELCBfasALM5BfvrCxdI7J02j3eUM9mf+uYzJkURA0PSn/29JVapVrYFm+z+9XijXu1PdA==", + "integrity": "sha1-lVeUp389ly8WRLms5loP39YPHfc=", "requires": { "glob-to-regexp": "^0.3.0", "node-fetch": "^1.3.3", @@ -7929,7 +7929,7 @@ "isparta": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/isparta/-/isparta-4.1.1.tgz", - "integrity": "sha512-kGwkNqmALQzdfGhgo5o8kOA88p14R3Lwg0nfQ/qzv4IhB4rXarT9maPMaYbo6cms4poWbeulrlFlURLUR6rDwQ==", + "integrity": "sha1-yS5JZylGkU7FQHyAEWDzN04LfLQ=", "dev": true, "requires": { "babel-core": "^6.1.4", @@ -7946,7 +7946,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=", "dev": true } } @@ -8188,7 +8188,7 @@ "istanbul-instrumenter-loader": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", - "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==", + "integrity": "sha1-mVe9WSUrNz+uXFK3tRiOb94qCUk=", "dev": true, "requires": { "convert-source-map": "^1.5.0", @@ -8239,7 +8239,7 @@ "loader-utils": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "integrity": "sha1-H/XcaRHJ8KBiUxpMBLYJQGEIwsc=", "dev": true, "requires": { "big.js": "^5.2.2", @@ -8519,7 +8519,7 @@ "karma-chrome-launcher": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", - "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "integrity": "sha1-gFpYZ5mk0F9OVPcqIEl58/MGZzg=", "dev": true, "requires": { "which": "^1.2.1" @@ -8539,7 +8539,7 @@ "karma-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.0.1.tgz", - "integrity": "sha512-SnFkHsnLsaXfxkey51rRN9JDLAEKYW2Lb0qOEvcruukk0NkSNDkjobNDZPt9Ni3kIhLZkLtpGOz661hN7OaZvQ==", + "integrity": "sha1-8CTxkfDxNd7iQDdjrDbMUqrCeaw=", "dev": true, "requires": { "dateformat": "^1.0.6", @@ -8557,13 +8557,13 @@ "istanbul-lib-coverage": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "integrity": "sha1-Z18KtpUD+tSx2En3NrqsqAM0T0k=", "dev": true }, "istanbul-lib-instrument": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "integrity": "sha1-pfY9kfC7wMPkee9MXeAnM17G1jA=", "dev": true, "requires": { "@babel/generator": "^7.4.0", @@ -8586,7 +8586,7 @@ "karma-coverage-istanbul-reporter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.0.tgz", - "integrity": "sha512-UH0mXPJFJyK5uiK7EkwGtQ8f30lCBAfqRResnZ4pzLJ04SOp4SPlYkmwbbZ6iVJ6sQFVzlDUXlntBEsLRdgZpg==", + "integrity": "sha1-XxvME8XhTuHZGCHuiUaGFnT1THU=", "dev": true, "requires": { "istanbul-api": "^2.1.6", @@ -8596,7 +8596,7 @@ "karma-firefox-launcher": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.2.0.tgz", - "integrity": "sha512-j9Zp8M8+VLq1nI/5xZGfzeaEPtGQ/vk3G+Y8vpmFWLvKLNZ2TDjD6cu2dUu7lDbu1HXNgatsAX4jgCZTkR9qhQ==", + "integrity": "sha1-ZP4D3RAwD5dU1I+ev78x9slKIAw=", "dev": true, "requires": { "is-wsl": "^2.1.0" @@ -8648,7 +8648,7 @@ "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "integrity": "sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -8688,7 +8688,7 @@ "karma-webpack": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-4.0.2.tgz", - "integrity": "sha512-970/okAsdUOmiMOCY8sb17A2I8neS25Ad9uhyK3GHgmRSIFJbDcNEFE8dqqUhNe9OHiCC9k3DMrSmtd/0ymP1A==", + "integrity": "sha1-IyGb2VvdqFPjBz04dNNER8d7ztA=", "dev": true, "requires": { "clone-deep": "^4.0.1", @@ -8717,7 +8717,7 @@ "loader-utils": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "integrity": "sha1-H/XcaRHJ8KBiUxpMBLYJQGEIwsc=", "dev": true, "requires": { "big.js": "^5.2.2", @@ -8734,7 +8734,7 @@ "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "integrity": "sha1-UwL4FpAxc1ImVECS5kmB91F1A4M=", "dev": true } } @@ -9532,7 +9532,7 @@ "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + "integrity": "sha1-DQVdU/UFKqZTyfbraLtdEr9cK1s=" }, "move-concurrently": { "version": "1.0.1", @@ -10643,7 +10643,7 @@ "react-chartjs-2": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.8.0.tgz", - "integrity": "sha512-BPpC+qfnh37DkcXvxRwA1rdD9rX/0AQrwru4VZTLofCCuZBwRsc7PbfxjilvoB6YlHhorwZu40YDWEQkoz7xfQ==", + "integrity": "sha1-HCTekfs3VfjEMCZ13n1m/dozl1k=", "requires": { "lodash": "^4.17.4", "prop-types": "^15.5.8" @@ -10652,7 +10652,7 @@ "react-ckeditor-wrapper": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/react-ckeditor-wrapper/-/react-ckeditor-wrapper-1.1.2.tgz", - "integrity": "sha512-/COVfezpSKFQxC/OjFoZf1PyzxTvUxzndlpGjEcajzjRgKPzSFZiCoh/VqqwGaaHJROO9pePQ9JxmJy2YlzDAQ==", + "integrity": "sha1-cvDGgo4X6mNxXV6YNvOrovhIfPM=", "requires": { "babel-runtime": "6.x", "classnames": "2.x", @@ -10665,7 +10665,7 @@ "react-datepicker": { "version": "0.60.2", "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-0.60.2.tgz", - "integrity": "sha512-5WNtLhozO5i6iGlcgpvjP/Wu4l7RqvTC48CEE/pS1juUny/T4juYHSv53mo+Z90qO4qfyUj59jECTT8AIwAVRQ==", + "integrity": "sha1-EIPcHLn/BjK8oywDmYz4PuHOLgE=", "requires": { "classnames": "^2.2.5", "moment": "^2.17.1", @@ -10694,7 +10694,7 @@ "react-hot-loader": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-3.1.3.tgz", - "integrity": "sha512-d7nZf78irxoGN5PY4zd6CSgZiroOhvIWzRast3qwTn4sSnBwlt08kV8WMQ9mitmxEdlCTwZt+5ClrRSjxWguMQ==", + "integrity": "sha1-b5KHcyaVjHywE0tRJHRReGkSYII=", "dev": true, "requires": { "global": "^4.3.0", @@ -10707,7 +10707,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -10746,15 +10746,9 @@ } }, "react-router": { -<<<<<<< HEAD - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-3.2.4.tgz", - "integrity": "sha512-5kIJXV1Yx+FYk0lDJoPQnt+qFf7HxS6XrIm2aCw0r3XQTxixFd0HSVlHenYRWKmSHlcvSQ7bpYWgdRwJGXWPKw==", -======= "version": "3.2.5", "resolved": "https://registry.npmjs.org/react-router/-/react-router-3.2.5.tgz", "integrity": "sha512-0/edMhPfOLRZ5IT3y6UkCpW7a13WrnGMR75ayAh2ZLynujEJOSptJt856GKnoCMW+7rk0/WYGUp/QaZNS9dTKg==", ->>>>>>> 1add5bc7fddddd17020ade4efb6afc2e16f761f9 "requires": { "create-react-class": "^15.5.1", "history": "^3.0.0", @@ -11200,7 +11194,7 @@ "semantic-ui-react": { "version": "0.79.1", "resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-0.79.1.tgz", - "integrity": "sha512-Z/QHE/h+l4MrZMilKe7RCBv6wPSBopcwRh5fPPZ1Cavxw4703XizJ3m+g7qcYXa5F40NnM0LKaVDuaReJ0f/3w==", + "integrity": "sha1-gL0Mz7OzwRhKGxs/YGerEiWu1fA=", "requires": { "babel-runtime": "^6.25.0", "classnames": "^2.2.5", @@ -11908,7 +11902,7 @@ "starwars": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/starwars/-/starwars-1.0.1.tgz", - "integrity": "sha512-d23qkhEuFNlHas4/w2J6ZF6qS7jqy8OK4N3gfQbtTk/5Lt1wKe+xv0cIweuNxwtwBGVXvmsLV2mdUOBKfj12cA==" + "integrity": "sha1-+OIWt4KUs/y/ytJbRPJxF543U9U=" }, "static-extend": { "version": "0.1.2", @@ -12450,7 +12444,7 @@ "uglifyjs-webpack-plugin": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-2.2.0.tgz", - "integrity": "sha512-mHSkufBmBuJ+KHQhv5H0MXijtsoA1lynJt1lXOaotja8/I0pR4L9oGaPIZw+bQBOFittXZg9OC1sXSGO9D9ZYg==", + "integrity": "sha1-51vIDn8ZN/cllUybTFoeln6p0Nc=", "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", @@ -12471,7 +12465,7 @@ "find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "integrity": "sha1-jQ+UzRP+Q8bHwmGg2GEVypGMBfc=", "requires": { "commondir": "^1.0.1", "make-dir": "^2.0.0", @@ -13030,7 +13024,7 @@ "webpack-bundle-tracker": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-0.4.3.tgz", - "integrity": "sha512-Sl/+OsNhFAH3/c6XADupRu8jLvGojfXX0pZIIm3O5ZcJqkHHqlY4nLG+NVRcbDgM/jOeWimKDGQMVEa8FBeJ2g==", + "integrity": "sha1-SKPiIm3bnSTXBGQKtBaMLRIPRSY=", "requires": { "deep-extend": "^0.6.0", "mkdirp": "^0.5.1",