conrad vor 1 Jahr
Commit
a14938dadc
40 geänderte Dateien mit 1578 neuen und 0 gelöschten Zeilen
  1. 8 0
      .idea/.gitignore
  2. 8 0
      .idea/fastdemo.iml
  3. 6 0
      .idea/inspectionProfiles/Project_Default.xml
  4. 6 0
      .idea/inspectionProfiles/profiles_settings.xml
  5. 4 0
      .idea/misc.xml
  6. 8 0
      .idea/modules.xml
  7. 6 0
      .idea/vcs.xml
  8. BIN
      __pycache__/main.cpython-38.pyc
  9. 25 0
      app/__init__.py
  10. BIN
      app/__pycache__/__init__.cpython-38.pyc
  11. BIN
      app/__pycache__/config.cpython-38.pyc
  12. BIN
      app/__pycache__/session.cpython-38.pyc
  13. 10 0
      app/api/__init__.py
  14. BIN
      app/api/__pycache__/__init__.cpython-38.pyc
  15. BIN
      app/api/__pycache__/classes.cpython-38.pyc
  16. BIN
      app/api/__pycache__/deps.cpython-38.pyc
  17. BIN
      app/api/__pycache__/users.cpython-38.pyc
  18. 349 0
      app/api/classes.py
  19. 21 0
      app/api/deps.py
  20. 162 0
      app/api/users.py
  21. 34 0
      app/config.py
  22. 24 0
      app/email-templates/reset_password.html
  23. BIN
      app/models/__pycache__/models.cpython-38.pyc
  24. 23 0
      app/models/models.py
  25. 12 0
      app/session.py
  26. 1 0
      app/templates/index.html
  27. BIN
      app/templates/ntcri_backstage/.DS_Store
  28. 167 0
      app/templates/ntcri_backstage/add-class.html
  29. 133 0
      app/templates/ntcri_backstage/class-list.html
  30. 167 0
      app/templates/ntcri_backstage/css/style.css
  31. 2 0
      app/templates/ntcri_backstage/css/style.css.map
  32. 164 0
      app/templates/ntcri_backstage/css/style.scss
  33. BIN
      app/templates/ntcri_backstage/img/.DS_Store
  34. BIN
      app/templates/ntcri_backstage/img/logo.png
  35. 67 0
      app/templates/ntcri_backstage/js/class-list.js
  36. 117 0
      app/templates/ntcri_backstage/js/index.js
  37. 44 0
      main.py
  38. 6 0
      ntcri_restart.sh
  39. 4 0
      readme.txt
  40. BIN
      requirements.txt

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 8 - 0
.idea/fastdemo.iml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+  </profile>
+</component>

+ 6 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 4 - 0
.idea/misc.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9(all)" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/fastdemo.iml" filepath="$PROJECT_DIR$/.idea/fastdemo.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

BIN
__pycache__/main.cpython-38.pyc


+ 25 - 0
app/__init__.py

@@ -0,0 +1,25 @@
+from fastapi import FastAPI
+from tortoise.contrib.fastapi import register_tortoise
+
+def create_app() -> FastAPI:
+    app = FastAPI()
+
+    register_rotuer(app)
+    register_db(app)
+    return app
+
+
+def register_rotuer(app: FastAPI) -> None:
+    """注册路由"""
+    from app.api import routers
+    app.include_router(routers)
+
+
+def register_db(app: FastAPI) -> None:
+    """"初始化数据库并根据数据模型生成对应的表"""
+    register_tortoise(
+            app,
+            db_url="mysql://choozmo:pAssw0rd@db.ptt.cx:3306/test",
+            modules={"models": ["app.models.models"]},# 这是一个列表用来指明我们的模型文件的路径
+            generate_schemas=True,
+            add_exception_handlers=True)

BIN
app/__pycache__/__init__.cpython-38.pyc


BIN
app/__pycache__/config.cpython-38.pyc


BIN
app/__pycache__/session.cpython-38.pyc


+ 10 - 0
app/api/__init__.py

@@ -0,0 +1,10 @@
+from fastapi import APIRouter
+
+from app.api.users import users
+from app.api.classes import classes
+
+routers = APIRouter()
+
+
+routers.include_router(users, prefix="/api", tags=["登入/註冊"])
+routers.include_router(classes, prefix="/api", tags=["課程列表"])

BIN
app/api/__pycache__/__init__.cpython-38.pyc


BIN
app/api/__pycache__/classes.cpython-38.pyc


BIN
app/api/__pycache__/deps.cpython-38.pyc


BIN
app/api/__pycache__/users.cpython-38.pyc


+ 349 - 0
app/api/classes.py

