user.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import os.path
  2. from hashlib import sha1
  3. from peewee import IntegrityError
  4. from aiofiles.os import path
  5. from aiofiles.tempfile import NamedTemporaryFile
  6. from db import db
  7. from db import User, Video, Like, Tag, VideoTag
  8. from tools import is_valid_username, is_valid_password
  9. from tools import hash_password
  10. from tools import probe_image, create_thumbnail
  11. from tools import probe_video, create_preview
  12. from tools import parse_tags
  13. from tools import save_content
  14. from video import VideoError
  15. from video import serialize_video, find_video_by_id
  16. from config import config
  17. class LoginError(Exception): pass
  18. class SignupError(Exception): pass
  19. class UserError(Exception): pass
  20. async def find_user(username):
  21. try:
  22. return await db.get(User
  23. .select()
  24. .where(User.username == username))
  25. except User.DoesNotExist:
  26. raise UserError('account is disabled or doesn\'t exist')
  27. async def find_user_by_id(user_id):
  28. try:
  29. user_id = int(user_id)
  30. user_id = abs(user_id)
  31. except ValueError:
  32. raise UserError('illegal user_id')
  33. try:
  34. return await db.get(User
  35. .select()
  36. .where(User.id == user_id))
  37. except User.DoesNotExist:
  38. raise UserError('account is disabled or doesn\'t exist')
  39. async def create_user(username, password, ip_address='127.0.0.1'):
  40. try:
  41. return await db.create(User,
  42. ip_address=ip_address,
  43. username=username,
  44. password=hash_password(password))
  45. except IntegrityError:
  46. raise UserError('this username is already taken')
  47. async def get_authorized_user(session):
  48. if 'authorized' not in session:
  49. raise UserError('not authorized')
  50. return await find_user_by_id(session['authorized'])
  51. async def log_in(session, username, password):
  52. if 'authorized' in session:
  53. raise LoginError('already logged in')
  54. if not is_valid_username(username):
  55. raise LoginError('illegal username')
  56. if not is_valid_password(password):
  57. raise LoginError('illegal password')
  58. user = await find_user(username)
  59. hash = hash_password(password)
  60. if bytes(user.password) != hash:
  61. raise LoginError('illegal credentials')
  62. session['authorized'] = user.id
  63. return user
  64. async def sign_up(session, username, password, ip_address='127.0.0.1'):
  65. if 'authorized' in session:
  66. raise SignupError('already logged in')
  67. if not is_valid_username(username):
  68. raise SignupError('illegal username')
  69. if not is_valid_password(password):
  70. raise SignupError('illegal password')
  71. return await create_user(username, password, ip_address)
  72. async def log_out(session):
  73. if 'authorized' not in session:
  74. raise UserError('not authorized')
  75. del session['authorized']
  76. async def is_video_liked(session, video_id):
  77. user = await get_authorized_user(session)
  78. video = await find_video_by_id(video_id)
  79. try:
  80. await db.get(user
  81. .likes
  82. .select()
  83. .join(Video)
  84. .where(Video.id == video.id))
  85. except Like.DoesNotExist:
  86. return False
  87. return True
  88. async def like_video(session, video_id):
  89. user = await get_authorized_user(session)
  90. video = await find_video_by_id(video_id)
  91. try:
  92. await db.get(user
  93. .likes
  94. .select()
  95. .join(Video)
  96. .where(Video.id == video.id))
  97. except Like.DoesNotExist:
  98. await db.create(Like,
  99. video=video,
  100. user=user)
  101. return
  102. raise UserError('this video is already liked')
  103. async def unlike_video(session, video_id):
  104. user = await get_authorized_user(session)
  105. video = await find_video_by_id(video_id)
  106. try:
  107. await db.delete(
  108. await db.get(user
  109. .likes
  110. .select()
  111. .join(Video)
  112. .where(Video.id == video.id))
  113. )
  114. except Like.DoesNotExist:
  115. raise UserError('this video is not liked yet')
  116. async def upload_avatar(session, image):
  117. user = await get_authorized_user(session)
  118. if not image:
  119. raise UserError('not a valid image')
  120. async with NamedTemporaryFile('wb') as f:
  121. while True:
  122. chunk = await image.read_chunk()
  123. if not chunk:
  124. break
  125. await f.write(chunk)
  126. image_info = await probe_image(f.name)
  127. if not image_info:
  128. raise UserError('not a valid image')
  129. if image_info[1] not in config.ALLOWED_IMAGE_FORMATS:
  130. raise UserError('this image format is not allowed')
  131. width, height = map(int, image_info[2].split('x'))
  132. if width > 2048 or height > 2048:
  133. raise UserError('image size shouldn\'t exceed 2048 pixels for each dimension')
  134. await create_thumbnail(f.name, user.id, dimension=128)
  135. await create_thumbnail(f.name, user.id, dimension=64)
  136. async def upload_video(session, video, tags):
  137. user = await get_authorized_user(session)
  138. tags = parse_tags(tags)
  139. async with NamedTemporaryFile('w+b') as f:
  140. hash = sha1(video).hexdigest()
  141. await f.write(video)
  142. video_info = await probe_video(f.name)
  143. if not video_info:
  144. raise VideoError('not a valid video')
  145. format = video_info['format']
  146. format_name = format['format_name'].split(',')
  147. if 'webm' not in format_name:
  148. raise VideoError('not a WebM video')
  149. if float(format['duration']) > 15 * 60:
  150. raise VideoError('video duration shouldn\'t exceed 15 minutes')
  151. video_stream = video_info['streams'][0]
  152. if video_stream['width'] > 2048 or video_stream['height'] > 2048:
  153. raise VideoError('video\'s resolution shouldn\'t exceed 2048px for each dimension')
  154. web_path = await save_content(f, 'videos', filename=hash)
  155. path = os.path.join('static', 'videos')
  156. path = os.path.join(path, f'{hash}.webm')
  157. path = os.path.join('.', path)
  158. thumbnail = await create_preview(path)
  159. video = await db.create(Video,
  160. video=web_path,
  161. thumbnail=thumbnail,
  162. uploader=user)
  163. for tag in tags:
  164. tag = await db.get_or_create(Tag, tag=tag)
  165. await db.create(VideoTag,
  166. video=video,
  167. tag=tag[0])
  168. return video
  169. async def get_user_videos(user_id, offset=0):
  170. user = await find_user_by_id(user_id)
  171. videos = await db.execute(Video
  172. .select(Video, User)
  173. .join(User)
  174. .switch(Video)
  175. .where(
  176. (Video.uploader.id == user.id) &
  177. (~Video.is_hidden)
  178. )
  179. .order_by(Video.upload_date.desc())
  180. .offset(offset)
  181. .limit(6))
  182. return [await serialize_video(video) for video in videos]
  183. async def get_user_videos_count(user_id):
  184. user = await find_user_by_id(user_id)
  185. return await db.count(Video
  186. .select(Video, User)
  187. .join(User)
  188. .switch(Video)
  189. .where(Video.uploader.id == user.id))
  190. async def get_avatar(user_id, dimension=128):
  191. filename = f'{user_id}.{dimension}.png'
  192. file_path = os.path.join('static', 'avatars')
  193. file_path = os.path.join(file_path, filename)
  194. file_path = os.path.join('.', file_path)
  195. if await path.isfile(file_path):
  196. return f'/static/avatars/{filename}'
  197. return f'/static/avatars/default/default.{dimension}.png'
  198. async def serialize_user(user):
  199. return {
  200. 'avatar64': await get_avatar(user.id, dimension=64),
  201. 'avatar128': await get_avatar(user.id, dimension=128),
  202. 'signup_ts': int(user.signup_date.timestamp()),
  203. 'username': user.username
  204. }