From aa52a569b28831c5537ea37545ab74e91c4407bb Mon Sep 17 00:00:00 2001 From: Developer Date: Fri, 3 Mar 2017 16:38:15 +0200 Subject: [PATCH 1/4] Initial --- .gitignore | 1 + source/Dockerfile | 11 ++ source/django-app/.gitignore | 2 + source/django-app/gitradar/__init__.py | 0 source/django-app/gitradar/settings.py | 115 ++++++++++++++++++ source/django-app/gitradar/urls.py | 22 ++++ source/django-app/gitradar/wsgi.py | 16 +++ source/django-app/manage.py | 22 ++++ source/django-app/requirements.txt | 5 + source/django-app/sync_machine/__init__.py | 0 source/django-app/sync_machine/admin.py | 3 + source/django-app/sync_machine/apps.py | 5 + .../sync_machine/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/syncdata.py | 10 ++ .../sync_machine/migrations/.gitignore | 3 + .../sync_machine/migrations/__init__.py | 0 source/django-app/sync_machine/models.py | 50 ++++++++ source/django-app/sync_machine/tests.py | 59 +++++++++ source/django-app/sync_machine/urls.py | 11 ++ source/django-app/sync_machine/views.py | 38 ++++++ 21 files changed, 373 insertions(+) create mode 100644 .gitignore create mode 100644 source/Dockerfile create mode 100644 source/django-app/.gitignore create mode 100644 source/django-app/gitradar/__init__.py create mode 100644 source/django-app/gitradar/settings.py create mode 100644 source/django-app/gitradar/urls.py create mode 100644 source/django-app/gitradar/wsgi.py create mode 100755 source/django-app/manage.py create mode 100644 source/django-app/requirements.txt create mode 100644 source/django-app/sync_machine/__init__.py create mode 100644 source/django-app/sync_machine/admin.py create mode 100644 source/django-app/sync_machine/apps.py create mode 100644 source/django-app/sync_machine/management/__init__.py create mode 100644 source/django-app/sync_machine/management/commands/__init__.py create mode 100644 source/django-app/sync_machine/management/commands/syncdata.py create mode 100644 source/django-app/sync_machine/migrations/.gitignore create mode 100644 source/django-app/sync_machine/migrations/__init__.py create mode 100644 source/django-app/sync_machine/models.py create mode 100644 source/django-app/sync_machine/tests.py create mode 100644 source/django-app/sync_machine/urls.py create mode 100644 source/django-app/sync_machine/views.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/source/Dockerfile b/source/Dockerfile new file mode 100644 index 0000000..40c9c45 --- /dev/null +++ b/source/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.5.2 +ENV PYTHONUNBUFFERED 1 +ENV DJANGO_SETTINGS_MODULE gitradar.settings +RUN mkdir /django-app +ADD /django-app /django-app +WORKDIR /django-app +RUN pip install -r requirements.txt +CMD python manage.py makemigrations +CMD python manage.py migrate +CMD python manage.py test +CMD python manage.py runserver 0.0.0.0:8080 \ No newline at end of file diff --git a/source/django-app/.gitignore b/source/django-app/.gitignore new file mode 100644 index 0000000..6fa1d34 --- /dev/null +++ b/source/django-app/.gitignore @@ -0,0 +1,2 @@ +.env +db.sqlite3 \ No newline at end of file diff --git a/source/django-app/gitradar/__init__.py b/source/django-app/gitradar/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/django-app/gitradar/settings.py b/source/django-app/gitradar/settings.py new file mode 100644 index 0000000..ff92690 --- /dev/null +++ b/source/django-app/gitradar/settings.py @@ -0,0 +1,115 @@ +""" +Django settings for gitradar project. + +Generated by 'django-admin startproject' using Django 1.10.6. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.10/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'ri%+7o1lp@ltxo+j(jgf=t*=49k2)x66ri&+w3+l$uob=5yb)%' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'sync_machine' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'gitradar.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'gitradar.wsgi.application' + +# Database +# https://docs.djangoproject.com/en/1.10/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + +# Password validation +# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/1.10/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.10/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/source/django-app/gitradar/urls.py b/source/django-app/gitradar/urls.py new file mode 100644 index 0000000..fff6add --- /dev/null +++ b/source/django-app/gitradar/urls.py @@ -0,0 +1,22 @@ +"""gitradar URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.10/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url, include +from django.contrib import admin + +urlpatterns = [ + url(r'^admin/', admin.site.urls), + url(r'^api/', include('sync_machine.urls')) +] diff --git a/source/django-app/gitradar/wsgi.py b/source/django-app/gitradar/wsgi.py new file mode 100644 index 0000000..a511f3f --- /dev/null +++ b/source/django-app/gitradar/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for gitradar project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gitradar.settings") + +application = get_wsgi_application() diff --git a/source/django-app/manage.py b/source/django-app/manage.py new file mode 100755 index 0000000..8f576ca --- /dev/null +++ b/source/django-app/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gitradar.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/source/django-app/requirements.txt b/source/django-app/requirements.txt new file mode 100644 index 0000000..4394ac3 --- /dev/null +++ b/source/django-app/requirements.txt @@ -0,0 +1,5 @@ +appdirs==1.4.2 +Django==1.10.6 +packaging==16.8 +pyparsing==2.1.10 +six==1.10.0 diff --git a/source/django-app/sync_machine/__init__.py b/source/django-app/sync_machine/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/django-app/sync_machine/admin.py b/source/django-app/sync_machine/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/source/django-app/sync_machine/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/source/django-app/sync_machine/apps.py b/source/django-app/sync_machine/apps.py new file mode 100644 index 0000000..ade1847 --- /dev/null +++ b/source/django-app/sync_machine/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class SyncMachineConfig(AppConfig): + name = 'sync_machine' diff --git a/source/django-app/sync_machine/management/__init__.py b/source/django-app/sync_machine/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/django-app/sync_machine/management/commands/__init__.py b/source/django-app/sync_machine/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/django-app/sync_machine/management/commands/syncdata.py b/source/django-app/sync_machine/management/commands/syncdata.py new file mode 100644 index 0000000..5b317e3 --- /dev/null +++ b/source/django-app/sync_machine/management/commands/syncdata.py @@ -0,0 +1,10 @@ +from django.core.management import BaseCommand + +from sync_machine.models import Commit + + +class Command(BaseCommand): + help = 'Closes the specified poll for voting' + + def handle(self, *args, **options): + Commit.data_loader.run() diff --git a/source/django-app/sync_machine/migrations/.gitignore b/source/django-app/sync_machine/migrations/.gitignore new file mode 100644 index 0000000..e839540 --- /dev/null +++ b/source/django-app/sync_machine/migrations/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!__init__.py \ No newline at end of file diff --git a/source/django-app/sync_machine/migrations/__init__.py b/source/django-app/sync_machine/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/django-app/sync_machine/models.py b/source/django-app/sync_machine/models.py new file mode 100644 index 0000000..d680227 --- /dev/null +++ b/source/django-app/sync_machine/models.py @@ -0,0 +1,50 @@ +from django.db import models +from urllib import request +import json + + +class Contributor(models.Model): + name = models.CharField(blank=False, max_length=100) + email = models.EmailField() + + def __str__(self): + return self.name + + +class CommitDataLoader(models.Manager): + def run(self): + path = 'https://api.github.com/repos/nodejs/node/commits' + f = request.urlopen(path) + response = f.read().decode('utf-8') + commits = json.loads(response) + for commit in commits: + sha = commit.get('sha') + message = commit['commit']['message'] + + author_email = commit['commit']['author']['email'] + author_name = commit['commit']['author']['name'] + author, created = Contributor.objects.get_or_create( + email=author_email, + name=author_name) + + Commit.objects.get_or_create(sha=sha, + defaults={ + 'author': author, + 'message': message + }) + + +class Commit(models.Model): + STATUS_TYPE = ( + ('R', 'read'), + ('U', 'unread') + ) + + sha = models.CharField(blank=False, max_length=40) + message = models.TextField() + author = models.ForeignKey(Contributor) + status = models.CharField(blank=False, max_length=1, default='U', choices=STATUS_TYPE) + created_at = models.DateTimeField(auto_now_add=True) + + data_loader = CommitDataLoader() + objects = models.Manager() diff --git a/source/django-app/sync_machine/tests.py b/source/django-app/sync_machine/tests.py new file mode 100644 index 0000000..7eec7e8 --- /dev/null +++ b/source/django-app/sync_machine/tests.py @@ -0,0 +1,59 @@ +from django.test import Client +from django.test import TestCase +import json + +from sync_machine.models import Contributor, Commit + + +class AuthTest(TestCase): + def setUp(self): + Commit.data_loader.run() + + def test_api_authors(self): + """ + Should test API route: /api/authors + :return: + """ + count_authors = Contributor.objects.all().count() + + response = Client().get('/api/authors/') + content = json.loads(response.content.decode("utf-8")) + result = content.get('result') + + self.assertEqual(response.status_code, 200) + self.assertIsNotNone(result) + self.assertEqual(count_authors, len(result)) + + def test_api_commits_author(self): + """ + Should test API route: /api/commits/author/(?P[0-9]+) + :return: + """ + author = Contributor.objects.all().first() + commit_author_count = Commit.objects.filter(author_id=author.id).count() + + response = Client().get('/api/commits/author/' + str(author.id) + '/') + content = json.loads(response.content.decode("utf-8")) + result = content.get('result') + + self.assertIsNotNone(author) + self.assertGreater(commit_author_count, 0) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(result), commit_author_count) + + def test_api_commit_status(self): + """ + Should test API route: /api/commit/(?P[0-9]+)/(?P(r|u)+) + :return: + """ + author = Contributor.objects.all().first() + commit = Commit.objects.filter(author_id=author.id).first() + + self.assertIsNotNone(commit) + self.assertEqual(commit.status, 'U') + + response = Client().get('/api/commit/' + str(commit.id) + '/' + 'R' + '/') + self.assertEqual(response.status_code, 200) + + commit.refresh_from_db() + self.assertEqual(commit.status, 'R') diff --git a/source/django-app/sync_machine/urls.py b/source/django-app/sync_machine/urls.py new file mode 100644 index 0000000..fd13344 --- /dev/null +++ b/source/django-app/sync_machine/urls.py @@ -0,0 +1,11 @@ +from django.conf.urls import url, include + +from sync_machine.views import authors_list, commits_list, commit_update_status + +urlpatterns = ( + + url(r'^authors/$', authors_list), + url(r'^commits/author/(?P[0-9]+)/$', commits_list), + url(r'^commit/(?P[0-9]+)/(?P(R|U)?)/$', commit_update_status) + +) diff --git a/source/django-app/sync_machine/views.py b/source/django-app/sync_machine/views.py new file mode 100644 index 0000000..e4febba --- /dev/null +++ b/source/django-app/sync_machine/views.py @@ -0,0 +1,38 @@ +from django.http import JsonResponse + +from sync_machine.models import Commit, Contributor + + +def authors_list(request): + authors = Contributor.objects.all() + results = [] + for author in authors: + results.append({ + 'id': author.id, + 'name': author.name + }) + response = dict(result=results) + return JsonResponse(response, status=200) + + +def commits_list(request, author_id=None): + commits = Commit.objects.all().filter(author_id=author_id) + results = [] + for commit in commits: + results.append({ + 'id': commit.id, + 'sha': commit.sha, + 'status': commit.get_status_display() + }) + response = dict(result=results) + return JsonResponse(response, status=200) + + +def commit_update_status(request, commit_id=None, status=None): + try: + commit = Commit.objects.all().get(id=commit_id) + commit.status = status + commit.save() + return JsonResponse({'result': 'Updated'}, status=200) + except Commit.DoesNotExist: + return JsonResponse({'error': 'Object not found'}, status=404) From 4fa9be9b7bec8f8867d176a8bcabb95f68cbb0a6 Mon Sep 17 00:00:00 2001 From: Developer Date: Fri, 3 Mar 2017 18:39:47 +0200 Subject: [PATCH 2/4] config cron --- source/Dockerfile | 2 +- source/README.md | 8 ++++++++ source/django-app/cronjob | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 source/README.md create mode 100644 source/django-app/cronjob diff --git a/source/Dockerfile b/source/Dockerfile index 40c9c45..7d64aed 100644 --- a/source/Dockerfile +++ b/source/Dockerfile @@ -8,4 +8,4 @@ RUN pip install -r requirements.txt CMD python manage.py makemigrations CMD python manage.py migrate CMD python manage.py test -CMD python manage.py runserver 0.0.0.0:8080 \ No newline at end of file +CMD python manage.py runserver 0.0.0.0:8080 diff --git a/source/README.md b/source/README.md new file mode 100644 index 0000000..91ff720 --- /dev/null +++ b/source/README.md @@ -0,0 +1,8 @@ +```bash +cd source +docker build --tag=gitradar . +docker run -it --rm --publish=127.0.0.1:8080:8080 gitradar +``` + +sudo docker build --tag=gitradar . +sudo docker run -it --rm --publish=127.0.0.1:8080:8080 gitradar diff --git a/source/django-app/cronjob b/source/django-app/cronjob new file mode 100644 index 0000000..75b38d3 --- /dev/null +++ b/source/django-app/cronjob @@ -0,0 +1 @@ +* * * * * root echo "Hello world" >> /var/log/cron.log 2>&1 From 1e0c117d801fa68680a285d8b58d7929f5fd7dd6 Mon Sep 17 00:00:00 2001 From: Developer Date: Fri, 3 Mar 2017 20:05:24 +0200 Subject: [PATCH 3/4] Config Docker. --- source/Dockerfile | 13 ++++++++++++- source/README.md | 3 --- source/django-app/cron.sh | 4 ++++ source/django-app/cronjob | 2 +- .../sync_machine/management/commands/syncdata.py | 1 + 5 files changed, 18 insertions(+), 5 deletions(-) create mode 100755 source/django-app/cron.sh diff --git a/source/Dockerfile b/source/Dockerfile index 7d64aed..02182d6 100644 --- a/source/Dockerfile +++ b/source/Dockerfile @@ -1,11 +1,22 @@ FROM python:3.5.2 +RUN apt-get update && apt-get -y install rsyslog mc ENV PYTHONUNBUFFERED 1 ENV DJANGO_SETTINGS_MODULE gitradar.settings + RUN mkdir /django-app ADD /django-app /django-app WORKDIR /django-app + RUN pip install -r requirements.txt CMD python manage.py makemigrations CMD python manage.py migrate CMD python manage.py test -CMD python manage.py runserver 0.0.0.0:8080 + +ADD /django-app/cronjob /etc/cron.d/ +RUN chmod +x /django-app/cron.sh +RUN chmod 0644 /etc/cron.d/cronjob +RUN touch /var/log/cron.log +RUN service cron reload + +CMD python /django-app/manage.py syncdata +CMD python manage.py runserver 0.0.0.0:8080 \ No newline at end of file diff --git a/source/README.md b/source/README.md index 91ff720..da77c3f 100644 --- a/source/README.md +++ b/source/README.md @@ -3,6 +3,3 @@ cd source docker build --tag=gitradar . docker run -it --rm --publish=127.0.0.1:8080:8080 gitradar ``` - -sudo docker build --tag=gitradar . -sudo docker run -it --rm --publish=127.0.0.1:8080:8080 gitradar diff --git a/source/django-app/cron.sh b/source/django-app/cron.sh new file mode 100755 index 0000000..ac824d4 --- /dev/null +++ b/source/django-app/cron.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# cron.sh + +python /django-app/manage.py syncdata \ No newline at end of file diff --git a/source/django-app/cronjob b/source/django-app/cronjob index 75b38d3..84f4f7c 100644 --- a/source/django-app/cronjob +++ b/source/django-app/cronjob @@ -1 +1 @@ -* * * * * root echo "Hello world" >> /var/log/cron.log 2>&1 +* * * * * root /django-app/cron.sh >> /var/log/cron.log 2>&1 diff --git a/source/django-app/sync_machine/management/commands/syncdata.py b/source/django-app/sync_machine/management/commands/syncdata.py index 5b317e3..7b5b1fa 100644 --- a/source/django-app/sync_machine/management/commands/syncdata.py +++ b/source/django-app/sync_machine/management/commands/syncdata.py @@ -8,3 +8,4 @@ class Command(BaseCommand): def handle(self, *args, **options): Commit.data_loader.run() + print('Task Complete.') From c9191546bebc40135265369f7830436ba1daaec0 Mon Sep 17 00:00:00 2001 From: Developer Date: Fri, 3 Mar 2017 20:13:39 +0200 Subject: [PATCH 4/4] Config Docker. --- source/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/source/README.md b/source/README.md index da77c3f..0883d9e 100644 --- a/source/README.md +++ b/source/README.md @@ -3,3 +3,25 @@ cd source docker build --tag=gitradar . docker run -it --rm --publish=127.0.0.1:8080:8080 gitradar ``` + +#API + +## List of authors +``` +GET /api/authors/ +``` + +## Commits from an author + +``` +GET /api/commits/author/{{AUTHOR_ID}} +``` + +## Mark commit as read or unread +``` +GET /api/commit/{{COMMIT_ID}}/{{STATUS}}/ +``` + +STATUS is status of commit: + 'R' - read + 'U' - unread \ No newline at end of file