Skip to content

Release 2020-11-23 #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
322c7b4
fix: allow ticket body to be blank in github_integration's CreateTick…
FNSdev Nov 1, 2020
87c92de
feat: use django messages to show erros that occur during GitHub logi…
FNSdev Nov 7, 2020
4af61d0
fix: correctly determine message type in NotificationsModalComponent
FNSdev Nov 7, 2020
1252872
feat: split update ticket and move ticket actions
FNSdev Nov 15, 2020
fe01a4c
feat: add priority to Category
FNSdev Nov 15, 2020
7c2450b
feat: add CategoryActionsViewSet
FNSdev Nov 15, 2020
1381c1e
feat: allow users to drag'n'drop categories
FNSdev Nov 15, 2020
4699211
chore: bump appVersion to 0.30
FNSdev Nov 15, 2020
9e670c6
test: fix failing tests
FNSdev Nov 15, 2020
4b1f411
fix: do not try to access dragged data in DragOver event
FNSdev Nov 15, 2020
3583272
feat: adjust OrganizationMembership permissions
FNSdev Nov 22, 2020
cc10381
feat: add endpoint to update OrganizationMembership's role
FNSdev Nov 22, 2020
40d8c4e
fix: use request_body in OpenAPI schema decorator instead of query_se…
FNSdev Nov 22, 2020
98985f6
feat: allow organization owner to change other member's role
FNSdev Nov 23, 2020
327d06f
chore: bump appVersion to 0.31 & bump dependencies
FNSdev Nov 23, 2020
e4b7e6a
chore: update requirements.txt file
FNSdev Nov 23, 2020
fa7b7c4
chore: add README.md
FNSdev Dec 3, 2020
f00d2ed
feat: allow users to update Category name and to delete Category
FNSdev Dec 6, 2020
3db6fbd
chore: bump appVersion to 0.32
FNSdev Dec 6, 2020
86df3c6
feat: do not allow members to delete tickets
FNSdev Dec 7, 2020
40f34cd
chore: bump appVersion to 0.33
FNSdev Dec 7, 2020
fd1f25b
test: fix failing test
FNSdev Dec 7, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.local
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
DJANGO_SECRET_KEY=GITRELLO_SECRET
DJANGO_SETTINGS_MODULE=gitrello.settings_local
DJANGO_DB_NAME=gitrello
DJANGO_DB_USER=gitrello
DJANGO_DB_PASSWORD=
Expand All @@ -11,6 +10,7 @@ GS_BUCKET_NAME=name
GS_PROJECT_ID=id
GS_CREDENTIALS=gcs_creds.json
URL=http://127.0.0.1:8000
GITHUB_INTEGRATION_SERVICE_URL=http://127.0.0.1:8001
GITHUB_INTEGRATION_SERVICE_URL=http://0.0.0.0:8001
GITHUB_INTEGRATION_SERVICE_TOKEN=CVb^3@1@3667nNGgK645VBnnM
GITHUB_CLIENT_ID=id
GITHUB_CLIENT_SECRET=secret
110 changes: 110 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,113 @@
# GITrello

