Pārlūkot izejas kodu

Merge remote-tracking branch 'origin/backend-dev' into front-dev

SyuanYu 2 gadi atpakaļ
vecāks
revīzija
010e3383fc

+ 2 - 1
backend/app/app/api/api_v1/api.py

@@ -1,6 +1,6 @@
 from fastapi import APIRouter
 
-from app.api.api_v1.endpoints import  login, users, utils, videos, images, reputations
+from app.api.api_v1.endpoints import  login, users, utils, videos, images, reputations, ser_no
 
 api_router = APIRouter()
 api_router.include_router(login.router, tags=["login"])
@@ -9,3 +9,4 @@ api_router.include_router(utils.router, prefix="/utils", tags=["utils"])
 api_router.include_router(videos.router, prefix="/videos", tags=["videos"])
 api_router.include_router(images.router, prefix="/images", tags=["iamges"])
 api_router.include_router(reputations.router, prefix="/reputations", tags=["reputations"])
+api_router.include_router(ser_no.router, prefix="/ser_nos", tags=["serial numbers"])

+ 1 - 3
backend/app/app/api/api_v1/endpoints/images.py

@@ -24,7 +24,7 @@ LOCAL_ZIP_STORAGE = Path("/").joinpath(settings.LOCAL_ZIP_STORAGE)
 
 
 router = APIRouter()
