Merge branch 'feature/backend' into 'master'
Feature/backend See merge request !4
Showing
16 changed files
with
678 additions
and
29 deletions
... | @@ -31,6 +31,7 @@ ALLOWED_HOSTS = ['127.0.0.1', 'khubox-api.khunet.net'] | ... | @@ -31,6 +31,7 @@ ALLOWED_HOSTS = ['127.0.0.1', 'khubox-api.khunet.net'] |
31 | # Application definition | 31 | # Application definition |
32 | 32 | ||
33 | INSTALLED_APPS = [ | 33 | INSTALLED_APPS = [ |
34 | + 'khubox.apps.KhuboxConfig', | ||
34 | 'django.contrib.admin', | 35 | 'django.contrib.admin', |
35 | 'django.contrib.auth', | 36 | 'django.contrib.auth', |
36 | 'django.contrib.contenttypes', | 37 | 'django.contrib.contenttypes', |
... | @@ -43,7 +44,6 @@ MIDDLEWARE = [ | ... | @@ -43,7 +44,6 @@ MIDDLEWARE = [ |
43 | 'django.middleware.security.SecurityMiddleware', | 44 | 'django.middleware.security.SecurityMiddleware', |
44 | 'django.contrib.sessions.middleware.SessionMiddleware', | 45 | 'django.contrib.sessions.middleware.SessionMiddleware', |
45 | 'django.middleware.common.CommonMiddleware', | 46 | 'django.middleware.common.CommonMiddleware', |
46 | - 'django.middleware.csrf.CsrfViewMiddleware', | ||
47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', | 47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', |
48 | 'django.contrib.messages.middleware.MessageMiddleware', | 48 | 'django.contrib.messages.middleware.MessageMiddleware', |
49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', | 49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', |
... | @@ -122,3 +122,10 @@ USE_TZ = True | ... | @@ -122,3 +122,10 @@ USE_TZ = True |
122 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ | 122 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ |
123 | 123 | ||
124 | STATIC_URL = '/static/' | 124 | STATIC_URL = '/static/' |
125 | + | ||
126 | + | ||
127 | +# Custom Settings | ||
128 | +S3_BUCKET = 'khubox-files' | ||
129 | +CDN_PATH = 'https://khubox-files.khunet.net' | ||
130 | +CLOUDFRONT_KEY_ID = 'APKAJ3FOBWI34OZJTXJQ' | ||
131 | +CLOUDFRONT_KEY_PRIVATE = '-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA18VtzURs+fQev5L00LRwRbJaObQI4kfJCIsOE7eWSOqq4Akh\nA7fI6vs3z8orXBvgc+k6GgAHrIdNwckxoQuTsCxrTDm1104qy1T4JkVxkIBYHJgH\nGzKUloK5IqdmcYbOK7IQeHJ2gR9Mv/3oKUytJSsrbM9k4oLrsxGpyEuJeHIg28aP\nwhoVWmBGcPu48l4aYAZEVY7LZRJSOQ9y7Lf8FS1u7Xtw1P91gEaqrqVXqRWY02C8\nsixpJJuiAPnM3rpcpVNlAaPdDkWmaWYJoJDOlce7Dmx1a9Ckr24krM//vpEljurC\nGml0AsHpL8LE9msM5VA+miCxCz/K+wDgm2xvvQIDAQABAoIBAFmP3pLceyuJVBYK\n5smWjB+x91eKTkG2sFB2f8JZau0bUxApWeXULHa1DiaW8UaLX7BdN7vBFW5cvz7X\nx1zklEoFNghuz/btwD+kJlikbI4hZ/F+fTyh0yFiY3xp5dDrtrpWcBW+1UeleVMc\nDnjOFfSepajFsUeANlue0k2MZSRz34s2T2scV5ZkooqdXddYUF/wDhefYm6uvCgI\nPyvY/mbJTyhte/xagY/m6yzk5gxgad0qP2ZZrHhLLMlJ/GEZToWDxD2xUei61NQT\nFFc5ZutkAE6fVb3I4SJUBSX5fl0tTMz4Aak1GP2phMhjZyjYnQMq8kvL4BNFb7gp\nary8W/UCgYEA+eKkfjjlPsEx6yHMhD7pAwy/MpUqJmF5LMxIG+qfd+GZMQ2oucn5\npUAwBHP7BD6E9H7/7jdjnCiO+iPrzM9vNLfqsdCtPWzoFYJp/6Fv002uX8seNYvJ\nQyQqrM85LYIghhnkcmJMA8GR/Iu5ZEeE2BkAl9T2EKclzmB62d/ki6MCgYEA3Q0V\nz08IEwSJW+jEsOM+XGg2YkNqCVKGQD9n4CPx0TFVJxfqFl2nVwlN2hfrlJLUQ9+l\nfXnS5AW3tE88t9we+ea0saJZEqqlm/rGsfTV/twS9cWSgvG5fTzhUbu9/ElMU29L\nmydQfWTvCup7zCuQtgwM5ZRtPwuKsI8urUg6zR8CgYAt0coZvvMCI8i0dbkbkrGF\nNqQkcUeOTBc9CKQ8QjRFdh9x6DBFCOz2ySNE3cNsTs5wSo1BL/Ta4HD/GvEU2ABr\nKUImor3xYnPX5dbr4b0wgLD1rbf3V49q+Um98C1q086E6GCEPNP1aFwNc81lvtt0\nCHmcXZdVDGEZS4WbR7uPgwKBgBO/moY12lPQoPDsH75p3uVkjg9DVJLWo5XT1FTr\nASyeSqw+b7Rl05BsDV+BqZNRdtNFhMRsANJMTHg4aAVJDh9nZBdGmMyZIEiKI/w8\nEm49fRgl+YvnSpoMuViS/EswxTfjBo8q+P7q6IxCHKNF9Ry+gNx14TizsEVL1XC3\ntkEjAoGBAMyp7wdPobJMXcclRVq6rqHs9OMcnZAveVKyxNgDbZu4OB5X4xTxGEYT\nNZQ0MFf/HcwlnH7797gVQeqF9dlqUJYe+Fc8lc/Rcwta/4R5uMgri9t8RKN91YKF\nUUFBsDEkWlkoAmfPkcrrq9cLJlmSNt3ehQj4p5iAJwoVBXXa++PO\n-----END RSA PRIVATE KEY-----' | ... | ... |
1 | -"""config URL Configuration | 1 | +from django.conf.urls import include, url |
2 | - | ||
3 | -The `urlpatterns` list routes URLs to views. For more information please see: | ||
4 | - https://docs.djangoproject.com/en/1.11/topics/http/urls/ | ||
5 | -Examples: | ||
6 | -Function views | ||
7 | - 1. Add an import: from my_app import views | ||
8 | - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') | ||
9 | -Class-based views | ||
10 | - 1. Add an import: from other_app.views import Home | ||
11 | - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') | ||
12 | -Including another URLconf | ||
13 | - 1. Import the include() function: from django.conf.urls import url, include | ||
14 | - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) | ||
15 | -""" | ||
16 | -from django.conf.urls import url | ||
17 | -from django.contrib import admin | ||
18 | 2 | ||
19 | urlpatterns = [ | 3 | urlpatterns = [ |
20 | - url(r'^admin/', admin.site.urls), | 4 | + url('', include('khubox.controllers')), |
21 | ] | 5 | ] | ... | ... |
khubox-api/khubox/admin.py
deleted
100644 → 0
khubox-api/khubox/aws.py
0 → 100644
1 | +import boto3 | ||
2 | +import datetime | ||
3 | +from botocore.signers import CloudFrontSigner | ||
4 | +from cryptography.hazmat.backends import default_backend | ||
5 | +from cryptography.hazmat.primitives import hashes, serialization | ||
6 | +from cryptography.hazmat.primitives.asymmetric import padding | ||
7 | +from django.conf import settings | ||
8 | + | ||
9 | + | ||
10 | +def rsa_signer(message): | ||
11 | + private_key = serialization.load_pem_private_key( | ||
12 | + settings.CLOUDFRONT_KEY_PRIVATE.encode('ascii'), | ||
13 | + password=None, | ||
14 | + backend=default_backend() | ||
15 | + ) | ||
16 | + return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1()) | ||
17 | + | ||
18 | + | ||
19 | +def sign_download(url): | ||
20 | + expire_date = datetime.datetime.utcnow() + datetime.timedelta(hours=1) | ||
21 | + cloudfront_signer = CloudFrontSigner(settings.CLOUDFRONT_KEY_ID, rsa_signer) | ||
22 | + signed_url = cloudfront_signer.generate_presigned_url(url, date_less_than=expire_date) | ||
23 | + return signed_url | ||
24 | + | ||
25 | + | ||
26 | +def sign_upload(file_id): | ||
27 | + s3 = boto3.client('s3') | ||
28 | + signed_url = s3.generate_presigned_url( | ||
29 | + 'put_object', | ||
30 | + Params={'Bucket': settings.S3_BUCKET, 'Key': file_id}, | ||
31 | + ExpiresIn=3600, | ||
32 | + HttpMethod='PUT' | ||
33 | + ) | ||
34 | + return signed_url | ||
35 | + | ||
36 | + | ||
37 | +def s3_delete(del_list): | ||
38 | + s3 = boto3.resource('s3') | ||
39 | + bucket = s3.Bucket(settings.S3_BUCKET) | ||
40 | + del_s3_list = [] | ||
41 | + for key in del_list: | ||
42 | + del_s3_list.append({'Key': key}) | ||
43 | + bucket.delete_objects(Delete={'Objects': del_s3_list}) | ||
44 | + | ||
45 | + | ||
46 | +def s3_copy(file_id, new_file_id): | ||
47 | + s3 = boto3.resource('s3') | ||
48 | + bucket = s3.Bucket(settings.S3_BUCKET) | ||
49 | + copy_source = { | ||
50 | + 'Bucket': settings.S3_BUCKET, | ||
51 | + 'Key': file_id | ||
52 | + } | ||
53 | + obj = bucket.Object(str(new_file_id)) | ||
54 | + obj.copy(copy_source) |
khubox-api/khubox/controllers/__init__.py
0 → 100644
1 | +from django.conf.urls import url | ||
2 | + | ||
3 | +from . import files, groups, users | ||
4 | + | ||
5 | +urlpatterns = [ | ||
6 | + url(r'^files$', files.index), # 폴더 생성, 파일 업로드, 폴더/파일 목록 | ||
7 | + url(r'^files/trash$', files.trash), # 휴지통 비우기 | ||
8 | + url(r'^files/(?P<file_id>[-\w]+)$', files.item), # 폴더/파일 조회, 폴더/파일 수정 | ||
9 | + url(r'^files/(?P<file_id>[-\w]+)/copy$', files.copy), # 파일 복제 | ||
10 | + url(r'^groups$', groups.index), # 그룹 생성 | ||
11 | + url(r'^groups/invite/(?P<invite_code>[-\w]+)$', groups.invite), # 그룹 초대장 조회, 그룹 초대장 사용 | ||
12 | + url(r'^groups/me$', groups.me), # 그룹 목록 | ||
13 | + url(r'^groups/(?P<group_id>\d+)$', groups.item), # 그룹 조회, 그룹 수정, 그룹 삭제 | ||
14 | + url(r'^groups/(?P<group_id>\d+)/users/(?P<user_id>\d+)$', groups.remove_user), # 그룹 사용자 삭제 | ||
15 | + url(r'^users$', users.index), # 회원가입 | ||
16 | + url(r'^users/login$', users.login), # 로그인 | ||
17 | + url(r'^users/me$', users.me), # 회원정보 조회, 회원정보 수정 | ||
18 | +] |
khubox-api/khubox/controllers/files.py
0 → 100644
1 | +from django.http import JsonResponse, Http404 | ||
2 | +from ..services import files | ||
3 | + | ||
4 | + | ||
5 | +def index(request): | ||
6 | + # 폴더/파일 목록 | ||
7 | + if request.method == 'GET': | ||
8 | + return JsonResponse(files.list_item(request)) | ||
9 | + # 폴더 생성, 파일 업로드 | ||
10 | + elif request.method == 'POST': | ||
11 | + return JsonResponse(files.create(request)) | ||
12 | + raise Http404 | ||
13 | + | ||
14 | + | ||
15 | +def trash(request): | ||
16 | + # 휴지통 비우기 | ||
17 | + if request.method == 'DELETE': | ||
18 | + return JsonResponse(files.empty_trash(request)) | ||
19 | + raise Http404 | ||
20 | + | ||
21 | + | ||
22 | +def item(request, file_id): | ||
23 | + # 폴더/파일 조회 | ||
24 | + if request.method == 'GET': | ||
25 | + return JsonResponse(files.find_item(request, file_id)) | ||
26 | + # 폴더/파일 수정 | ||
27 | + elif request.method == 'PATCH': | ||
28 | + return JsonResponse(files.update_item(request, file_id)) | ||
29 | + raise Http404 | ||
30 | + | ||
31 | + | ||
32 | +def copy(request, file_id): | ||
33 | + # 파일 복제 | ||
34 | + if request.method == 'POST': | ||
35 | + return JsonResponse(files.copy(request, file_id)) | ||
36 | + raise Http404 |
khubox-api/khubox/controllers/groups.py
0 → 100644
1 | +from django.http import JsonResponse, Http404 | ||
2 | +from ..services import groups | ||
3 | + | ||
4 | + | ||
5 | +def index(request): | ||
6 | + # 그룹 생성 | ||
7 | + if request.method == 'POST': | ||
8 | + return JsonResponse(groups.create(request)) | ||
9 | + raise Http404 | ||
10 | + | ||
11 | + | ||
12 | +def invite(request, invite_code): | ||
13 | + # 그룹 초대장 조회 | ||
14 | + if request.method == 'GET': | ||
15 | + return JsonResponse(groups.find_invite(request, invite_code)) | ||
16 | + # 그룹 초대장 사용 | ||
17 | + elif request.method == 'POST': | ||
18 | + return JsonResponse(groups.use_invite(request, invite_code)) | ||
19 | + raise Http404 | ||
20 | + | ||
21 | + | ||
22 | +def me(request): | ||
23 | + # 그룹 목록 | ||
24 | + if request.method == 'GET': | ||
25 | + return JsonResponse(groups.list_me(request)) | ||
26 | + raise Http404 | ||
27 | + | ||
28 | + | ||
29 | +def item(request, group_id): | ||
30 | + # 그룹 조회 | ||
31 | + if request.method == 'GET': | ||
32 | + return JsonResponse(groups.find_item(request, group_id)) | ||
33 | + # 그룹 수정 | ||
34 | + elif request.method == 'PATCH': | ||
35 | + return JsonResponse(groups.update_item(request, group_id)) | ||
36 | + # 그룹 삭제 | ||
37 | + elif request.method == 'DELETE': | ||
38 | + return JsonResponse(groups.delete_item(request, group_id)) | ||
39 | + raise Http404 | ||
40 | + | ||
41 | + | ||
42 | +def remove_user(request, group_id, user_id): | ||
43 | + # 그룹 사용자 삭제 | ||
44 | + if request.method == 'DELETE': | ||
45 | + return JsonResponse(groups.remove_user(request, group_id, user_id)) | ||
46 | + raise Http404 |
khubox-api/khubox/controllers/users.py
0 → 100644
1 | +from django.http import JsonResponse, Http404 | ||
2 | +from ..services import users | ||
3 | + | ||
4 | + | ||
5 | +def index(request): | ||
6 | + # 회원가입 | ||
7 | + if request.method == 'POST': | ||
8 | + return JsonResponse(users.create(request)) | ||
9 | + raise Http404 | ||
10 | + | ||
11 | + | ||
12 | +def login(request): | ||
13 | + # 로그인 | ||
14 | + if request.method == 'POST': | ||
15 | + return JsonResponse(users.login(request)) | ||
16 | + raise Http404 | ||
17 | + | ||
18 | + | ||
19 | +def me(request): | ||
20 | + # 회원정보 조회 | ||
21 | + if request.method == 'GET': | ||
22 | + return JsonResponse(users.find_me(request)) | ||
23 | + # 회원정보 수정 | ||
24 | + elif request.method == 'PATCH': | ||
25 | + return JsonResponse(users.update_me(request)) | ||
26 | + raise Http404 |
khubox-api/khubox/migrations/0001_initial.py
0 → 100644
1 | +# -*- coding: utf-8 -*- | ||
2 | +# Generated by Django 1.11.29 on 2020-06-07 17:28 | ||
3 | +from __future__ import unicode_literals | ||
4 | + | ||
5 | +from django.db import migrations, models | ||
6 | + | ||
7 | + | ||
8 | +class Migration(migrations.Migration): | ||
9 | + | ||
10 | + initial = True | ||
11 | + | ||
12 | + dependencies = [ | ||
13 | + ] | ||
14 | + | ||
15 | + operations = [ | ||
16 | + migrations.CreateModel( | ||
17 | + name='File', | ||
18 | + fields=[ | ||
19 | + ('id', models.CharField(max_length=36, primary_key=True, serialize=False)), | ||
20 | + ('parent_id', models.CharField(blank=True, max_length=36, null=True)), | ||
21 | + ('owner_user_id', models.IntegerField(blank=True, null=True)), | ||
22 | + ('owner_group_id', models.IntegerField(blank=True, null=True)), | ||
23 | + ('uploader_id', models.IntegerField(blank=True, null=True)), | ||
24 | + ('type', models.CharField(max_length=6)), | ||
25 | + ('name', models.CharField(max_length=255)), | ||
26 | + ('size', models.BigIntegerField()), | ||
27 | + ('is_public', models.IntegerField(default=0)), | ||
28 | + ('is_starred', models.IntegerField(default=0)), | ||
29 | + ('is_trashed', models.IntegerField(default=0)), | ||
30 | + ('created_at', models.DateTimeField()), | ||
31 | + ('deleted_at', models.DateTimeField(blank=True, null=True)), | ||
32 | + ], | ||
33 | + ), | ||
34 | + migrations.CreateModel( | ||
35 | + name='Group', | ||
36 | + fields=[ | ||
37 | + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
38 | + ('owner_id', models.IntegerField()), | ||
39 | + ('name', models.CharField(max_length=50)), | ||
40 | + ('root_folder', models.CharField(max_length=36)), | ||
41 | + ('invite_code', models.CharField(max_length=36)), | ||
42 | + ('created_at', models.DateTimeField()), | ||
43 | + ], | ||
44 | + ), | ||
45 | + migrations.CreateModel( | ||
46 | + name='GroupUser', | ||
47 | + fields=[ | ||
48 | + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
49 | + ('group_id', models.IntegerField()), | ||
50 | + ('user_id', models.IntegerField()), | ||
51 | + ('joined_at', models.DateTimeField()), | ||
52 | + ], | ||
53 | + ), | ||
54 | + migrations.CreateModel( | ||
55 | + name='User', | ||
56 | + fields=[ | ||
57 | + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
58 | + ('email', models.CharField(max_length=255)), | ||
59 | + ('password', models.CharField(max_length=60)), | ||
60 | + ('name', models.CharField(max_length=50)), | ||
61 | + ('root_folder', models.CharField(max_length=36)), | ||
62 | + ('created_at', models.DateTimeField()), | ||
63 | + ], | ||
64 | + ), | ||
65 | + ] |
1 | from django.db import models | 1 | from django.db import models |
2 | 2 | ||
3 | -# Create your models here. | 3 | + |
4 | +class File(models.Model): | ||
5 | + id = models.CharField(primary_key=True, max_length=36) | ||
6 | + parent_id = models.CharField(max_length=36, blank=True, null=True) | ||
7 | + owner_user_id = models.IntegerField(blank=True, null=True) | ||
8 | + owner_group_id = models.IntegerField(blank=True, null=True) | ||
9 | + uploader_id = models.IntegerField(blank=True, null=True) | ||
10 | + type = models.CharField(max_length=6) | ||
11 | + name = models.CharField(max_length=255) | ||
12 | + size = models.BigIntegerField() | ||
13 | + is_public = models.IntegerField(default=0) | ||
14 | + is_starred = models.IntegerField(default=0) | ||
15 | + is_trashed = models.IntegerField(default=0) | ||
16 | + created_at = models.DateTimeField() | ||
17 | + deleted_at = models.DateTimeField(blank=True, null=True) | ||
18 | + | ||
19 | + | ||
20 | +class Group(models.Model): | ||
21 | + owner_id = models.IntegerField() | ||
22 | + name = models.CharField(max_length=50) | ||
23 | + root_folder = models.CharField(max_length=36) | ||
24 | + invite_code = models.CharField(max_length=36) | ||
25 | + created_at = models.DateTimeField() | ||
26 | + | ||
27 | + | ||
28 | +class GroupUser(models.Model): | ||
29 | + group_id = models.IntegerField() | ||
30 | + user_id = models.IntegerField() | ||
31 | + joined_at = models.DateTimeField() | ||
32 | + | ||
33 | + | ||
34 | +class User(models.Model): | ||
35 | + email = models.CharField(max_length=255) | ||
36 | + password = models.CharField(max_length=60) | ||
37 | + name = models.CharField(max_length=50) | ||
38 | + root_folder = models.CharField(max_length=36) | ||
39 | + created_at = models.DateTimeField() | ... | ... |
khubox-api/khubox/services/files.py
0 → 100644
1 | +import json | ||
2 | +import uuid | ||
3 | +from django.conf import settings | ||
4 | +from django.utils import timezone | ||
5 | +from ..aws import sign_upload, sign_download, s3_copy, s3_delete | ||
6 | +from ..models import File, GroupUser | ||
7 | + | ||
8 | + | ||
9 | +# 폴더/파일 목록 | ||
10 | +def list_item(request): | ||
11 | + # TODO: Auth | ||
12 | + request.user_id = 1 | ||
13 | + | ||
14 | + # Validate | ||
15 | + if request.GET.get('is_public') != 'true' \ | ||
16 | + and request.GET.get('is_starred') != 'true' \ | ||
17 | + and request.GET.get('is_trashed') != 'true': | ||
18 | + return {'result': False, 'error': '입력이 누락되었습니다.'} | ||
19 | + | ||
20 | + # Query Files | ||
21 | + files = None | ||
22 | + if request.GET.get('is_public') == 'true': | ||
23 | + files = File.objects.filter(owner_user_id=request.user_id, is_public=1, deleted_at__isnull=True) | ||
24 | + elif request.GET.get('is_starred') == 'true': | ||
25 | + files = File.objects.filter(owner_user_id=request.user_id, is_starred=1, deleted_at__isnull=True) | ||
26 | + elif request.GET.get('is_trashed') == 'true': | ||
27 | + files = File.objects.filter(owner_user_id=request.user_id, is_trashed=1, deleted_at__isnull=True) | ||
28 | + | ||
29 | + # Structure | ||
30 | + data = [] | ||
31 | + for file in files: | ||
32 | + data.append({ | ||
33 | + 'id': file.id, | ||
34 | + 'type': file.type, | ||
35 | + 'name': file.name, | ||
36 | + 'size': file.size, | ||
37 | + 'is_public': file.is_public, | ||
38 | + 'is_starred': file.is_starred, | ||
39 | + 'is_trashed': file.is_trashed, | ||
40 | + 'created_at': file.created_at, | ||
41 | + }) | ||
42 | + | ||
43 | + return {'result': True, 'data': data} | ||
44 | + | ||
45 | + | ||
46 | +# 폴더 생성, 파일 업로드 | ||
47 | +def create(request): | ||
48 | + # TODO: Auth | ||
49 | + request.user_id = 1 | ||
50 | + | ||
51 | + # Load | ||
52 | + try: | ||
53 | + received = json.loads(request.body.decode('utf-8')) | ||
54 | + except json.decoder.JSONDecodeError: | ||
55 | + return {'result': False, 'error': '입력이 잘못되었습니다.'} | ||
56 | + | ||
57 | + # Validate | ||
58 | + if 'parent_id' not in received \ | ||
59 | + or 'type' not in received \ | ||
60 | + or 'name' not in received: | ||
61 | + return {'result': False, 'error': '입력이 누락되었습니다.'} | ||
62 | + if (received['type'] != 'folder' and received['type'] != 'file') \ | ||
63 | + or received['name'] == '': | ||
64 | + return {'result': False, 'error': '입력이 잘못되었습니다.'} | ||
65 | + | ||
66 | + # Get Parent | ||
67 | + parent = File.objects.filter(id=received['parent_id'], is_trashed=0, deleted_at__isnull=True) | ||
68 | + | ||
69 | + # Check Exists | ||
70 | + if len(parent) == 0: | ||
71 | + return {'result': False, 'error': '경로가 잘못되었습니다.'} | ||
72 | + | ||
73 | + # Check Owner | ||
74 | + is_auth = False | ||
75 | + if parent[0].owner_user_id == request.user_id: | ||
76 | + is_auth = True | ||
77 | + is_my_group = GroupUser.objects.filter(group_id=parent[0].owner_group_id, user_id=request.user_id) | ||
78 | + if len(is_my_group) != 0: | ||
79 | + is_auth = True | ||
80 | + if is_auth is False: | ||
81 | + return {'result': False, 'error': '경로가 잘못되었습니다.'} | ||
82 | + | ||
83 | + # Insert | ||
84 | + file_id = uuid.uuid4() | ||
85 | + File.objects.create( | ||
86 | + id=file_id, | ||
87 | + parent_id=received['parent_id'], | ||
88 | + owner_user_id=parent[0].owner_user_id, | ||
89 | + owner_group_id=parent[0].owner_group_id, | ||
90 | + uploader_id=request.user_id, | ||
91 | + type=received['type'], | ||
92 | + name=received['name'], | ||
93 | + size=0, | ||
94 | + created_at=timezone.now() | ||
95 | + ) | ||
96 | + | ||
97 | + # Return Folder | ||
98 | + if received['type'] == 'folder': | ||
99 | + return {'result': True, 'file_id': file_id} | ||
100 | + | ||
101 | + # Return File | ||
102 | + upload_url = sign_upload(str(file_id)) | ||
103 | + return {'result': True, 'file_id': file_id, 'upload_url': upload_url} | ||
104 | + | ||
105 | + | ||
106 | +# 휴지통 비우기 | ||
107 | +def empty_trash(request): | ||
108 | + # TODO: Auth | ||
109 | + request.user_id = 1 | ||
110 | + | ||
111 | + # Query Files | ||
112 | + files = File.objects.filter(owner_user_id=request.user_id, is_trashed=1, deleted_at__isnull=True) | ||
113 | + | ||
114 | + # First Depth | ||
115 | + del_list = [] | ||
116 | + del_check = [] | ||
117 | + for del_file in files: | ||
118 | + del_check.append(del_file.id) | ||
119 | + | ||
120 | + # Child Depth | ||
121 | + while True: | ||
122 | + if not del_check: | ||
123 | + break | ||
124 | + child_files = File.objects.filter(parent_id__in=del_check) | ||
125 | + del_list.extend(del_check) | ||
126 | + del_check.clear() | ||
127 | + for del_file in child_files: | ||
128 | + del_check.append(del_file.id) | ||
129 | + | ||
130 | + # S3 Delete | ||
131 | + s3_delete(del_list) | ||
132 | + | ||
133 | + # Update | ||
134 | + File.objects.filter(id__in=del_list).update(is_trashed=1, deleted_at=timezone.now()) | ||
135 | + | ||
136 | + return {'result': True, 'affected': del_list} | ||
137 | + | ||
138 | + | ||
139 | +# 폴더/파일 조회 | ||
140 | +def find_item(request, file_id): | ||
141 | + # TODO: Auth | ||
142 | + request.user_id = 1 | ||
143 | + | ||
144 | + # Query | ||
145 | + file = File.objects.filter(id=file_id, deleted_at__isnull=True) | ||
146 | + | ||
147 | + # Check Exists | ||
148 | + if len(file) == 0: | ||
149 | + return {'result': False, 'error': '잘못된 요청입니다.'} | ||
150 | + | ||
151 | + # Check Owner | ||
152 | + is_auth = False | ||
153 | + if file[0].owner_user_id == request.user_id: | ||
154 | + is_auth = True | ||
155 | + is_my_group = GroupUser.objects.filter(group_id=file[0].owner_group_id, user_id=request.user_id) | ||
156 | + if len(is_my_group) != 0: | ||
157 | + is_auth = True | ||
158 | + | ||
159 | + # Check Public | ||
160 | + if file[0].is_public == 1: | ||
161 | + is_auth = True | ||
162 | + parent_id = file[0].parent_id | ||
163 | + while True: | ||
164 | + if parent_id is None or is_auth: | ||
165 | + break | ||
166 | + parent_file = File.objects.filter(id=parent_id) | ||
167 | + if parent_file[0].is_public == 1: | ||
168 | + is_auth = True | ||
169 | + parent_id = parent_file[0].parent_id | ||
170 | + | ||
171 | + # Check Auth | ||
172 | + if is_auth is False: | ||
173 | + return {'result': False, 'error': '잘못된 요청입니다.'} | ||
174 | + | ||
175 | + # Return File | ||
176 | + if file[0].type == 'file': | ||
177 | + download_url = '%s/%s' % (settings.CDN_PATH, file[0].id) | ||
178 | + download_url = sign_download(download_url) | ||
179 | + data = { | ||
180 | + 'id': file[0].id, | ||
181 | + 'parent_id': file[0].parent_id, | ||
182 | + 'uploader_id': file[0].uploader_id, | ||
183 | + 'name': file[0].name, | ||
184 | + 'size': file[0].size, | ||
185 | + 'is_public': file[0].is_public, | ||
186 | + 'is_starred': file[0].is_starred, | ||
187 | + 'is_trashed': file[0].is_trashed, | ||
188 | + 'created_at': file[0].created_at, | ||
189 | + 'download_url': download_url, | ||
190 | + } | ||
191 | + return {'result': True, 'data': data} | ||
192 | + | ||
193 | + # Query | ||
194 | + files = File.objects.filter(parent_id=file[0].id, is_trashed=0, deleted_at__isnull=True) | ||
195 | + | ||
196 | + # Structure | ||
197 | + data = [] | ||
198 | + for file in files: | ||
199 | + data.append({ | ||
200 | + 'id': file.id, | ||
201 | + 'type': file.type, | ||
202 | + 'name': file.name, | ||
203 | + 'size': file.size, | ||
204 | + 'is_public': file.is_public, | ||
205 | + 'is_starred': file.is_starred, | ||
206 | + 'is_trashed': file.is_trashed, | ||
207 | + 'created_at': file.created_at, | ||
208 | + }) | ||
209 | + | ||
210 | + # Return Folder | ||
211 | + return {'result': True, 'data': data} | ||
212 | + | ||
213 | + | ||
214 | +# 폴더/파일 수정 | ||
215 | +def update_item(request, file_id): | ||
216 | + # TODO: Auth | ||
217 | + request.user_id = 1 | ||
218 | + | ||
219 | + # Load | ||
220 | + try: | ||
221 | + received = json.loads(request.body.decode('utf-8')) | ||
222 | + except json.decoder.JSONDecodeError: | ||
223 | + return {'result': False, 'error': '입력이 잘못되었습니다.'} | ||
224 | + | ||
225 | + # Validate | ||
226 | + if 'name' not in received \ | ||
227 | + and 'parent_id' not in received \ | ||
228 | + and 'is_public' not in received \ | ||
229 | + and 'is_starred' not in received \ | ||
230 | + and 'is_trashed' not in received: | ||
231 | + return {'result': False, 'error': '입력이 누락되었습니다.'} | ||
232 | + | ||
233 | + # Query | ||
234 | + file = File.objects.filter(id=file_id, deleted_at__isnull=True) | ||
235 | + | ||
236 | + # Check Exists | ||
237 | + if len(file) == 0: | ||
238 | + return {'result': False, 'error': '잘못된 요청입니다.'} | ||
239 | + | ||
240 | + # Check Owner | ||
241 | + is_auth = False | ||
242 | + if file[0].owner_user_id == request.user_id: | ||
243 | + is_auth = True | ||
244 | + is_my_group = GroupUser.objects.filter(group_id=file[0].owner_group_id, user_id=request.user_id) | ||
245 | + if len(is_my_group) != 0 \ | ||
246 | + and 'is_public' not in received \ | ||
247 | + and 'is_starred' not in received \ | ||
248 | + and 'is_trashed' not in received: | ||
249 | + is_auth = True | ||
250 | + | ||
251 | + # Check Parent | ||
252 | + if 'parent_id' in received: | ||
253 | + parent = File.objects.filter(id=received['parent_id'], type='folder', deleted_at__isnull=True) | ||
254 | + if len(parent) == 0: | ||
255 | + return {'result': False, 'error': '잘못된 요청입니다.'} | ||
256 | + if (is_auth is True or len(is_my_group) != 0) \ | ||
257 | + and parent[0].owner_user_id == file[0].owner_user_id \ | ||
258 | + and parent[0].owner_group_id == file[0].owner_group_id \ | ||
259 | + and file_id != received['parent_id']: | ||
260 | + is_auth = True | ||
261 | + else: | ||
262 | + is_auth = False | ||
263 | + | ||
264 | + # Check Auth | ||
265 | + if is_auth is False: | ||
266 | + return {'result': False, 'error': '잘못된 요청입니다.'} | ||
267 | + | ||
268 | + # Update | ||
269 | + if 'name' in received: | ||
270 | + file[0].name = received['name'] | ||
271 | + if 'parent_id' in received: | ||
272 | + file[0].parent_id = received['parent_id'] | ||
273 | + if 'is_public' in received: | ||
274 | + file[0].is_public = 1 if received['is_public'] is True else 0 | ||
275 | + if 'is_starred' in received: | ||
276 | + file[0].is_starred = 1 if received['is_starred'] is True else 0 | ||
277 | + if 'is_trashed' in received: | ||
278 | + if file[0].parent_id is None: | ||
279 | + return {'result': False, 'error': '잘못된 요청입니다.'} | ||
280 | + file[0].is_trashed = 1 if received['is_trashed'] is True else 0 | ||
281 | + file[0].save() | ||
282 | + | ||
283 | + return {'result': True} | ||
284 | + | ||
285 | + | ||
286 | +# 파일 복제 | ||
287 | +def copy(request, file_id): | ||
288 | + # TODO: Auth | ||
289 | + request.user_id = 1 | ||
290 | + | ||
291 | + # Get File | ||
292 | + file = File.objects.filter(id=file_id, type='file', is_trashed=0, deleted_at__isnull=True) | ||
293 | + | ||
294 | + # Check Exists | ||
295 | + if len(file) == 0: | ||
296 | + return {'result': False, 'error': '잘못된 요청입니다.'} | ||
297 | + | ||
298 | + # Check Owner | ||
299 | + is_auth = False | ||
300 | + if file[0].owner_user_id == request.user_id: | ||
301 | + is_auth = True | ||
302 | + is_my_group = GroupUser.objects.filter(group_id=file[0].owner_group_id, user_id=request.user_id) | ||
303 | + if len(is_my_group) != 0: | ||
304 | + is_auth = True | ||
305 | + if is_auth is False: | ||
306 | + return {'result': False, 'error': '경로가 잘못되었습니다.'} | ||
307 | + | ||
308 | + # Create UUID | ||
309 | + new_file_id = uuid.uuid4() | ||
310 | + | ||
311 | + # S3 Copy | ||
312 | + s3_copy(file_id, new_file_id) | ||
313 | + | ||
314 | + # Create | ||
315 | + File.objects.create( | ||
316 | + id=new_file_id, | ||
317 | + parent_id=file[0].parent_id, | ||
318 | + owner_user_id=file[0].owner_user_id, | ||
319 | + owner_group_id=file[0].owner_group_id, | ||
320 | + uploader_id=request.user_id, | ||
321 | + type=file[0].type, | ||
322 | + name='%s의 사본' % file[0].name, | ||
323 | + size=file[0].size, | ||
324 | + created_at=timezone.now() | ||
325 | + ) | ||
326 | + | ||
327 | + return {'result': True, 'file_id': file_id} |
khubox-api/khubox/services/groups.py
0 → 100644
1 | +# 그룹 생성 | ||
2 | +def create(request): | ||
3 | + return {'result': True} | ||
4 | + | ||
5 | + | ||
6 | +# 그룹 초대장 조회 | ||
7 | +def find_invite(request, invite_code): | ||
8 | + return {'result': True} | ||
9 | + | ||
10 | + | ||
11 | +# 그룹 초대장 사용 | ||
12 | +def use_invite(request, invite_code): | ||
13 | + return {'result': True} | ||
14 | + | ||
15 | + | ||
16 | +# 그룹 목록 | ||
17 | +def list_me(request): | ||
18 | + return {'result': True} | ||
19 | + | ||
20 | + | ||
21 | +# 그룹 조회 | ||
22 | +def find_item(request, group_id): | ||
23 | + return {'result': True} | ||
24 | + | ||
25 | + | ||
26 | +# 그룹 수정 | ||
27 | +def update_item(request, group_id): | ||
28 | + return {'result': True} | ||
29 | + | ||
30 | + | ||
31 | +# 그룹 삭제 | ||
32 | +def delete_item(request, group_id): | ||
33 | + return {'result': True} | ||
34 | + | ||
35 | + | ||
36 | +# 그룹 사용자 삭제 | ||
37 | +def remove_user(request, group_id, user_id): | ||
38 | + return {'result': True} |
khubox-api/khubox/services/users.py
0 → 100644
khubox-api/khubox/tests.py
deleted
100644 → 0
khubox-api/khubox/views.py
deleted
100644 → 0
... | @@ -2,9 +2,11 @@ argcomplete==1.11.1 | ... | @@ -2,9 +2,11 @@ argcomplete==1.11.1 |
2 | boto3==1.13.16 | 2 | boto3==1.13.16 |
3 | botocore==1.16.16 | 3 | botocore==1.16.16 |
4 | certifi==2020.4.5.1 | 4 | certifi==2020.4.5.1 |
5 | +cffi==1.14.0 | ||
5 | cfn-flip==1.2.3 | 6 | cfn-flip==1.2.3 |
6 | chardet==3.0.4 | 7 | chardet==3.0.4 |
7 | click==7.1.2 | 8 | click==7.1.2 |
9 | +cryptography==2.9.2 | ||
8 | Django==1.11.29 | 10 | Django==1.11.29 |
9 | docutils==0.15.2 | 11 | docutils==0.15.2 |
10 | durationpy==0.5 | 12 | durationpy==0.5 |
... | @@ -16,6 +18,7 @@ jmespath==0.10.0 | ... | @@ -16,6 +18,7 @@ jmespath==0.10.0 |
16 | -e git+http://khuhub.khu.ac.kr/2016104129/kappa.git@1b0e17bb6da7460d9d494828c682a5ef66736aa3#egg=kappa | 18 | -e git+http://khuhub.khu.ac.kr/2016104129/kappa.git@1b0e17bb6da7460d9d494828c682a5ef66736aa3#egg=kappa |
17 | pip-tools==5.1.2 | 19 | pip-tools==5.1.2 |
18 | placebo==0.9.0 | 20 | placebo==0.9.0 |
21 | +pycparser==2.20 | ||
19 | PyMySQL==0.9.3 | 22 | PyMySQL==0.9.3 |
20 | python-dateutil==2.6.1 | 23 | python-dateutil==2.6.1 |
21 | python-slugify==4.0.0 | 24 | python-slugify==4.0.0 | ... | ... |
-
Please register or login to post a comment