@@ -0,0 +1,349 @@
+from fastapi import APIRouter, Form, Depends, HTTPException
+from fastapi.security import OAuth2PasswordRequestForm
+from app.models.models import User
+from app.models.models import Class_list
+from app.api import deps
+from sqlalchemy.orm import Session
+from typing import Any, Dict
+import secrets
+from fastapi_login.exceptions import InvalidCredentialsException
+from fastapi_login import LoginManager
+from datetime import timedelta,datetime
+from app.config import settings
+from pathlib import Path
+from jose import jwt
+import emails
+from emails.template import JinjaTemplate
+import logging
+from tortoise.queryset import Q
+
+classes = APIRouter()
+
+# SECRET: str = secrets.token_urlsafe(32)
+# manager = LoginManager(SECRET, '/login',default_expiry=timedelta(hours=72))
+
+
+# @manager.user_loader()
+# async def query_user(user_id: str):
+#     """
+#     Get a user from the db
+#     :param user_id: E-Mail of the user
+#     :return: None or the user object
+#     """
+#     result = await User.filter(username=user_id).first()
+#     if not result:
+#         print('[]')
+#         return []
+#     return result
+#     # return DB['users'].get(user_id)
+@classes.post("/insert_class")
+async def insert_class(
+    id: int = Form(default=0),
+    name: str = Form(default=''),
+    start_time: datetime = Form(default=datetime.now()),
+    end_time: datetime = Form(default=datetime.now()),
+    location: str = Form(default=''),
+    lecturer: str = Form(default=''),
+    organizer: str = Form(default=''),
+    contact: str = Form(default=''),
+    introduction: str = Form(default=''),
+    content: str = Form(default=''),
+    cover_img: str = Form(default='')
+
+):
+    try:
+        new_class = await Class_list.create(
+            id=id,
+            name=name,
+            start_time=start_time,
+            end_time=end_time,
+            location=location,
+            lecturer=lecturer,
+            organizer=organizer,
+            contact=contact,
+            introduction=introduction,
+            content=content,
+            cover_img=cover_img
+        )
+        return {"msg": "success", "code": 200, "class_id": new_class.id}
+    except Exception as e:
+        return {"msg": str(e), "code": 500}
+
+# @classes.post("/update_class")
+# async def update_class(
+#     id: int = Form(default=0),
+#     name: str = Form(default=''),
+#     start_time: datetime = Form(default=datetime.now()),
+#     end_time: datetime = Form(default=datetime.now()),
+#     location: str = Form(default=''),
+#     lecturer: str = Form(default=''),
+#     organizer: str = Form(default=''),
+#     contact: str = Form(default=''),
+#     introduction: str = Form(default=''),
+#     content: str = Form(default='')
+# ):
+#     try:
+#         await Class_list.filter(id=id).update(
+#             name=name,
+#             start_time=start_time,
+#             end_time=end_time,
+#             location=location,
+#             lecturer=lecturer,
+#             organizer=organizer,
+#             contact=contact,
+#             introduction=introduction,
+#             content=content
+#         )
+#         return {"msg": "success", "code": 200}
+#     except Exception as e:
+#         return {"msg": str(e), "code": 500}
+
+@classes.post("/update_class")
+async def update_class(
+    id: int = Form(default=0),
+    name: str = Form(default=''),
+    start_time: datetime = Form(default=datetime.now()),
+    end_time: datetime = Form(default=datetime.now()),
+    location: str = Form(default=''),
+    lecturer: str = Form(default=''),
+    organizer: str = Form(default=''),
+    contact: str = Form(default=''),
+    introduction: str = Form(default=''),
+    content: str = Form(default=''),
+    cover_img: str = Form(default=''),
+):
+    try:
+        class_obj = await Class_list.get(id=id)
+
+        if name.strip() != '':
+            class_obj.name = name
+
+        if start_time:
+            class_obj.start_time = start_time
+
+        if end_time:
+            class_obj.end_time = end_time
+
+        if location.strip() != '':
+            class_obj.location = location
+
+        if lecturer.strip() != '':
+            class_obj.lecturer = lecturer
+
+        if organizer.strip() != '':
+            class_obj.organizer = organizer
+
+        if contact.strip() != '':
+            class_obj.contact = contact
+
+        if introduction.strip() != '':
+            class_obj.introduction = introduction
+
+        if content.strip() != '':
+            class_obj.content = content
+
+        if cover_img.strip() != '':
+            class_obj.cover_img = cover_img
+
+
+        await class_obj.save()
+        return {"msg": "success", "code": 200}
+    except Exception as e:
+        return {"msg": str(e), "code": 500}
+
+@classes.post("/delete_class")
+async def delete(id: int):
+    if id:
+        await Class_list.filter(id=id).delete()
+        return {"msg": "success", "code": 200}
+
+
+@classes.get("/search_class")
+async def search_class(id: int):
+    try:
+        class_obj = await Class_list.get(id=id)
+        return {
+            "msg": "success",
+            "code": 200,
+            "class_id": class_obj.id,
+            "name": class_obj.name,
+            "start_time": class_obj.start_time,
+            "end_time": class_obj.end_time,
+            "location": class_obj.location,
+            "lecturer": class_obj.lecturer,
+            "organizer": class_obj.organizer,
+            "contact": class_obj.contact,
+            "introduction": class_obj.introduction,
+            "content": class_obj.content,
+            "cover_img": class_obj.cover_img,
+
+        }
+    except Exception as e:
+        return {"msg": str(e), "code": 500}
+
+@classes.get("/get_class")
+async def get_class():
+    try:
+        class_list = await Class_list.all()
+        classes = []
+        for class_obj in class_list:
+            class_data = {
+                "class_id": class_obj.id,
+                "name": class_obj.name,
+                "start_time": class_obj.start_time,
+                "end_time": class_obj.end_time,
+                "location": class_obj.location,
+                "lecturer": class_obj.lecturer,
+                "organizer": class_obj.organizer,
+                "contact": class_obj.contact,
+                "introduction": class_obj.introduction,
+                "content": class_obj.content,
+                "cover_img": class_obj.cover_img
+            }
+            classes.append(class_data)
+
+        return {"msg": "success", "code": 200, "classes": classes}
+    except Exception as e:
+        return {"msg": str(e), "code": 500}
+@classes.get("/search_class_like")
+async def search_class_like(keyword: str):
+    try:
+        class_list = await Class_list.filter(
+Q(name__icontains=keyword) | Q(lecturer__icontains=keyword)
+        ).all()
+
+        classes = []
+        for class_obj in class_list:
+            class_data = {
+                "class_id": class_obj.id,
+                "name": class_obj.name,
+                "start_time": class_obj.start_time,
+                "end_time": class_obj.end_time,
+                "location": class_obj.location,
+                "lecturer": class_obj.lecturer,
+                "organizer": class_obj.organizer,
+                "contact": class_obj.contact,
+                "introduction": class_obj.introduction,
+                "content": class_obj.content,
+		 "cover_img": class_obj.cover_img
+
+            }
+            classes.append(class_data)
+
+        return {"msg": "success", "code": 200, "classes": classes}
+    except Exception as e:
+        return {"msg": str(e), "code": 500}
+
+
+# @classes.post("/login")
+# async def login(data: OAuth2PasswordRequestForm = Depends()):
+#     username = data.username
+#     password = data.password
+
+#     user = await query_user(username)
+#     print(user)
+
+#     if not user:
+#         # you can return any response or error of your choice
+#         raise InvalidCredentialsException
+#     elif password != user.password:
+#         raise InvalidCredentialsException
+#     access_token = manager.create_access_token(
+#         data={'sub': username}
+#     )
+#     return {'access_token': access_token}
+
+
+# @classes.post("/logout")
+# async def logout():
+
+#     return {"msg":"logout success","code":200}
+
+# @classes.post("/add")
+# async def add(username: str = Form(default=''), password: str = Form(default=''), email: str = Form(default='')):
+#     if username and password and email:
+#         u = await User.create(username=username, password=password, email=email)
+#         if u:
+#             send_email()
+#             return {"msg": "已寄送認證信", "code": 200}
+
+#     return {"msg": "create user failed", "code": 403}
+
+
+# def generate_password_reset_token(email: str) -> str:
+#     delta = timedelta(hours=settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS)
+#     now = datetime.utcnow()
+#     expires = now + delta
+#     exp = expires.timestamp()
+#     encoded_jwt = jwt.encode(
+#         {"exp": exp, "nbf": now, "sub": email}, settings.SECRET_KEY, algorithm="HS256",
+#     )
+#     return encoded_jwt
+
+
+# def send_email(
+#     email_to: str,
+#     subject_template: str = "",
+#     html_template: str = "",
+#     environment: Dict[str, Any] = {},
+# ) -> None:
+#     # assert settings.EMAILS_ENABLED, "no provided configuration for email variables"
+#     message = emails.Message(
+#         subject=JinjaTemplate(subject_template),
+#         html=JinjaTemplate(html_template),
+#         mail_from=(settings.EMAILS_FROM_NAME, settings.EMAILS_FROM_EMAIL),
+#     )
+#     smtp_options = {"host": settings.SMTP_HOST, "port": settings.SMTP_PORT}
+#     if settings.SMTP_TLS:
+#         smtp_options["tls"] = True
+#     if settings.SMTP_USER:
+#         smtp_options["user"] = settings.SMTP_USER
+#     if settings.SMTP_PASSWORD:
+#         smtp_options["password"] = settings.SMTP_PASSWORD
+#     response = message.send(to=email_to, render=environment, smtp=smtp_options)
+#     logging.info(f"send email result: {response}")
+
+
+# def send_reset_password_email(email_to: str, email: str, token: str) -> None:
+#     subject = f"Password recovery for user {email}"
+#     with open(Path(settings.EMAIL_TEMPLATES_DIR) / "reset_password.html") as f:
+#         template_str = f.read()
+#     server_host = settings.SERVER_HOST
+#     link = f"{server_host}/reset-password?token={token}"
+#     send_email(
+#         email_to=email_to,
+#         subject_template=subject,
+#         html_template=template_str,
+#         environment={
+#             # "project_name": settings.PROJECT_NAME,
+#             "username": email,
+#             "email": email_to,
+#             "valid_hours": settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS,
+#             "link": link,
+#         },
+#     )
+
+
+# @users.post("/password-recovery/{email}")
+# async def recover_password(email:str):
+#     user = await User.filter(email=email).first()
+#     if not user:
+#         raise HTTPException(
+#             status_code=404,
+#             detail="The user with this username does not exist in the system.",
+#         )
+#     password_reset_token = generate_password_reset_token(email=email)
+#     send_reset_password_email(
+#         email_to=user.email, email=email, token=password_reset_token
+#     )
+#     return {"msg": "Password recovery email sent"}
+
+
+
+# @users.get("/delete_user/{id}")
+# async def delete(id: int):
+#     if id:
+#         await User.filter(id=id).delete()
+#         return {"msg": "success", "code": 200}
+
+#     return {"msg": "failed", "code": 400}