-
+sr_clients = {}
 
 @router.post("/sr")
 async def supser_resolution(
@@ -106,8 +106,6 @@ def get_image(
 def del_image():
     pass
 
-sr_clients = {}
-
 @router.websocket("/sr")
 async def websocket_endpoint(websocket: WebSocket):
     await websocket.accept()

+ 36 - 8
backend/app/app/api/api_v1/endpoints/login.py

@@ -1,10 +1,10 @@
 from datetime import timedelta
-from typing import Any
+from typing import Any, Optional
 
 from fastapi import APIRouter, Body, Depends, HTTPException
 from fastapi.security import OAuth2PasswordRequestForm
 from sqlalchemy.orm import Session
-
+from datetime import datetime
 from app import crud, models, schemas
 from app.api import deps
 from app.core import security
@@ -22,9 +22,9 @@ from google.auth.transport import requests
 router = APIRouter()
 
 
-@router.post("/login/access-token", response_model=schemas.Token)
+@router.post("/login/access-token")#, response_model=schemas.Token)
 def login_access_token(
-    db: Session = Depends(deps.get_db), form_data: OAuth2PasswordRequestForm = Depends()
+    db: Session = Depends(deps.get_db), form_data: OAuth2PasswordRequestForm = Depends(), add_time_code: Optional[str] = None
 ) -> Any:
     """
     OAuth2 compatible token login, get an access token for future requests
@@ -37,16 +37,30 @@ def login_access_token(
     elif not crud.user.is_active(user):
         raise HTTPException(status_code=400, detail="Inactive user")
     access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
-    return {
+    return_msg = {
         "access_token": security.create_access_token(
             user.id, expires_delta=access_token_expires
         ),
         "token_type": "bearer",
     }
+    if add_time_code:
+        available_ser_no = crud.serial_number.available(db, ser_no=add_time_code)
+        print(available_ser_no)
+        if available_ser_no:
+            user_in = schemas.UserUpdate(available_time=user.available_time+available_ser_no.time)
+            crud.user.update(db, db_obj=user, obj_in=user_in)
+        
+            ser_no_in = schemas.SerialNumberUpdate(code=available_ser_no.code, is_used=True, used_datetime=str(datetime.now()), owner_id=user.id)
+            crud.serial_number.update(db, db_obj=available_ser_no, obj_in=ser_no_in)
+            print(available_ser_no.time, type(available_ser_no.time))
+            return_msg['time_added'] = available_ser_no.time
+        else:
+            return_msg['time_added'] = -1
+    return return_msg 
 
-@router.post("/login/google/access-token", response_model=schemas.Token)
+@router.post("/login/google/access-token")#, response_model=schemas.Token)
 def login_access_token(
-    db: Session = Depends(deps.get_db), form_data: OAuth2PasswordRequestForm = Depends()
+    db: Session = Depends(deps.get_db), form_data: OAuth2PasswordRequestForm = Depends(), add_time_code: Optional[str] = None
 ) -> Any:
     """
     OAuth2 compatible token login, get an access token for future requests
@@ -60,12 +74,26 @@ def login_access_token(
     elif not crud.user.is_active(user):
         raise HTTPException(status_code=400, detail="Inactive user")
     access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
-    return {
+    return_msg = {
         "access_token": security.create_access_token(
             user.id, expires_delta=access_token_expires
         ),
         "token_type": "bearer",
     }
+    if add_time_code:
+        available_ser_no = crud.serial_number.available(db, ser_no=add_time_code)
+        print(available_ser_no)
+        if available_ser_no:
+            user_in = schemas.UserUpdate(available_time=user.available_time+available_ser_no.time)
+            crud.user.update(db, db_obj=user, obj_in=user_in)
+        
+            ser_no_in = schemas.SerialNumberUpdate(code=available_ser_no.code, is_used=True, used_datetime=str(datetime.now()), owner_id=user.id)
+            crud.serial_number.update(db, db_obj=available_ser_no, obj_in=ser_no_in)
+            print(available_ser_no.time, type(available_ser_no.time))
+            return_msg['time_added'] = available_ser_no.time
+        else:
+            return_msg['time_added'] = -1
+    return return_msg 
 
 
 

+ 1 - 1
backend/app/app/api/api_v1/endpoints/reputations.py

@@ -36,6 +36,6 @@ async def post_reputation(
     """
     
     #print(posted_article)
-    article = crud.artivle.create_with_owner(db=db, obj_in=posted_article, owner_id=current_user.id, posted_datetime=str(datetime.now()))
+    article = crud.article.create_with_owner(db=db, obj_in=posted_article, owner_id=current_user.id, posted_datetime=str(datetime.now()))
     if article:
         return {"id":article.id}

+ 41 - 0
backend/app/app/api/api_v1/endpoints/ser_no.py

@@ -0,0 +1,41 @@
+from typing import Any, List
+from fastapi.responses import RedirectResponse
+from fastapi import APIRouter, Body, Depends, HTTPException, Request
+from fastapi.encoders import jsonable_encoder
+from pydantic.networks import EmailStr
+from sqlalchemy.orm import Session
+
+from app import crud, models, schemas
+from app.api import deps
+from app.core.config import settings
+
+from datetime import datetime
+
+router = APIRouter()
+
+@router.get("/add-time")
+def add_time(
+    *,
+    db: Session = Depends(deps.get_db),
+    current_user: models.User = Depends(deps.get_current_active_user),
+    ser_no: models.SerialNumber = Depends(deps.get_avairable_serial_number)
+)-> Any:
+
+  
+    user_in = schemas.UserUpdate(available_time=current_user.available_time+ser_no.time)
+    crud.user.update(db, db_obj=current_user, obj_in=user_in)
+    
+    ser_no_in = schemas.SerialNumberUpdate(code=ser_no.code, is_used=True, used_datetime=str(datetime.now()), owner_id=current_user.id)
+    crud.serial_number.update(db, db_obj=ser_no, obj_in=ser_no_in)
+
+    return {"time_added": ser_no.time}
+
+@router.get("/add-time-no-token")
+def add_time_no_token(
+    request: Request,
+    ser_no: models.SerialNumber = Depends(deps.get_avairable_serial_number)
+)-> Any:
+    print(f"{request.base_url}login?ser_no={ser_no.code}")
+    #return RedirectResponse(f"{request.base_url}login?ser_no={ser_no.code}")
+    return RedirectResponse(f"http://localhost:5173/login?add-time-code={ser_no.code}")
+

+ 105 - 10
backend/app/app/api/api_v1/endpoints/videos.py

@@ -1,10 +1,10 @@
 from typing import Any, List, Optional
 import subprocess
 from fastapi import UploadFile, File, Form
-from fastapi.responses import FileResponse
-from fastapi import APIRouter, Depends, HTTPException
+from fastapi.responses import FileResponse, JSONResponse
+from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, WebSocket
 from sqlalchemy.orm import Session
-
+from fastapi.encoders import jsonable_encoder
 import app.crud as crud
 import app.models as models
 import app.schemas as schemas 
@@ -13,14 +13,16 @@ from app.api import deps
 from app.core.celery_app import celery_app
 from app.core.config import settings
 from pathlib import Path
+from app.db.session import SessionLocal
 
-from app.core.celery_app import celery_app
+import asyncio
 
 BACKEND_ZIP_STORAGE = Path("/app").joinpath(settings.BACKEND_ZIP_STORAGE)
 LOCAL_ZIP_STORAGE = Path("/").joinpath(settings.LOCAL_ZIP_STORAGE)
 
 
 router = APIRouter()
+video_clients = {}
 
 @router.get("/", response_model=List[schemas.Video])
 def get_video_list(
@@ -40,6 +42,20 @@ def get_video_list(
         )
     return videos
 
+@router.post("/test")
+def test(
+    *,
+    title: str,
+    anchor_id: int,
+    lang_id: int,
+    current_user: models.User = Depends(deps.get_current_active_user),
+) -> Any:
+    video_data = {"title":title, "anchor_id":anchor_id, "lang_id":lang_id}
+    print(video_data)
+    task = celery_app.send_task("app.worker.make_video_test",  kwargs=video_data, )
+    print(task)
+    return "ok"
+
 @router.post("/", response_model=schemas.Video)
 def upload_plot(
     *,
@@ -49,18 +65,17 @@ def upload_plot(
     lang_id: int=Form(...),
     upload_file: UploadFile=File(),
     current_user: models.User = Depends(deps.get_current_active_user),
+    background_tasks: BackgroundTasks,
 ) -> Any:
     """
     Create new video.
     """
     print(title)
     print(upload_file.filename)
-    file_name = crud.video.generate_file_name(db=db, n=20)
-    video_create = schemas.VideoCreate(title=title, progress_state="waiting", stored_file_name=file_name)
-    video = crud.video.create_with_owner(db=db, obj_in=video_create, owner_id=current_user.id)
-
+    filename = crud.video.generate_file_name(db=db, n=20)
+    
     try:
-        with open(str(Path(BACKEND_ZIP_STORAGE).joinpath(video.stored_file_name+".zip")), 'wb') as f:
+        with open(str(Path(BACKEND_ZIP_STORAGE).joinpath(filename+".zip")), 'wb') as f:
             while contents := upload_file.file.read(1024 * 1024):
                 f.write(contents)
     except Exception as e:
@@ -68,13 +83,75 @@ def upload_plot(
         return {"error": str(e)}
     finally:
         upload_file.file.close()
+    
+
+    '''
     zip_filename = video.stored_file_name+".zip"
     print(str(BACKEND_ZIP_STORAGE/zip_filename))
     r = subprocess.run(["sshpass", "-p", "choozmo9", 
                     "scp", "-P", "5722", "-o", "StrictHostKeyChecking=no", f"{str(BACKEND_ZIP_STORAGE/zip_filename)}", f"root@172.104.93.163:{str(LOCAL_ZIP_STORAGE/zip_filename)}"])
     print(r.returncode)
     celery_app.send_task("app.worker.make_video", args=[video.id, video.stored_file_name, current_user.id, anchor_id, current_user.membership_status, current_user.available_time])
-    return video
+    '''
+    video_create = schemas.VideoCreate(title=title, progress_state="PENDING", stored_filename=filename)
+    video = crud.video.create_with_owner(db=db, obj_in=video_create, owner_id=current_user.id)
+    return_msg = {"video_message":"accepted"}
+    video_data = jsonable_encoder(video)
+    video_data['membership_status'] = current_user.membership_status
+    video_data['available_time'] = current_user.available_time
+    video_data['video_id'] = video_data['id']
+    background_tasks.add_task(wait_finish, video_data)
+    return  JSONResponse(return_msg, background=background_tasks)
+
+async def wait_finish(video_data:dict): 
+    zip_filename = video_data['stored_filename']+".zip"
+    process = await asyncio.create_subprocess_exec("sshpass", "-p", "choozmo9", 
+                    "scp", "-P", "5722", "-o", "StrictHostKeyChecking=no", f"{str(BACKEND_ZIP_STORAGE/zip_filename)}", f"root@172.104.93.163:{str(LOCAL_ZIP_STORAGE)}")
+    await process.wait()
+
+    task = celery_app.send_task("app.worker.make_video", kwargs=video_data)
+    while True:
+       await asyncio.sleep(1)
+       if task.state != "PENDING":
+           break
+       
+    db = SessionLocal()
+    video = db.query(models.Video).get(video_data['id'])
+    video.progress_state = "STARTED" 
+    db.commit()
+    db.close()
+    msg_data = f"{video_data['stored_filename']}:STARTED"
+    await publish(msg_data)
+
+    while True:
+        await asyncio.sleep(1)
+        if task.state != "STARTED":
+            break
+
+    if task.state == "SUCCESS":
+        db = SessionLocal()
+        video = db.query(models.Video).get(video_data['id'])
+        user = db.query(models.User).get(video_data['owner_id'])
+        video.progress_state = "SUCCESS" 
+        if task.result:
+            user.available_time -= int(task.result)
+        db.commit()
+        db.close()
+        msg_data = f"{video_data['stored_filename']}:SUCCESS"
+
+    elif task.state == "FAILURE":
+        db = SessionLocal()
+        video = db.query(models.Video).get(video_data['id'])
+        video.progress_state = "FAILURE" 
+        db.commit()
+        db.close()
+        msg_data = f"{video_data['stored_filename']}:FAILURE"
+
+    await publish(msg_data)
+
+async def publish(data):
+    for video_client in video_clients.values():
+        await video_client.send_text(f"{data}")
 
 @router.get("/{id}")
 def download_video(
@@ -86,3 +163,21 @@ def download_video(
     
     return {"message":"address"}
 
+@router.websocket("")
+async def websocket_endpoint(websocket: WebSocket):
+    await websocket.accept()
+    key = websocket.headers.get('sec-websocket-key')
+    video_clients[key] = websocket
+    try:
+        while True:
+            data = await websocket.receive_text()
+            if not data.startswith("subscribe"):
+              del video_clients[key]
+              #for client in sr_clients.values():
+              #      await client.send_text(f"ID: {key} | Message: {data}")
+
+    except:
+        # 接続が切れた場合、当該クライアントを削除する
+        del video_clients[key]
+
+

+ 12 - 0
backend/app/app/api/deps.py

@@ -59,3 +59,15 @@ def get_current_active_superuser(
             status_code=400, detail="The user doesn't have enough privileges"
         )
     return current_user
+
+def get_valid_serial_number(code: str, db: Session = Depends(get_db)):
+    ser_no = db.query(models.SerialNumber).filter(models.SerialNumber.code==code).first()
+    if not ser_no:
+        raise HTTPException(status_code=400, detail="This serial number is invalid")
+    
+    return ser_no
+
+def get_avairable_serial_number(ser_no:models.SerialNumber = Depends(get_valid_serial_number)):
+    if ser_no.is_used:
+        raise HTTPException(status_code=400, detail="This serial number is already used")
+    return ser_no

+ 1 - 1
backend/app/app/core/celery_app.py

@@ -4,4 +4,4 @@ celery_app = Celery("worker", broker="redis://172.104.93.163:16379/0", backend="
 
 
 
-celery_app.conf.task_routes = {"app.worker.make_video": "main-queue", "app.worker.super_resolution": "main-queue"}
+celery_app.conf.task_routes = {"app.worker.make_video": "video", "app.worker.super_resolution": "image", "app.worker.make_video_test": "video"}

+ 1 - 0
backend/app/app/core/celeryconf.py

@@ -0,0 +1 @@
+task_track_started = True

+ 2 - 1
backend/app/app/crud/__init__.py

@@ -1,6 +1,7 @@
 from .crud_user import user
 from .crud_video import video
-from .crud_article import artivle
+from .crud_article import article
+from .crud_ser_no import serial_number
 # For a new basic set of CRUD operations you could just do
 
 # from .base import CRUDBase

+ 1 - 1
backend/app/app/crud/crud_article.py

@@ -20,4 +20,4 @@ class CRUDArticle(CRUDBase[Article, ArticleCreate, ArticleUpdate]):
         db.refresh(db_obj)
         return db_obj
     
-artivle = CRUDArticle(Article)
+article = CRUDArticle(Article)

+ 25 - 0
backend/app/app/crud/crud_ser_no.py

@@ -0,0 +1,25 @@
+from typing import List, Optional
+
+from fastapi.encoders import jsonable_encoder
+from sqlalchemy.orm import Session
+
+from app.crud.base import CRUDBase
+from app.models.serial_number import SerialNumber
+from app.schemas.serial_number import SerialNumberCreate, SerialNumberUpdate
+
+from app.utils import random_name
+
+class CRUDSerialNumber(CRUDBase[SerialNumber, SerialNumberCreate, SerialNumberUpdate]):
+    
+    def valid(self, db: Session, *, ser_no:str) -> Optional[SerialNumber]:
+        return db.query(SerialNumber).filter(SerialNumber.code==ser_no).first()
+
+    def available(self, db: Session, *, ser_no:str) -> Optional[SerialNumber]:
+        valid_ser_no = self.valid(db, ser_no=ser_no)
+        if valid_ser_no and not valid_ser_no.is_used:
+            return valid_ser_no
+        else:
+            return None
+        
+    
+serial_number = CRUDSerialNumber(SerialNumber)

+ 3 - 3
backend/app/app/crud/crud_video.py

@@ -35,9 +35,9 @@ class CRUDVideo(CRUDBase[Video, VideoCreate, VideoUpdate]):
       self, db: Session, *, n:int
     ) -> bool:
         while True:
-            file_name = random_name(n)
-            if not db.query(self.model).filter(Video.stored_file_name==file_name).first():
-              return file_name
+            filename = random_name(n)
+            if not db.query(self.model).filter(Video.stored_filename==filename).first():
+              return filename
 
 
 video = CRUDVideo(Video)

+ 2 - 0
backend/app/app/db/base.py

@@ -6,3 +6,5 @@ from app.models.video import Video
 from app.models.enum import Progress, Membership
 from app.models.character import Character
 from app.models.article import Article
+from app.models.serial_number import SerialNumber
+from app.models.voice import Voice

+ 3 - 1
backend/app/app/models/__init__.py

@@ -1,4 +1,6 @@
 from .user import User
 from .video import Video
 from .enum import Membership, Progress
-from .article import Article
+from .article import Article
+from .serial_number import SerialNumber
+from .voice import Voice

+ 14 - 0
backend/app/app/models/serial_number.py

@@ -0,0 +1,14 @@
+from typing import TYPE_CHECKING
+
+from sqlalchemy import Column, ForeignKey, Integer, String, DateTime, Boolean
+
+from app.db.base_class import Base
+
+class SerialNumber(Base):
+  __tablename__ = "serial_number"
+  code = Column(String(length=20), primary_key=True)
+  time = Column(Integer, nullable=False)
+  created_datetime = Column(DateTime)
+  is_used = Column(Boolean, nullable=False, default=False)
+  used_datetime = Column(DateTime)
+  owner_id = Column(Integer, ForeignKey("user.id"))

+ 1 - 1
backend/app/app/models/video.py

@@ -13,7 +13,7 @@ if TYPE_CHECKING:
 class Video(Base):
   id = Column(Integer, primary_key=True, index=True)
   title = Column(String(20), index=True, nullable=False)
-  stored_file_name = Column(String(30), unique=True, nullable=False)
+  stored_filename = Column(String(30), unique=True, nullable=False)
   progress_state = Column(String(10), 
                     ForeignKey("progress.state", ondelete="RESTRICT", onupdate="CASCADE"),
                     default="waiting")

+ 10 - 0
backend/app/app/models/voice.py

@@ -0,0 +1,10 @@
+from typing import TYPE_CHECKING
+
+from sqlalchemy import Column, ForeignKey, Integer, String, DateTime, Boolean
+
+from app.db.base_class import Base
+
+class Voice(Base):
+  id = Column(Integer, primary_key=True, index=True,)
+  name = Column(String(30))
+  play_id = Column(String(30))

+ 1 - 0
backend/app/app/schemas/__init__.py

@@ -3,3 +3,4 @@ from .user import User, UserCreate, UserInDB, UserUpdate
 from .msg import Msg
 from .video import Video, VideoCreate, VideoInDB, VideoUpdate
 from .article import ArticleBase, ArticleCreate, ArticleUpdate
+from .serial_number import SerialNumberBase, SerialNumberCreate, SerialNumberUpdate

+ 21 - 0
backend/app/app/schemas/serial_number.py

@@ -0,0 +1,21 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+
+
+class SerialNumberBase(BaseModel):
+    code: Optional[str] = None
+    time: Optional[int] = None
+    created_datetime: Optional[str] = None
+    is_used: Optional[bool] = False
+    used_datetime: Optional[str] = None
+
+# Properties to receive via API on creation
+class SerialNumberCreate(SerialNumberBase):
+    pass
+
+
+# Properties to receive via API on update
+class SerialNumberUpdate(SerialNumberBase):
+    owner_id: int

+ 1 - 0
backend/app/app/schemas/user.py

@@ -19,6 +19,7 @@ class UserCreate(UserBase):
 # Properties to receive via API on update
 class UserUpdate(UserBase):
     password: Optional[str] = None
+    available_time: Optional[int] = None
 
 
 class UserInDBBase(UserBase):

+ 2 - 2
backend/app/app/schemas/video.py

@@ -8,13 +8,13 @@ from fastapi import UploadFile, File
 class VideoBase(BaseModel):
     title: Optional[str] = None
     progress_state: Optional[str] = None
-    stored_file_name: Optional[str] = None
+    stored_filename: Optional[str] = None
 
 # Properties to receive on video creation
 class VideoCreate(VideoBase):
     title: str
     progress_state: str
-    stored_file_name: str
+    stored_filename: str
 
 # Properties to receive on video update
 class VideoUpdate(VideoBase):