서승완

Merge branch 'feature/backend' into 'master'

Feature/backend



See merge request !4
...@@ -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 ]
......
1 -from django.contrib import admin
2 -
3 -# Register your models here.
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)
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 +]
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
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
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
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()
......
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}
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}
1 +# 회원가입
2 +def create(request):
3 + return {'result': True}
4 +
5 +
6 +# 로그인
7 +def login(request):
8 + return {'result': True}
9 +
10 +
11 +# 회원정보 조회
12 +def find_me(request):
13 + return {'result': True}
14 +
15 +
16 +# 회원정보 수정
17 +def update_me(request):
18 + return {'result': True}
1 -from django.test import TestCase
2 -
3 -# Create your tests here.
1 -from django.shortcuts import render
2 -
3 -# Create your views here.
...@@ -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
......