+ 21 - 0
app/api/deps.py

@@ -0,0 +1,21 @@
+from typing import Generator
+
+from fastapi import Depends, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer
+from jose import jwt
+from pydantic import ValidationError
+from sqlalchemy.orm import Session
+from app.session import SessionLocal
+from app.config import settings
+
+reusable_oauth2 = OAuth2PasswordBearer(
+    tokenUrl=f"{settings.API_V1_STR}/login/access-token"
+)
+
+
+def get_db() -> Generator:
+    try:
+        db = SessionLocal()
+        yield db
+    finally:
+        db.close()

+ 162 - 0
app/api/users.py

@@ -0,0 +1,162 @@
+from fastapi import APIRouter, Form, Depends, HTTPException
+from fastapi.security import OAuth2PasswordRequestForm
+from app.models.models import User
+from app.api import deps
+from sqlalchemy.orm import Session
+from typing import Any, Dict
+import secrets
+from fastapi_login.exceptions import InvalidCredentialsException
+from fastapi_login import LoginManager
+from datetime import timedelta,datetime
+from app.config import settings
+from pathlib import Path
+from jose import jwt
+import emails
+from emails.template import JinjaTemplate
+import logging
+import bcrypt
+
+
+users = APIRouter()
+
+SECRET: str = secrets.token_urlsafe(32)
+manager = LoginManager(SECRET, '/login',default_expiry=timedelta(hours=72))
+
+
+@manager.user_loader()
+async def query_user(user_id: str):
+    """
+    Get a user from the db
+    :param user_id: E-Mail of the user
+    :return: None or the user object
+    """
+    result = await User.filter(username=user_id).first()
+    if not result:
+        print('[]')
+        return []
+    return result
+    # return DB['users'].get(user_id)
+
+
+@users.post("/login")
+async def login(data: OAuth2PasswordRequestForm = Depends()):
+    username = data.username
+    password = data.password
+    password_bytes = password.encode('utf-8')  # 輸入的密碼
+    print(password_bytes)
+    user = await query_user(username)
+    stored_hashed_password_bytes = user.password.encode('utf-8')
+    print(user)
+    access_token = manager.create_access_token(
+        data={'sub': username}
+    )
+    if not user:
+        # you can return any response or error of your choice
+        raise InvalidCredentialsException
+
+    # elif password != user.password:
+    #     raise InvalidCredentialsException
+
+    else:
+        if bcrypt.checkpw(password_bytes, stored_hashed_password_bytes):
+            return {'access_token': access_token}
+        else:
+            return {"message": "Invalid username or password"}
+
+
+@users.post("/logout")
+async def logout():
+
+    return {"msg":"logout success","code":200}
+
+@users.post("/add")
+async def add(username: str = Form(default=''), password: str = Form(default=''), email: str = Form(default=''), re_password: str = Form(default='')):
+    if username and password and email:
+        if password == re_password:
+            hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
+            u = await User.create(username=username, password=hashed_password, email=email,point='1000')
+            if u:
+                # send_email()
+                return {"msg": "已寄送認證信", "code": 200}
+        else:
+            return {"msg":"確認密碼錯誤","code":403}
+    return {"msg": "create user failed", "code": 403}
+
+
+def generate_password_reset_token(email: str) -> str:
+    delta = timedelta(hours=settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS)
+    now = datetime.utcnow()
+    expires = now + delta
+    exp = expires.timestamp()
+    encoded_jwt = jwt.encode(
+        {"exp": exp, "nbf": now, "sub": email}, settings.SECRET_KEY, algorithm="HS256",
+    )
+    return encoded_jwt
+
+
+def send_email(
+    email_to: str,
+    subject_template: str = "",
+    html_template: str = "",
+    environment: Dict[str, Any] = {},
+) -> None:
+    # assert settings.EMAILS_ENABLED, "no provided configuration for email variables"
+    message = emails.Message(
+        subject=JinjaTemplate(subject_template),
+        html=JinjaTemplate(html_template),
+        mail_from=(settings.EMAILS_FROM_NAME, settings.EMAILS_FROM_EMAIL),
+    )
+    smtp_options = {"host": settings.SMTP_HOST, "port": settings.SMTP_PORT}
+    if settings.SMTP_TLS:
+        smtp_options["tls"] = True
+    if settings.SMTP_USER:
+        smtp_options["user"] = settings.SMTP_USER
+    if settings.SMTP_PASSWORD:
+        smtp_options["password"] = settings.SMTP_PASSWORD
+    response = message.send(to=email_to, render=environment, smtp=smtp_options)
+    logging.info(f"send email result: {response}")
+
+
+def send_reset_password_email(email_to: str, email: str, token: str) -> None:
+    subject = f"Password recovery for user {email}"
+    with open(Path(settings.EMAIL_TEMPLATES_DIR) / "reset_password.html") as f:
+        template_str = f.read()
+    server_host = settings.SERVER_HOST
+    link = f"{server_host}/reset-password?token={token}"
+    send_email(
+        email_to=email_to,
+        subject_template=subject,
+        html_template=template_str,
+        environment={
+            # "project_name": settings.PROJECT_NAME,
+            "username": email,
+            "email": email_to,
+            "valid_hours": settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS,
+            "link": link,
+        },
+    )
+
+
+@users.post("/password-recovery/{email}")
+async def recover_password(email:str):
+    user = await User.filter(email=email).first()
+    if not user:
+        raise HTTPException(
+            status_code=404,
+            detail="The user with this username does not exist in the system.",
+        )
+    password_reset_token = generate_password_reset_token(email=email)
+    send_reset_password_email(
+        email_to=user.email, email=email, token=password_reset_token
+    )
+    return {"msg": "Password recovery email sent"}
+
+
+
+@users.get("/delete_user/{id}")
+async def delete(id: int):
+    if id:
+        await User.filter(id=id).delete()
+        return {"msg": "success", "code": 200}
+
+    return {"msg": "failed", "code": 400}