[![Build Status](https://gitrello.me/jenkins/buildStatus/icon?job=gitrello)](https://gitrello.me/jenkins/job/gitrello/)

## How to run locally

### Tested on

* Linux Mint 19.3
* Python 3.8.5
* Poetry 1.1.4
* Docker 19.03.14
* Docker-Compose 1.26.0

### 1. Install required libraries

```
$ sudo apt install libpq-dev git gcc libc6-dev
```

### 2. Clone repository

```
$ git clone https://github.com/FNSdev/gitrello.git
$ cd gitrello
```

### 3. Create virtual environment

Consider using `pyenv` and `pyenv-virtualenv`

```
$ pyenv virtualenv 3.8.5 gitrello
$ pyenv local gitrello
```

### 4. Install dependencies

```
$ poetry install
```

### 5. Run database using Docker-Compose

#### 5.1. Launch containers

```
$ docker-compose up
```

#### 5.2. Initialize cluster

```
$ docker exec -it roach_1 ./cockroach init --insecure
```

#### 5.3. Create database & user

```
$ docker exec -it roach_1 ./cockroach sql --insecure
```

```sql
create database gitrello;
create user gitrello;
grant all on database gitrello to gitrello;
grant admin to gitrello;
```

### 6. Create .env file

It should be OK to use default values for now.

```
$ cp .env.local .env
```

### 7. Apply migrations

```
$ cd gitrello
$ python manage.py migrate
```

### 8. [OPTIONAL] Run test

```
$ python manage.py test
```

### 9. Run server

```
$ python manage.py runserver
```

### 10. [OPTIONAL] Configure integration with GitHub

#### 10.1. Set-up and run Gitrello-GitHub-Integration-Service

Follow instructions in https://github.com/FNSdev/gitrello-github-integration-service

#### 10.3. Create GitHub Oauth application

Visit https://github.com/settings/developers to create a new Oauth application.
Remember it's `client_id` and `client_secret`.

#### 10.2 Configure ENV variables

* GITHUB_INTEGRATION_SERVICE_URL - http://0.0.0.0:8001 by default
* GITHUB_INTEGRATION_SERVICE_TOKEN - Should be the same as GITRELLO_ACCESS_TOKEN in gitrello-github-integration-service
* GITHUB_CLIENT_ID - use value from the previous step
* GITHUB_CLIENT_SECRET - use value from the previous step
4 changes: 2 additions & 2 deletions gitrello/authentication/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class UsersView(views.APIView):
@retry_on_transaction_serialization_error
@atomic
@gitrello_schema(
query_serializer=CreateUserSerializer,
request_body=CreateUserSerializer,
responses={201: CreateUserResponseSerializer},
security=[],
)
Expand Down Expand Up @@ -100,7 +100,7 @@ class OauthStatesView(views.APIView):
# TODO add tests
@retry_on_transaction_serialization_error
@atomic
@gitrello_schema(query_serializer=CreateOauthStateSerializer, responses={201: CreateOauthStateResponseSerializer})
@gitrello_schema(request_body=CreateOauthStateSerializer, responses={201: CreateOauthStateResponseSerializer})
def post(self, request, *args, **kwargs):
serializer = CreateOauthStateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
Expand Down
9 changes: 0 additions & 9 deletions gitrello/authentication/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,3 @@ class UserAlreadyExistsException(GITRelloAuthenticationException):
class OauthStateNotFoundException(GITRelloAuthenticationException):
message = 'OauthState was not found'
code = 211


class GithubException(GITrelloException):
message = 'Something went wrong when using Github API'
code = 220

def __init__(self, error, error_description):
self.error = error
self.error_description = error_description
23 changes: 10 additions & 13 deletions gitrello/authentication/services/permissions_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,28 +61,21 @@ def get_organization_membership_permissions(cls, organization_membership_id: int
if not target_organization_membership:
return Permissions.with_no_permissions()

if target_organization_membership.user_id == user_id:
return Permissions.with_all_permissions()

membership = OrganizationMembership.objects \
.filter(
organization_id=target_organization_membership.organization_id,
user_id=user_id,
) \
.values('role') \
.first()

if not membership:
return Permissions.with_no_permissions()

if membership['role'] == OrganizationMemberRole.OWNER:
if membership.role == OrganizationMemberRole.OWNER:
return Permissions.with_all_permissions()

if membership['role'] == OrganizationMemberRole.ADMIN:
if target_organization_membership.role == OrganizationMemberRole.MEMBER:
return Permissions.with_all_permissions()

return Permissions.with_read_permissions()
if membership.id == target_organization_membership.id:
return Permissions(can_read=True, can_mutate=False, can_delete=True)

return Permissions.with_read_permissions()

Expand Down Expand Up @@ -179,16 +172,20 @@ def get_ticket_permissions(cls, ticket_id: int, user_id: int) -> Permissions:
if not ticket:
return Permissions.with_no_permissions()

is_board_member = BoardMembership.objects \
board_membership = BoardMembership.objects \
.filter(
organization_membership__user_id=user_id,
board_id=ticket['category__board_id'],
) \
.exists()
.prefetch_related('organization_membership') \
.first()

if not is_board_member:
if not board_membership:
return Permissions.with_no_permissions()

if board_membership.organization_membership.role == OrganizationMemberRole.MEMBER:
return Permissions.with_mutate_permissions()

return Permissions.with_all_permissions()

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ def _assert_has_mutate_permissions(self, permissions: Permissions):
self.assertTrue(permissions.can_mutate)
self.assertFalse(permissions.can_delete)

def _assert_has_read_and_delete_permissions(self, permissions: Permissions):
self.assertTrue(permissions.can_read)
self.assertTrue(permissions.can_delete)
self.assertFalse(permissions.can_mutate)

def _assert_has_all_permissions(self, permissions: Permissions):
self.assertTrue(permissions.can_read)
self.assertTrue(permissions.can_mutate)
Expand Down Expand Up @@ -68,13 +73,23 @@ def test_organization_membership_permissions_for_owner(self):
target_organization_membership = OrganizationMembershipFactory(
organization_id=organization_membership.organization_id,
)
target_admin_organization_membership = OrganizationMembershipFactory(
organization_id=organization_membership.organization_id,
role=OrganizationMemberRole.ADMIN,
)

permissions = PermissionsService.get_organization_membership_permissions(
organization_membership_id=target_organization_membership.id,
user_id=organization_membership.user_id,
)
self._assert_has_all_permissions(permissions)

permissions = PermissionsService.get_organization_membership_permissions(
organization_membership_id=target_admin_organization_membership.id,
user_id=organization_membership.user_id,
)
self._assert_has_all_permissions(permissions)

permissions = PermissionsService.get_organization_membership_permissions(
organization_membership_id=organization_membership.id,
user_id=organization_membership.user_id,
Expand All @@ -99,13 +114,13 @@ def test_organization_membership_permissions_for_admin(self):
organization_membership_id=target_organization_membership.id,
user_id=organization_membership.user_id,
)
self._assert_has_all_permissions(permissions)
self._assert_has_read_permissions(permissions)

permissions = PermissionsService.get_organization_membership_permissions(
organization_membership_id=organization_membership.id,
user_id=organization_membership.user_id,
)
self._assert_has_all_permissions(permissions)
self._assert_has_read_and_delete_permissions(permissions)

permissions = PermissionsService.get_organization_membership_permissions(
organization_membership_id=target_organization_owner_membership.id,
Expand Down Expand Up @@ -143,7 +158,7 @@ def test_organization_membership_permissions_for_member(self):
organization_membership_id=organization_membership.id,
user_id=organization_membership.user_id,
)
self._assert_has_all_permissions(permissions)
self._assert_has_read_and_delete_permissions(permissions)

permissions = PermissionsService.get_organization_membership_permissions(
organization_membership_id=target_organization_owner_membership.id,
Expand Down Expand Up @@ -391,7 +406,7 @@ def test_ticket_permissions_for_board_member(self):
ticket_id=ticket.id,
user_id=board_membership.organization_membership.user_id,
)
self._assert_has_all_permissions(permissions)
self._assert_has_mutate_permissions(permissions)

def test_ticket_permissions_for_not_a_board_member(self):
ticket = TicketFactory()
Expand Down
5 changes: 3 additions & 2 deletions gitrello/boards/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class BoardsView(views.APIView):

@retry_on_transaction_serialization_error
@atomic
@gitrello_schema(query_serializer=CreateBoardSerializer, responses={201: CreateBoardResponseSerializer})
@gitrello_schema(request_body=CreateBoardSerializer, responses={201: CreateBoardResponseSerializer})
def post(self, request, *args, **kwargs):
serializer = CreateBoardSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
Expand Down Expand Up @@ -49,7 +49,7 @@ class BoardMembershipsView(views.APIView):
@retry_on_transaction_serialization_error
@atomic
@gitrello_schema(
query_serializer=CreateBoardMembershipSerializer,
request_body=CreateBoardMembershipSerializer,
responses={201: CreateBoardMembershipResponseSerializer},
)
def post(self, request, *args, **kwargs):
Expand Down Expand Up @@ -99,6 +99,7 @@ class BoardPermissionsView(views.APIView):
security=[],
)
def get(self, request, *args, **kwargs):
# TODO use query params instead of request.data
serializer = GetBoardPermissionsSerializer(data=request.data)
serializer.is_valid(raise_exception=True)

Expand Down
7 changes: 7 additions & 0 deletions gitrello/core/templates/core/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@
window.GITHUB_REDIRECT_URL = "{{ GITHUB_REDIRECT_URL }}";
window.GITHUB_DEFAULT_SCOPES = "{{ GITHUB_DEFAULT_SCOPES }}";
window.GITHUB_INTEGRATION_SERVICE_URL = "{{ GITHUB_INTEGRATION_SERVICE_URL }}";

window.MESSAGES = [];
{% if messages %}
{% for message in messages %}
window.MESSAGES.push({"type": "{{ message.tags }}", "text": "{{ message }}"});
{% endfor %}
{% endif %}
</script>
</body>
</html>
4 changes: 2 additions & 2 deletions gitrello/github_integration/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@

class CreateTicketSerializer(serializers.Serializer):
board_id = serializers.IntegerField()
title = serializers.CharField()
body = serializers.CharField()
title = serializers.CharField(allow_blank=True)
body = serializers.CharField(allow_blank=True)
15 changes: 15 additions & 0 deletions gitrello/github_integration/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from gitrello.exceptions import GITrelloException


class GithubException(GITrelloException):
message = 'Something went wrong when using Github API'
code = 220

def __init__(self, error, error_description):
self.error = error
self.error_description = error_description


class GithubAccountUsedByAnotherUserException(GITrelloException):
message = 'This GitHub account is being used by another GITrello user'
code = 230
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.conf import settings

from authentication.services import UserService
from github_integration.exceptions import GithubAccountUsedByAnotherUserException
from gitrello.exceptions import HttpRequestException
from gitrello.handlers import safe_http_request

Expand All @@ -27,5 +28,12 @@ def create_github_profile(self, access_token: str):
headers=self.headers,
)

if not response.status_code == 201:
raise HttpRequestException
if response.status_code == 201:
return

if response.status_code == 400:
data = response.json()
if data['error_code'] == self.ALREADY_EXISTS:
raise GithubAccountUsedByAnotherUserException

raise HttpRequestException
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import requests
from django.conf import settings

from authentication.exceptions import GithubException
from github_integration.exceptions import GithubException
from gitrello.handlers import safe_http_request


Expand Down
Loading