+ 34 - 0
app/config.py

@@ -0,0 +1,34 @@
+import secrets
+from typing import Any, Dict, List, Optional, Union
+
+from pydantic import AnyHttpUrl, BaseSettings, EmailStr, HttpUrl, PostgresDsn, validator, DirectoryPath
+
+
+class Settings(BaseSettings):
+    API_V1_STR: str = "/api/v1"
+    SECRET_KEY: str = secrets.token_urlsafe(32)
+    # 60 minutes * 24 hours * 8 days = 8 days
+    ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
+    SERVER_HOST: AnyHttpUrl = "http://cmm.ai:8088"
+
+    SMTP_TLS: bool = True
+    SMTP_PORT: Optional[int] = None
+    SMTP_HOST: Optional[str] = None
+    SMTP_USER: Optional[str] = None
+    SMTP_PASSWORD: Optional[str] = None
+    EMAILS_FROM_EMAIL: Optional[EmailStr] = None
+    EMAILS_FROM_NAME: Optional[str] = None
+    EMAILS_ENABLED: bool = True
+
+    @validator("EMAILS_ENABLED", pre=True, check_fields=False)
+    def get_emails_enabled(cls, v: bool, values: Dict[str, Any]) -> bool:
+        return bool(
+            values.get("SMTP_HOST")
+            and values.get("SMTP_PORT")
+            and values.get("EMAILS_FROM_EMAIL")
+        )
+    EMAIL_TEMPLATES_DIR: str = "C:\/Users\/s1301\/PycharmProjects\/fastdemo\/app\email-templates"
+    EMAIL_RESET_TOKEN_EXPIRE_HOURS: int = 48
+
+
+settings = Settings()

Datei-Diff unterdrückt, da er zu groß ist
+ 24 - 0
app/email-templates/reset_password.html


BIN
app/models/__pycache__/models.cpython-38.pyc


+ 23 - 0
app/models/models.py

@@ -0,0 +1,23 @@
+from tortoise import fields
+from tortoise.models import Model
+from pydantic import BaseModel
+from datetime import datetime
+class User(Model):
+    id = fields.IntField(pk=True)
+    username = fields.CharField(max_length=30, unique=True, description="帳號")
+    password = fields.CharField(max_length=128, description="密碼")
+    email = fields.CharField(max_length=128, description="信箱")
+    points = fields.IntField(description="點數")
+
+class Class_list(Model):
+    id = fields.IntField(pk=True)
+    name = fields.CharField(max_length=255, description="活動名稱")
+    start_time = fields.DatetimeField(description="開始時間")
+    end_time = fields.DatetimeField(description="結束時間")
+    location = fields.CharField(max_length=255, description="地點")
+    lecturer = fields.CharField(max_length=255, description="講師")
+    organizer = fields.CharField(max_length=255, description="主辦單位")
+    contact = fields.CharField(max_length=255, description="聯絡資訊")
+    introduction = fields.TextField(description="簡介")
+    content = fields.TextField(description="內容")
+    cover_img = fields.CharField(max_length=255, description="封面圖片")

+ 12 - 0
app/session.py

@@ -0,0 +1,12 @@
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+import pymysql
+pymysql.install_as_MySQLdb()
+
+user = "choozmo"
+password = "pAssw0rd"
+host = "db.ptt.cx:3306"
+db_name = "test"
+engine = create_engine(f'mysql://{user}:{password}@{host}/{db_name}', pool_pre_ping=True)
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

+ 1 - 0
app/templates/index.html

@@ -0,0 +1 @@
+asdasd

BIN
app/templates/ntcri_backstage/.DS_Store


+ 167 - 0
app/templates/ntcri_backstage/add-class.html

@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<html lang="zh">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>工藝中心後台</title>
+
+
+
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.min.css">
+    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css"
+        integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp" crossorigin="anonymous">
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet"
+        integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
+    <!-- Add the slick-theme.css if you want default styling -->
+    <link rel="stylesheet" type="/text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css" />
+    <!-- Add the slick-theme.css if you want default styling -->
+    <link rel="stylesheet" type="./text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick-theme.css" />
+    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/timepicker/1.3.5/jquery.timepicker.min.css">
+
+    <link rel="stylesheet" href="/css/style.css">
+
+</head>
+
+<body>
+
+    <div class="bacstage">
+
+        <div class="row px-0">
+            <div class="col-lg-2">
+                <div class="logo_box">
+                    <img class="ntcri_logo img-fluid" src="img/logo.png" alt="">
+                </div>
+                <div class="bacstage_nav">
+
+                    <div class="bacstage_nav_title course_management" href="">課程管理功能</div>
+                    <div id="bacstage_nav_cml" class="course_management_list">
+                        <ul class="bacstage_nav_content">
+                            <li>
+                                <a href="/class-list.html">課程清單</a>
+                            </li>
+                            <li>
+                                <a href="/add-class.html">課程上架</a>
+                            </li>
+
+                        </ul>
+                    </div>
+
+                    <div class="bacstage_nav_title course_management" href="">輔導團頁面</div>
+                    <div class="bacstage_nav_title course_management" href="">網站管理</div>
+
+
+                </div>
+            </div>
+            <div class="bacstage_wordspace col-lg-10">
+                <div class="bacstage_wordspace_breadcrumb">
+                    <nav aria-label="breadcrumb">
+                        <ol class="breadcrumb">
+                            <li class="breadcrumb-item"><a href="#">課程管理</a></li>
+                            <li class="breadcrumb-item active" aria-current="page">課程上架</li>
+                        </ol>
+                    </nav>
+                </div>
+
+                <div class="bacstage_wordspace_addclass">
+                    <div class="bacstage_wordspace_addclass_w80">
+                        <!-- <div class="input-group mb-3">
+                            <span class="input-group-text" id="inputGroup-sizing-default">課程id</span>
+                            <input type="text" class="form-control" aria-label="Sizing example input"
+                                aria-describedby="inputGroup-sizing-default">
+                        </div> -->
+                        <div class="input-group mb-3">
+                            <span class="input-group-text">課程名稱</span>
+                            <input type="text" class="form-control" id="name" aria-label="Sizing example input"
+                                aria-describedby="inputGroup-sizing-default">
+                        </div>
+                        <div class="input-group mb-3">
+                            <span class="input-group-text">課程圖片</span>
+                            <input type="text" class="form-control" id="cover_img" aria-label="Sizing example input"
+                                aria-describedby="inputGroup-sizing-default">
+                        </div>
+                        <div class="input-group mb-3">
+                            <span class="input-group-text">課程起始日期</span>
+                            <input type="datetime-local" class="form-control" id="start_time"
+                                aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default"
+                                min="2023-06-07T00:00"
+                                max="2030-06-14T00:00">
+                            <!-- <input type="datetime-local" value="2018-06-12T19:30" min="2018-06-07T00:00"
+                                max="2018-06-14T00:00"> -->
+
+                        </div>
+                        <div class="input-group mb-3">
+                            <span class="input-group-text">課程結束日期</span>
+                            <input type="datetime-local" class="form-control" id="end_time"
+                                aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default">
+                        </div>
+
+
+                        <div class="input-group mb-3">
+                            <span class="input-group-text">課程地點</span>
+                            <input type="text" class="form-control" id="location" aria-label="Sizing example input"
+                                aria-describedby="inputGroup-sizing-default">
+                        </div>
+                        <div class="input-group mb-3">
+                            <span class="input-group-text">課程講師</span>
+                            <input type="text" class="form-control" id="lecturer" aria-label="Sizing example input"
+                                aria-describedby="inputGroup-sizing-default">
+                        </div>
+
+                        <div class="input-group mb-3">
+                            <span class="input-group-text">主辦單位</span>
+                            <input type="text" class="form-control" id="organizer" aria-label="Sizing example input"
+                                aria-describedby="inputGroup-sizing-default">
+                        </div>
+
+
+                        <div class="input-group mb-3">
+                            <span class="input-group-text">聯絡資訊</span>
+                            <input type="text" class="form-control" id="contact" aria-label="Sizing example input"
+                                aria-describedby="inputGroup-sizing-default">
+                        </div>
+
+                        <div class="input-group">
+                            <span class="input-group-text">課程描述</span>
+                            <textarea class="form-control" id="introduction" aria-label="With textarea"></textarea>
+                        </div>
+
+                        <div class="input-group mt-3">
+                            <span class="input-group-text">課程內容<br>(文字編輯器)</span>
+                            <textarea class="form-control" id="content" aria-label="With textarea"></textarea>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="send_button text-end">
+                    <div>
+                        <!-- <button type="button"  class="btn btn-secondary">取消</button> -->
+                        <button type="button" id="submit" class="btn btn-success">上傳</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+
+
+
+
+    <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.all.min.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"
+        integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p"
+        crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.min.js"
+        integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT"
+        crossorigin="anonymous"></script>
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
+
+    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js"></script>
+    <script src="//cdnjs.cloudflare.com/ajax/libs/timepicker/1.3.5/jquery.timepicker.min.js"></script>
+    <!-- <script type="module" src="/js/module.js"></script> -->
+    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
+    <script async defer src="/js/index.js"></script>
+
+</body>
+
+</html>

+ 133 - 0
app/templates/ntcri_backstage/class-list.html

@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html lang="zh">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>工藝中心後台</title>
+
+
+
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.min.css">
+    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css"
+        integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp" crossorigin="anonymous">
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet"
+        integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
+    <!-- Add the slick-theme.css if you want default styling -->
+    <link rel="stylesheet" type="./text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css" />
+    <!-- Add the slick-theme.css if you want default styling -->
+    <link rel="stylesheet" type="/text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick-theme.css" />
+    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/timepicker/1.3.5/jquery.timepicker.min.css">
+
+    <link rel="stylesheet" href="/css/style.css">
+
+</head>
+
+<body>
+
+    <div class="bacstage">
+
+        <div class="row px-0">
+            <div class="col-lg-2">
+                <div class="logo_box">
+                    <img class="ntcri_logo img-fluid" src="img/logo.png" alt="">
+                </div>
+                <div class="bacstage_nav">
+                    
+                    <div class="bacstage_nav_title course_management" href="">課程管理功能</div>
+                    <div id="bacstage_nav_cml" class="course_management_list">
+                        <ul class="bacstage_nav_content">
+                            <li>
+                                <a href="/class-list.html">課程清單</a>
+                            </li>
+                            <li>
+                                <a href="/add-class.html">課程上架</a>
+                            </li>
+                            
+                        </ul>
+                    </div>
+
+                    <div class="bacstage_nav_title course_management" href="">輔導團頁面</div>
+                    <div class="bacstage_nav_title course_management" href="">網站管理</div>
+
+                   
+                </div>
+            </div>
+            <div class="bacstage_wordspace col-lg-10">
+                <div class="bacstage_wordspace_breadcrumb">
+                    <nav aria-label="breadcrumb">
+                        <ol class="breadcrumb">
+                            <li class="breadcrumb-item"><a href="#">課程管理</a></li>
+                            <li class="breadcrumb-item active" aria-current="page">課程清單</li>
+                        </ol>
+                    </nav>
+                </div>
+
+                <div class="bacstage_wordspace_class_list">
+                    <table class="table">
+                        <thead>
+                          <tr>
+                            <th scope="col" class="number">項次</th>
+                            <th scope="col" class="classname">課程名稱</th>
+                            <th scope="col">課程起始日期</th>
+                            <th scope="col">課程結束日期</th>
+                            <th scope="col">課程地點</th>
+                            <th scope="col" class="teacher">課程講師</th>
+                            <th scope="col">主辦單位</th>
+                            <th scope="col">聯絡資訊</th>
+                            <th scope="col">刪除</th>
+                            <!-- <th scope="col">刪除</th> -->
+                          </tr>
+                        </thead>
+                        <tbody class="class_list_table">
+                          <tr>
+                            <td>1</td>
+                            <td >樹藝教師培力工作坊</td>
+                            <td>2023-08-22 09:00:00</td>
+                            <td>2023-08-25 17:00:00</td>
+                            <td>國立臺灣工藝研究發展中心|地方工藝館工藝教室</td>
+                            <td>李永謨老師</td>
+                            <td>國立臺灣工藝研究發展中心</td>
+                            <td>049-2334141*312</td>
+                            <td>
+                                <div>
+                                    <button class="btn__delete" type="submit" value="delete" onclick="return confirm('確定要刪除此專欄文章?');"><i class="fas fa-trash-alt"></i></button>
+                                </div>
+                            </td>
+                          </tr>
+                        </tbody>
+                      </table>
+                </div>
+
+                <!-- <div class="send_button text-end">
+                    <div>
+                        <button type="button" class="btn btn-secondary">取消</button>
+                        <button type="button" class="btn btn-success">上傳</button>
+                    </div>
+                </div> -->
+            </div>
+        </div>
+    </div>
+
+
+
+
+
+    <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.all.min.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"
+        integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p"
+        crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.min.js"
+        integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT"
+        crossorigin="anonymous"></script>
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
+
+    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js"></script>
+    <script src="//cdnjs.cloudflare.com/ajax/libs/timepicker/1.3.5/jquery.timepicker.min.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
+    <script async defer src="/js/index.js"></script>
+    <script async defer src="/js/class-list.js"></script>
+
+</body>
+
+</html>

+ 167 - 0
app/templates/ntcri_backstage/css/style.css

@@ -0,0 +1,167 @@
+* {
+  margin: 0;
+  padding: 0;
+  -webkit-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+body {
+  background: #F0F0F0;
+  overflow-x: hidden;
+}
+
+.bacstage {
+  background: #F0F0F0;
+}
+
+.bacstage_wordspace {
+  width: 75%;
+  margin: 0 auto;
+}
+
+.bacstage_nav {
+  padding: 15px;
+  background: #3B7054;
+  height: 100% !important;
+  color: #fff;
+}
+
+.ntcri_logo {
+  -o-object-fit: cover;
+     object-fit: cover;
+}
+
+.logo_box {
+  -webkit-box-shadow: 0 2px 5px 1px rgba(64, 60, 67, 0.16);
+          box-shadow: 0 2px 5px 1px rgba(64, 60, 67, 0.16);
+  padding: 10px;
+  background: #fff;
+}
+
+.bacstage_nav_content {
+  padding: 15px;
+  list-style: none;
+}
+
+.bacstage_nav_content ul {
+  padding: 10px;
+}
+
+.bacstage_nav_content li {
+  margin-top: 10px;
+  cursor: pointer;
+  font-size: 20px;
+}
+
+.bacstage_nav_content li a {
+  color: #fff;
+  font-size: 18px;
+  text-decoration: none;
+}
+
+.bacstage_wordspace_breadcrumb {
+  background: #fff;
+  -webkit-box-shadow: 0 2px 5px 1px rgba(64, 60, 67, 0.16);
+          box-shadow: 0 2px 5px 1px rgba(64, 60, 67, 0.16);
+  padding: 30px;
+  margin-top: 50px;
+  font-size: 24px;
+}
+
+.bacstage_wordspace_breadcrumb a {
+  color: #6c757d;
+  text-decoration: underline;
+}
+
+.bacstage_wordspace_addclass_w80 {
+  width: 80%;
+  margin: 0 auto;
+}
+
+.bacstage_wordspace_addclass {
+  padding: 15px;
+}
+
+.bacstage_wordspace_addclass, .bacstage_wordspace_class_list {
+  background: #fff;
+  -webkit-box-shadow: 0 2px 5px 1px rgba(64, 60, 67, 0.16);
+          box-shadow: 0 2px 5px 1px rgba(64, 60, 67, 0.16);
+  padding: 50px 0;
+  margin-top: 50px;
+}
+
+.bacstage_wordspace_addclass .input-group-text, .bacstage_wordspace_class_list .input-group-text {
+  background-color: #fff !important;
+  border: none !important;
+  width: 15%;
+}
+
+.bacstage_wordspace_addclass .date, .bacstage_wordspace_class_list .date {
+  background-color: #fff !important;
+  border: none !important;
+  width: 15%;
+}
+
+.send_button {
+  margin-top: 30px;
+}
+
+.bacstage_nav_title {
+  color: #ffff;
+  font-size: 20px;
+  text-decoration: none;
+}
+
+.bacstage_nav_title {
+  cursor: pointer;
+  margin-top: 15px;
+}
+
+.bacstage_wordspace_class_list {
+  padding: 15px;
+}
+
+.table tr th {
+  font-size: 14px;
+}
+
+.table td {
+  font-size: 14px;
+}
+
+.delete {
+  font-size: 12px;
+}
+
+.teacher {
+  width: 9%;
+}
+
+.classname {
+  width: 15%;
+}
+
+.number {
+  width: 5%;
+}
+
+.btn__delete {
+  display: inline-block;
+  width: 2.5rem;
+  height: 2.5rem;
+  border-radius: 50%;
+  box-shadow: 0 0 6px #b3b3b3;
+  -webkit-box-shadow: 0 0 6px #b3b3b3;
+  -moz-box-shadow: 0 0 6px #b3b3b3;
+  color: #fff;
+  margin-right: 0;
+  background-color: #e74c3c;
+  border: none;
+  -webkit-transition: 0.3s;
+  transition: 0.3s;
+}
+
+.btn__delete:hover {
+  background-color: #d0ece7;
+}
+/*# sourceMappingURL=style.css.map */

Datei-Diff unterdrückt, da er zu groß ist
+ 2 - 0
app/templates/ntcri_backstage/css/style.css.map


+ 164 - 0
app/templates/ntcri_backstage/css/style.scss

@@ -0,0 +1,164 @@
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+  }
+  
+  body{
+    background: #F0F0F0;
+    overflow-x: hidden;
+  }
+  .bacstage{
+    background: #F0F0F0;
+    // height: 100vh !important;
+  }
+
+ .bacstage_wordspace{
+    width: 75%;
+    margin: 0 auto;
+ }
+
+  .bacstage_nav{
+    padding: 15px;
+    background:#3B7054;
+    height:100% !important;
+    color:#fff;
+    
+  }
+
+  .ntcri_logo{
+    object-fit: cover;
+    // width: 100px;
+    // height: auto;
+  }
+
+  .logo_box{
+    box-shadow: 0 2px 5px 1px rgba(64,60,67,.16);
+    padding: 10px;
+    background: #fff;
+  }
+  .bacstage_nav_content{
+    padding: 15px;
+    list-style:none;
+    ul{
+        padding: 10px;
+    }
+    li{
+        margin-top: 10px;
+        cursor: pointer;
+        font-size: 20px;
+        a{
+            color:#fff;
+            font-size: 18px;
+            text-decoration: none;
+        }
+    }
+  }
+
+
+  .bacstage_wordspace_breadcrumb{
+    background: #fff;
+    box-shadow: 0 2px 5px 1px rgba(64,60,67,.16);
+    padding: 30px;
+    margin-top: 50px;
+    font-size: 24px;
+    a {
+        color: #6c757d;
+        text-decoration: underline;
+
+    }
+
+  }
+
+  .bacstage_wordspace_addclass_w80{
+    width: 80%;
+    margin:0 auto ;
+ 
+  }
+  .bacstage_wordspace_addclass{
+    padding: 15px;
+  }
+
+  .bacstage_wordspace_addclass,.bacstage_wordspace_class_list{
+    background: #fff;
+    box-shadow: 0 2px 5px 1px rgba(64,60,67,.16);
+    padding:50px 0;
+    margin-top: 50px;
+    .input-group-text{
+        background-color:#fff !important;
+        border: none !important;
+        width: 15%;
+    }
+   .date{
+         background-color:#fff !important;
+        border: none !important;
+        width: 15%;
+   }
+  }
+  
+  .send_button{
+    margin-top: 30px;
+  }
+
+  .bacstage_nav_title{
+    color:#ffff;
+    font-size: 20px;
+    text-decoration: none;
+
+}
+
+.bacstage_nav_title {
+    cursor: pointer;
+    margin-top: 15px;
+}
+
+// 課程清單頁面
+
+.bacstage_wordspace_class_list{
+    padding: 15px;
+}
+
+.table{
+    tr{
+        th{
+            font-size: 14px;
+        }
+    }
+    td{
+        font-size: 14px;
+    }
+}
+
+.delete{
+    font-size: 12px;
+}
+
+.teacher{
+    width: 9%;
+}
+
+.classname{
+    width: 15%;
+}
+.number{
+    width: 5%;
+}
+
+.btn__delete {
+    display: inline-block;
+    width: 2.5rem;
+    height: 2.5rem;
+    border-radius: 50%;
+    box-shadow: 0 0 6px rgb(179, 179, 179);
+    -webkit-box-shadow: 0 0 6px rgb(179, 179, 179);
+    -moz-box-shadow: 0 0 6px rgb(179, 179, 179);
+    color: #fff;
+    margin-right: 0;
+    background-color: #e74c3c;
+    border:none;
+    transition: 0.3s;
+    &:hover{
+     background-color: #d0ece7;
+    
+    }
+}

BIN
app/templates/ntcri_backstage/img/.DS_Store


BIN
app/templates/ntcri_backstage/img/logo.png


+ 67 - 0
app/templates/ntcri_backstage/js/class-list.js

@@ -0,0 +1,67 @@
+function get_data() {
+
+    axios
+        .get('http://cmm.ai:8088/api/get_class')
+        .then(
+            (response) => {
+                courseData = response.data.classes
+                console.log(courseData);
+                var contentarticle = '';
+                for (var i = 0; i < courseData.length; i++) {
+                    console.log(i + 1);
+                    var itemnum = i + 1
+                    contentarticle += '\
+            <tr> \
+            <td>'+ itemnum + '</td>\
+            <td >'+ courseData[i].name + ' </td>\
+            <td>'+ courseData[i].start_time + '</td>\
+            <td>'+ courseData[i].end_time + '</td>\
+            <td>'+ courseData[i].location + '</td>\
+            <td>'+ courseData[i].lecturer + '</td>\
+            <td>'+ courseData[i].organizer + '</td>\
+            <td>'+ courseData[i].contact + '</td>\
+            <td>\
+                <div>\
+                    <button id="'+ courseData[i].class_id + '" class="btn__delete" type="submit" value="' + courseData[i].class_id + '");"><i class="fas fa-trash-alt"></i></button>\
+                </div>\
+            </td>\
+            </tr>\
+            '
+                }
+
+                $('.class_list_table').html(contentarticle);
+
+            })
+        .catch((error) => console.log(error))
+
+
+}
+
+get_data();
+
+
+
+$(".btn__delete").on("click", function () {
+    var delete_id = $(this).val();
+    console.log(delete_id)
+});
+
+
+
+$(document).on("click", ".btn__delete", function (event) {
+    console.log(1);
+    var delete_id = $(this).val();
+    console.log(delete_id);
+    
+    const formData_class = new FormData();
+    formData_class.append("id", delete_id)
+    axios.post('http://cmm.ai:8088/api/delete_class?id=' + delete_id)
+        .then(
+            (response) => {
+                console.log(response);
+                // 在请求成功后重新加载页面
+                location.reload();
+            })
+        .catch((error) => console.log(error))
+
+});

+ 117 - 0
app/templates/ntcri_backstage/js/index.js

@@ -0,0 +1,117 @@
+function send() {
+    console.log('addEventListener');
+    let name = document.querySelector('#name').value;
+    let start_time = document.querySelector('#start_time').value;
+    let end_time = document.querySelector('#end_time').value;
+    let location = document.querySelector('#location').value;
+    let lecturer = document.querySelector('#lecturer').value;
+    let organizer = document.querySelector('#organizer').value;
+    let contact = document.querySelector('#contact').value;
+    let introduction = document.querySelector('#introduction').value;
+    let content = document.querySelector('#content').value;
+    let cover_img = document.querySelector('#cover_img').value;
+
+    // if (name === "" || email === "" || date === "" || phone === "" || line === "" || gender==="") {
+    //   $('.errorText').show();
+    //   return;
+    // } else {
+    //   // $('.loading-btn').show();
+    //   $('.errorText').hide();
+    // }
+
+    // var currentDatetime = new Date();
+    // var formattedDatetime_start = `${currentDatetime.toISOString().slice(0, 23)}`;
+    // var formattedDatetime_end = `${currentDatetime.toISOString().slice(0, 23)}`;
+
+    // start_time.value = formattedDatetime_start;
+    // end_time.value=formattedDatetime_end;
+
+    const scriptURL = 'http://cmm.ai:8088/api/insert_class';
+    
+
+    // var data = {
+    //     class_id:"",
+    //     name: name,
+    //     start_time: start_time+':00+00:00',
+    //     end_time: end_time+':00+00:00',
+    //     location: location,
+    //     lecturer: lecturer,
+    //     organizer: organizer,
+    //     contact: contact,
+    //     introduction: introduction,
+    //     content: content,
+    //     cover_img: cover_img
+
+    // }
+
+
+
+    // console.log(formattedDatetime_start);
+    // console.log(formattedDatetime_end);
+
+    const formData = new FormData();
+    formData.append("class_id","")
+    formData.append("name", name)
+    formData.append("start_time", start_time+':00+00:00')
+    formData.append("end_time", end_time+':00+00:00')
+    formData.append("location", location)
+    formData.append("lecturer", lecturer)
+    formData.append("organizer", organizer)
+    formData.append("contact", contact)
+    formData.append("introduction", introduction)
+    formData.append("content", content)
+    formData.append("cover_img", cover_img)
+
+   
+
+    axios.post('http://cmm.ai:8088/api/insert_class', formData )
+        .then(
+            (response) =>{
+                console.log(response);
+                // 在请求成功后重新加载页面
+                alert('上傳成功')
+              })
+        .catch((error) => console.log(error))
+
+    // $.ajax({
+    //     url: scriptURL,
+    //     method: "post",
+    //     data: {
+    //         class_id:"",
+    //         name: name,
+    //         start_time: start_time+':00+00:00',
+    //         end_time: end_time+':00+00:00',
+    //         location: location,
+    //         lecturer: lecturer,
+    //         organizer: organizer,
+    //         contact: contact,
+    //         introduction: introduction,
+    //         content: content,
+    //         cover_img: cover_img
+    //     },
+    //     success: function (response) {
+
+    //         if (response == "成功") {
+    //             // $('.loading-btn').hide();
+    //             alert("成功提交!");
+    //             location.reload();
+    //         }
+    //     },
+    // });
+
+};
+
+
+$('#submit').on('click', function (e) {
+    e.preventDefault();
+    send();
+});
+
+
+
+
+$(".course_management").on("click", function () {
+    $("#bacstage_nav_cml").slideToggle("slow", function () {
+        // Animation complete.
+    });
+});

+ 44 - 0
main.py

@@ -0,0 +1,44 @@
+import uvicorn
+from fastapi import Request
+from app import create_app
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.templating import Jinja2Templates
+from fastapi.responses import HTMLResponse
+
+
+
+app = create_app()
+templates = Jinja2Templates(directory="app/templates")
+origins = [
+    "http://www.googo.org",
+    "http://www.googo.org:8080",
+    "http://0.0.0.0:8080",
+    "http://googo.org:8080",
+    "http://googo.org",
+    "http://139.162.121.30",
+         "http://127.0.0.1:5173",
+         "http://localhost:5173"
+]
+#uvicorn main:app --host 0.0.0.0 --port 8001
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["*"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+
+@app.get("/index.html", response_class=HTMLResponse)
+def read_index(request: Request):
+    return templates.TemplateResponse("index.html", {"request": request})
+
+#@app.get("/ntcri_backstage/add-class.html", response_class=HTMLResponse)
+#def read_index(request: Request):
+#    return templates.TemplateResponse("/ntcri_backstage/add-class.html", {"request": request})
+#@app.get("/ntcri_backstage/class-list.html", response_class=HTMLResponse)
+#def read_index(request: Request):
+#    return templates.TemplateResponse("/ntcri_backstage/class-list.html", {"request": request})
+
+if __name__ == "__main__":
+    uvicorn.run("main:app", host="cmm.ai", port=8088)

+ 6 - 0
ntcri_restart.sh

@@ -0,0 +1,6 @@
+tmux new -d -s  ntcri_oak
+
+tmux new-window -t ntcri_oak:0 -d
+tmux send-keys -t ntcri_oak:0 "cd /var/www/ntcri" Enter
+tmux send-keys -t ntcri_oak:0 "sudo uvicorn main:app --host cmm.ai  --port 8088 --ssl-keyfile=/etc/letsencrypt/live/cmm.ai/privkey.pem --ssl-certfile=/etc/letsencrypt/live/cmm.ai/fullchain.pem" Enter
+

+ 4 - 0
readme.txt

@@ -0,0 +1,4 @@
+cmm.ai那台
+cd /var/www/ntcri
+
+sudo uvicorn main:app --host cmm.ai  --port 8088 --ssl-keyfile=/etc/letsencrypt/live/cmm.ai/privkey.pem --ssl-certfile=/etc/letsencrypt/live/cmm.ai/fullchain.pem

BIN
requirements.txt


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.