feat(projects): 新增FastAPI项目相关源码
This commit is contained in:
BIN
projects/web-fastapi/alchemy/alchemy.db
Normal file
BIN
projects/web-fastapi/alchemy/alchemy.db
Normal file
Binary file not shown.
41
projects/web-fastapi/alchemy/basic.py
Normal file
41
projects/web-fastapi/alchemy/basic.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String, create_engine, insert, select
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
engine = create_engine("sqlite://", echo=True, future=True)
|
||||||
|
|
||||||
|
|
||||||
|
class User(Base):
|
||||||
|
__tablename__ = "users"
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
name = Column(String(10), nullable=False)
|
||||||
|
email = Column(String(20))
|
||||||
|
telephone = Column(String(11))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<User(name='{self.name}', email='{self.email}', telephone='{self.telephone}')>"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
|
db = engine.connect()
|
||||||
|
stmt = (
|
||||||
|
insert(User)
|
||||||
|
.values(name="100gle", email="100gle@example.com", telephone="001-1234-5678")
|
||||||
|
.compile()
|
||||||
|
)
|
||||||
|
|
||||||
|
db.execute(stmt)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
query = select(User)
|
||||||
|
data = db.execute(query).all()
|
||||||
|
for row in data:
|
||||||
|
print(row)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
76
projects/web-fastapi/alchemy/crud.py
Normal file
76
projects/web-fastapi/alchemy/crud.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import pathlib
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, create_engine, insert
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
ROOT = pathlib.Path(__file__).parent
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
engine = create_engine(f"sqlite:///{ROOT / 'alchemy.db'}", future=True)
|
||||||
|
Session = sessionmaker(bind=engine)
|
||||||
|
fake_data = [
|
||||||
|
dict(name="100gle", email="100gle@example.com", telephone="001-1234-5678"),
|
||||||
|
dict(name="Steve", email="steve@example.com", telephone="011-1234-4321"),
|
||||||
|
dict(name="John", email="john@example.com", telephone="000-8764-4321"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class User(Base):
|
||||||
|
__tablename__ = "users"
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
name = Column(String(10), nullable=False)
|
||||||
|
email = Column(String(20))
|
||||||
|
telephone = Column(String(11))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<User(name='{self.name}', email='{self.email}', telephone='{self.telephone}')>"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
|
db = Session()
|
||||||
|
|
||||||
|
# create data
|
||||||
|
users = [User(**data) for data in fake_data]
|
||||||
|
|
||||||
|
# same as: db.bulk_save_objects(users)
|
||||||
|
db.add_all(users)
|
||||||
|
|
||||||
|
# insert expression
|
||||||
|
stmt = insert(User).values(
|
||||||
|
name="Tom", email="tom@example.com", telephone="000-1234-5678"
|
||||||
|
)
|
||||||
|
db.execute(stmt)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# select data
|
||||||
|
print("query all data: \n", db.query(User).all())
|
||||||
|
print(
|
||||||
|
"query the data where name is '100gle': \n",
|
||||||
|
db.query(User).filter(User.name == "100gle").first(),
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"query the data where telephone number ends with '4321': \n",
|
||||||
|
db.query(User).filter(User.telephone.endswith("4321")).all(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# update data
|
||||||
|
u = db.query(User).filter(User.name == "100gle")
|
||||||
|
print("before updating: \n", u.first())
|
||||||
|
u.update({"email": ""})
|
||||||
|
db.commit()
|
||||||
|
print(f'after updating: \n', db.query(User).filter(User.name == "100gle").all())
|
||||||
|
|
||||||
|
# delete data
|
||||||
|
u = db.query(User).filter(User.name == "John")
|
||||||
|
u.delete()
|
||||||
|
db.commit()
|
||||||
|
print("after deleting:\n", db.query(User).all())
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
Base.metadata.drop_all(engine)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
38
projects/web-fastapi/alchemy/sess.py
Normal file
38
projects/web-fastapi/alchemy/sess.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String, create_engine
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
engine = create_engine("sqlite://", echo=True, future=True)
|
||||||
|
Session = sessionmaker(bind=engine)
|
||||||
|
|
||||||
|
|
||||||
|
class User(Base):
|
||||||
|
__tablename__ = "users"
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
name = Column(String(10), nullable=False)
|
||||||
|
email = Column(String(20))
|
||||||
|
telephone = Column(String(11))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<User(name='{self.name}', email='{self.email}', telephone='{self.telephone}')>"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
|
with Session() as db:
|
||||||
|
user = User(
|
||||||
|
name="100gle", email="100gle@example.com", telephone="001-1234-5678"
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(user)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
data = db.query(User).all()
|
||||||
|
for row in data:
|
||||||
|
print(row)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
45
projects/web-fastapi/basic.py
Normal file
45
projects/web-fastapi/basic.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import uvicorn
|
||||||
|
from fastapi import APIRouter, FastAPI
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def index():
|
||||||
|
response = """
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<h1>Hello, World</h1>
|
||||||
|
<p>you got it!</p>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
return HTMLResponse(content=response)
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/users")
|
||||||
|
def get_users():
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/users/create")
|
||||||
|
def create_user():
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/users/update/{user_id}")
|
||||||
|
def update_user(user_id):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/users/delete/{user_id}")
|
||||||
|
def delete_user(user_id):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
app.include_router(router)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
uvicorn.run("basic:app")
|
||||||
29
projects/web-fastapi/http/body.py
Normal file
29
projects/web-fastapi/http/body.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
class LoginData(BaseModel):
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/login")
|
||||||
|
def login(data: LoginData):
|
||||||
|
response = dict(message="Succeed", token="user-token")
|
||||||
|
if data.username == "admin":
|
||||||
|
response["token"] = "superuser-token"
|
||||||
|
elif data.username != "user":
|
||||||
|
invalid = dict(
|
||||||
|
message="Login failed. Invalid credentials",
|
||||||
|
token=None,
|
||||||
|
)
|
||||||
|
response.update(invalid)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
uvicorn.run("body:app")
|
||||||
22
projects/web-fastapi/http/path.py
Normal file
22
projects/web-fastapi/http/path.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
fake_data = {
|
||||||
|
1: {"name": "100gle", "age": 18},
|
||||||
|
2: {"name": "Jenny", "age": 28},
|
||||||
|
3: {"name": "Peter", "age": 25},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/user/{user_id}")
|
||||||
|
def get_user_by_id(user_id):
|
||||||
|
id = int(user_id)
|
||||||
|
data = fake_data.get(id, None)
|
||||||
|
if not data:
|
||||||
|
return {"message": "User not found"}
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
uvicorn.run("path:app")
|
||||||
24
projects/web-fastapi/http/query.py
Normal file
24
projects/web-fastapi/http/query.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
fake_data = {
|
||||||
|
1: {"name": "100gle", "age": 18},
|
||||||
|
2: {"name": "Jenny", "age": 28},
|
||||||
|
3: {"name": "Peter", "age": 25},
|
||||||
|
}
|
||||||
|
|
||||||
|
# just change this part:
|
||||||
|
# ===============
|
||||||
|
@app.get("/user")
|
||||||
|
def get_user_by_id(id: int):
|
||||||
|
data = fake_data.get(id, None)
|
||||||
|
if not data:
|
||||||
|
return {"message": "User not found"}
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
# ==============
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
uvicorn.run("query:app")
|
||||||
0
projects/web-fastapi/popup_api/__init__.py
Normal file
0
projects/web-fastapi/popup_api/__init__.py
Normal file
95
projects/web-fastapi/popup_api/api.py
Normal file
95
projects/web-fastapi/popup_api/api.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
from fastapi import APIRouter, Depends, Form
|
||||||
|
from popup_api.models import Group, Task, get_db
|
||||||
|
from popup_api.schema import Response
|
||||||
|
from sqlalchemy import delete
|
||||||
|
from starlette.responses import RedirectResponse
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tasks")
|
||||||
|
def query_all_tasks(db=Depends(get_db)):
|
||||||
|
data = (
|
||||||
|
db.query(Task)
|
||||||
|
.with_entities(
|
||||||
|
Task.id,
|
||||||
|
Task.name,
|
||||||
|
Task.priority,
|
||||||
|
Task.description,
|
||||||
|
Task.is_done,
|
||||||
|
Task.group_id,
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
response = Response(data=data, message="query all tasks successfully.")
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/groups")
|
||||||
|
def query_all_groups(db=Depends(get_db)):
|
||||||
|
data = db.query(Group).all()
|
||||||
|
response = Response(data=data, message="query all groups successfully.")
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/tasks/create")
|
||||||
|
def create_task(
|
||||||
|
name: str = Form(..., alias="taskName", example="测试"),
|
||||||
|
priority: int = Form(1, alias="taskPriority", example=1),
|
||||||
|
description: str = Form("", alias="taskDescription", example="任务描述测试"),
|
||||||
|
group_id: int = Form(1, alias="taskGroup", example=1),
|
||||||
|
db=Depends(get_db),
|
||||||
|
):
|
||||||
|
|
||||||
|
task = Task(
|
||||||
|
name=name, priority=priority, description=description, group_id=group_id
|
||||||
|
)
|
||||||
|
db.add(task)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
response = RedirectResponse("/")
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/tasks/update/{task_id}")
|
||||||
|
def update_task(task_id: int, db=Depends(get_db)):
|
||||||
|
|
||||||
|
task = db.get(Task, task_id)
|
||||||
|
|
||||||
|
if not task:
|
||||||
|
response = Response(
|
||||||
|
code=10001, message=f"can't found the task which id is: {task_id}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
status = 0 if task.is_done == 1 else 1
|
||||||
|
task.is_done = status
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
response = Response(
|
||||||
|
data=dict(id=task_id, name=task.name),
|
||||||
|
message=f"update #{task_id} task successfully.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/tasks/delete/{task_id}")
|
||||||
|
def delete_task(task_id: int, db=Depends(get_db)):
|
||||||
|
|
||||||
|
task = db.get(Task, task_id)
|
||||||
|
if not task:
|
||||||
|
response = Response(
|
||||||
|
code=10001, message=f"can't found the task which id is: {task_id}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
stmt = delete(Task).where(Task.id == task_id)
|
||||||
|
db.execute(stmt)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
response = Response(
|
||||||
|
data=dict(id=task_id, name=task.name),
|
||||||
|
message=f"delete #{task_id} task successfully.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
BIN
projects/web-fastapi/popup_api/db.sqlite3
Normal file
BIN
projects/web-fastapi/popup_api/db.sqlite3
Normal file
Binary file not shown.
79
projects/web-fastapi/popup_api/main.py
Normal file
79
projects/web-fastapi/popup_api/main.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import pathlib
|
||||||
|
|
||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.openapi.docs import (
|
||||||
|
get_redoc_html,
|
||||||
|
get_swagger_ui_html,
|
||||||
|
get_swagger_ui_oauth2_redirect_html,
|
||||||
|
)
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from popup_api.api import router
|
||||||
|
from popup_api.models import init_db
|
||||||
|
from popup_api.settings import settings
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title=settings.SITE_NAME,
|
||||||
|
version=settings.VERSION,
|
||||||
|
description=settings.DESCRIPTION,
|
||||||
|
docs_url=None,
|
||||||
|
redoc_url=None,
|
||||||
|
)
|
||||||
|
ROOT = pathlib.Path(__file__).parent
|
||||||
|
templates = Jinja2Templates(directory=ROOT.joinpath("templates"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def index(request: Request):
|
||||||
|
return templates.TemplateResponse("index.html", context={"request": request})
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/")
|
||||||
|
def index(request: Request):
|
||||||
|
return templates.TemplateResponse("index.html", context={"request": request})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/docs", include_in_schema=False)
|
||||||
|
def custom_swagger_ui_html():
|
||||||
|
return get_swagger_ui_html(
|
||||||
|
openapi_url=app.openapi_url,
|
||||||
|
title=app.title + " - Swagger UI",
|
||||||
|
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
|
||||||
|
swagger_js_url="/static/js/swagger-ui-bundle.js",
|
||||||
|
swagger_css_url="/static/css/swagger-ui.css",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
|
||||||
|
def swagger_ui_redirect():
|
||||||
|
return get_swagger_ui_oauth2_redirect_html()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/redoc", include_in_schema=False)
|
||||||
|
def redoc_html():
|
||||||
|
return get_redoc_html(
|
||||||
|
openapi_url=app.openapi_url,
|
||||||
|
title=app.title + " - ReDoc",
|
||||||
|
redoc_js_url="/static/js/redoc.standalone.js",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
def startup():
|
||||||
|
init_db()
|
||||||
|
print("initialization for app")
|
||||||
|
|
||||||
|
|
||||||
|
app.include_router(router, prefix="/api")
|
||||||
|
app.mount("/static", StaticFiles(directory=ROOT.joinpath("static")), name="static")
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
uvicorn.run("main:app", reload=True)
|
||||||
108
projects/web-fastapi/popup_api/models.py
Normal file
108
projects/web-fastapi/popup_api/models.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
from sqlalchemy import (
|
||||||
|
Column,
|
||||||
|
DateTime,
|
||||||
|
ForeignKey,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
create_engine,
|
||||||
|
func,
|
||||||
|
insert,
|
||||||
|
select,
|
||||||
|
)
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
from popup_api.settings import settings # isort:skip
|
||||||
|
|
||||||
|
engine = create_engine(
|
||||||
|
url=settings.DATABASE_URL,
|
||||||
|
echo=settings.DATABASE_ECHO,
|
||||||
|
connect_args=settings.DATABASE_CONNECT_ARGS,
|
||||||
|
)
|
||||||
|
Base = declarative_base()
|
||||||
|
Session = sessionmaker(bind=engine)
|
||||||
|
|
||||||
|
# ==========
|
||||||
|
# Data Model
|
||||||
|
# ==========
|
||||||
|
|
||||||
|
|
||||||
|
class Task(Base):
|
||||||
|
__tablename__ = "popup_task"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True, comment="序号")
|
||||||
|
name = Column(String(50), nullable=False, comment="任务名称")
|
||||||
|
priority = Column(Integer, nullable=False, default=0, comment="任务优先级")
|
||||||
|
description = Column(String(200), nullable=True, comment="任务描述", default="")
|
||||||
|
create_at = Column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
server_default=func.now(),
|
||||||
|
comment="创建时间",
|
||||||
|
)
|
||||||
|
update_at = Column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
onupdate=func.now(),
|
||||||
|
comment="更新时间",
|
||||||
|
)
|
||||||
|
is_done = Column(Integer, nullable=False, default=0, comment="是否完成")
|
||||||
|
group_id = Column(Integer, ForeignKey("popup_group.id"), default=1, comment="分类序号")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Task(id={self.id}, name={self.name}, is_done={self.is_done}, group_id={self.group_id})>"
|
||||||
|
|
||||||
|
|
||||||
|
class Group(Base):
|
||||||
|
__tablename__ = "popup_group"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True, comment="分类序号")
|
||||||
|
name = Column(
|
||||||
|
String(50),
|
||||||
|
nullable=False,
|
||||||
|
default="收集箱",
|
||||||
|
comment="分类名称",
|
||||||
|
unique=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Group(id={self.id}, name={self.name})"
|
||||||
|
|
||||||
|
|
||||||
|
# ============
|
||||||
|
# Data handler
|
||||||
|
# ============
|
||||||
|
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
|
|
||||||
|
group_check = engine.execute(select(Group).limit(5)).all()
|
||||||
|
|
||||||
|
if not group_check:
|
||||||
|
groups = [
|
||||||
|
dict(name="收集箱"),
|
||||||
|
dict(name="生活"),
|
||||||
|
dict(name="工作"),
|
||||||
|
]
|
||||||
|
stmt = insert(Group).values(groups)
|
||||||
|
engine.execute(stmt)
|
||||||
|
|
||||||
|
task_check = engine.execute(select(Task).limit(5)).all()
|
||||||
|
if not task_check:
|
||||||
|
tasks = [
|
||||||
|
dict(name="吃饭", priority=3, group_id=2),
|
||||||
|
dict(name="睡觉", priority=3, group_id=2),
|
||||||
|
dict(name="打豆豆", priority=0, description="打豆豆是一件很有意思的事情"),
|
||||||
|
dict(name="逛逛少数派", priority=1, description="https://sspai.com"),
|
||||||
|
dict(name="记得下班打卡", priority=3),
|
||||||
|
]
|
||||||
|
|
||||||
|
stmt = insert(Task).values(tasks)
|
||||||
|
engine.execute(stmt)
|
||||||
|
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
session = Session()
|
||||||
|
try:
|
||||||
|
yield session
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
11
projects/web-fastapi/popup_api/schema.py
Normal file
11
projects/web-fastapi/popup_api/schema.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from typing import Optional, TypeVar
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class Response(BaseModel):
|
||||||
|
code: int = 0
|
||||||
|
message: str
|
||||||
|
data: Optional[T]
|
||||||
24
projects/web-fastapi/popup_api/settings.py
Normal file
24
projects/web-fastapi/popup_api/settings.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import pathlib
|
||||||
|
|
||||||
|
from pydantic import BaseSettings
|
||||||
|
|
||||||
|
APP_ROOT = pathlib.Path(__file__).parent
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
|
||||||
|
# fastapi settings
|
||||||
|
VERSION = 0.1
|
||||||
|
SITE_NAME = "Todolist - Popup"
|
||||||
|
DESCRIPTION = """
|
||||||
|
This is the brand new Todolist application,
|
||||||
|
which built in FastAPI with RESTful APIs.
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
# database settings
|
||||||
|
DATABASE_URL = f"sqlite:///{APP_ROOT / 'db.sqlite3'}"
|
||||||
|
DATABASE_ECHO = True
|
||||||
|
DATABASE_CONNECT_ARGS = {"check_same_thread": False}
|
||||||
|
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
1
projects/web-fastapi/popup_api/static/css/simple-v1.min.css
vendored
Executable file
1
projects/web-fastapi/popup_api/static/css/simple-v1.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
123
projects/web-fastapi/popup_api/static/css/style.css
Normal file
123
projects/web-fastapi/popup_api/static/css/style.css
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
.container {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* navbar */
|
||||||
|
.navbar ul {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
text-align: center;
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar a:hover {
|
||||||
|
color: goldenrod !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* data table */
|
||||||
|
.data-table {
|
||||||
|
display: table;
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.data-table th,
|
||||||
|
.data-table td {
|
||||||
|
text-align: center;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
.data-table th:first-child {
|
||||||
|
width: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table th:nth-child(2) {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table th:nth-child(4) {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td[class="ops"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.data-table .ops span {
|
||||||
|
display: block;
|
||||||
|
color: var(--accent);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-task {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-task > button {
|
||||||
|
display: block;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 20px;
|
||||||
|
box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-task > button:hover {
|
||||||
|
background-color: rgb(204, 77, 77);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-form {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.form {
|
||||||
|
display: block;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form > div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form .btn-submit {
|
||||||
|
text-align: center;
|
||||||
|
transition: 0.1ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form .btn-submit > input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 20px;
|
||||||
|
transition: 0.1ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
#taskDescription {
|
||||||
|
width: 60% !important;
|
||||||
|
resize: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* footer */
|
||||||
|
#footer {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
4
projects/web-fastapi/popup_api/static/css/swagger-ui.css
Normal file
4
projects/web-fastapi/popup_api/static/css/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
2
projects/web-fastapi/popup_api/static/js/axios.min.js
vendored
Normal file
2
projects/web-fastapi/popup_api/static/js/axios.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
projects/web-fastapi/popup_api/static/js/vue.min.js
vendored
Normal file
6
projects/web-fastapi/popup_api/static/js/vue.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
84
projects/web-fastapi/popup_api/static/sspai-logo-light.svg
Normal file
84
projects/web-fastapi/popup_api/static/sspai-logo-light.svg
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="123" height="44" viewBox="0 0 123 44" style="enable-background:new 0 0 123 44;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#292525;}
|
||||||
|
.st1{fill:#DA282A;}
|
||||||
|
.st2{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M59.8,31.7C59.8,31.7,59.7,31.7,59.8,31.7c-0.1,0-0.2-0.1-0.2-0.1l-0.9-1.3c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1
|
||||||
|
c0,0,0-0.1,0.1-0.1c0,0,0.1,0,0.1,0c6.6-0.6,11.8-2.9,17-7.5c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0,0,0.1,0.1
|
||||||
|
l0.9,1.1c0,0,0.1,0.1,0,0.2c0,0.1,0,0.1-0.1,0.1C71.6,28.6,66.6,30.8,59.8,31.7L59.8,31.7z" />
|
||||||
|
<path class="st0" d="M69.2,12.2h-1.5c-0.1,0-0.2,0.1-0.2,0.2v12.4c0,0.1,0.1,0.2,0.2,0.2h1.5c0.1,0,0.2-0.1,0.2-0.2V12.5
|
||||||
|
C69.4,12.3,69.3,12.2,69.2,12.2z" />
|
||||||
|
<path class="st0" d="M60.7,23.5H59c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1-0.1c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1l3.6-7.9c0,0,0-0.1,0.1-0.1
|
||||||
|
c0,0,0.1,0,0.1,0h1.7c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1l-3.6,7.9c0,0,0,0.1-0.1,0.1
|
||||||
|
C60.8,23.5,60.7,23.5,60.7,23.5z" />
|
||||||
|
<path class="st0" d="M75.6,21c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1-0.1l-3.1-5.4c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1c0,0,0-0.1,0.1-0.1
|
||||||
|
c0,0,0.1,0,0.1,0h1.8c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0.1l3.1,5.4c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1-0.1,0.1
|
||||||
|
c0,0-0.1,0-0.1,0H75.6L75.6,21z" />
|
||||||
|
<path class="st0" d="M108.5,31.6C108.5,31.6,108.4,31.6,108.5,31.6l-1.4-0.5c-0.1,0-0.1-0.1-0.1-0.1c0,0,0-0.1,0-0.1
|
||||||
|
c0,0,0-0.1,0-0.1l0.8-2c0.3-0.8,0.6-1.6,0.8-2.5c0.3-1.3,0.4-2.5,0.4-3.8V14c0-0.1,0-0.1,0.1-0.2c0,0,0.1-0.1,0.2-0.1
|
||||||
|
c5.3,0.1,7.5-0.5,10.3-1.3l0.7-0.2c0,0,0.1,0,0.1,0s0.1,0,0.1,0.1l0.8,1.2c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1-0.1,0.1
|
||||||
|
c0,0-0.1,0-0.1,0l-0.3,0.1c-2.9,0.9-5,1.5-9.8,1.6c-0.1,0-0.1,0-0.1,0.1c0,0-0.1,0.1-0.1,0.1v6.9c0,1.4-0.2,2.7-0.5,4.1l-0.1,0.3
|
||||||
|
c-0.2,0.8-0.4,1.7-0.8,2.5l-0.8,2C108.7,31.5,108.6,31.6,108.5,31.6C108.6,31.6,108.5,31.6,108.5,31.6z" />
|
||||||
|
<path class="st0" d="M105.6,22c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1-0.1L103,18c0,0,0,0,0-0.1c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1
|
||||||
|
c0,0,0,0,0.1-0.1l1.8-1.1c0,0,0.1,0,0.1-0.1c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1l-1.9-2.9c0,0,0-0.1,0-0.1
|
||||||
|
c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1c0,0,0,0,0.1-0.1l1.3-0.8c0,0,0.1,0,0.2,0c0.1,0,0.1,0,0.1,0.1l2.3,3.6c0,0,0,0,0,0.1l0.6,0.9
|
||||||
|
c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0,0,0-0.1,0.1l-1.8,1.1c0,0-0.1,0-0.1,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1
|
||||||
|
c0,0,0,0.1,0,0.1l1.4,2.2c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.1,0,0.1-0.1,0.1l-1.3,0.8C105.7,22,105.6,22,105.6,22z" />
|
||||||
|
<path class="st0" d="M104.4,31.8C104.4,31.8,104.4,31.8,104.4,31.8l-1.5-0.6c0,0-0.1,0-0.1,0c0,0,0,0,0-0.1c0,0,0-0.1,0-0.1
|
||||||
|
c0,0,0-0.1,0-0.1l3.3-8.3c0,0,0-0.1,0-0.1c0,0,0,0,0.1,0s0.1,0,0.1,0c0,0,0.1,0,0.1,0l1.4,0.6c0,0,0.1,0,0.1,0c0,0,0,0,0,0.1
|
||||||
|
c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1l-3.3,8.3c0,0,0,0,0,0.1C104.6,31.7,104.6,31.7,104.4,31.8C104.5,31.8,104.5,31.8,104.4,31.8z" />
|
||||||
|
<path class="st0" d="M120.8,31.7C120.8,31.7,120.8,31.7,120.8,31.7c-0.1,0-0.2-0.1-0.2-0.1l-1.6-2.3c-0.3-0.4-0.6-0.8-0.8-1.3
|
||||||
|
l-0.8-1.6c-0.3-0.6-0.6-1.3-0.8-2l-1.5-5.7c0,0,0-0.1-0.1-0.1c0,0-0.1,0-0.1,0c-0.4,0-0.7,0-1.1,0.1c0,0-0.1,0-0.1,0
|
||||||
|
c0,0,0,0.1,0,0.1v10.9c0,0.1,0,0.1,0.1,0.1c0,0,0.1,0.1,0.1,0.1h2.6c0.1,0,0.1,0,0.2,0.1c0,0,0.1,0.1,0.1,0.2v1.4
|
||||||
|
c0,0.1,0,0.1-0.1,0.2c0,0-0.1,0.1-0.2,0.1h-4.3c-0.1,0-0.1,0-0.2-0.1c0,0-0.1-0.1-0.1-0.2V17.6c0,0,0,0,0-0.1V17
|
||||||
|
c0-0.1,0-0.1,0.1-0.2c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0c2.4,0,4.7-0.3,7-0.8l0.9-0.2c0,0,0.1,0,0.1,0c0,0,0.1,0.1,0.1,0.1l0.7,1.3
|
||||||
|
c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1-0.1,0.1c0,0-0.1,0-0.1,0l-0.9,0.2c-1,0.2-1.9,0.4-2.9,0.6c0,0,0,0-0.1,0c0,0,0,0,0,0
|
||||||
|
c0,0,0,0,0,0.1c0,0,0,0,0,0.1l0.9,3.5c0,0,0,0.1,0,0.1c0,0,0,0,0.1,0.1c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0l2.9-1.2c0,0,0.1,0,0.1,0
|
||||||
|
c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0.1l0.5,1.3c0,0.1,0,0.1,0,0.2c0,0.1-0.1,0.1-0.1,0.1l-3.2,1.3c0,0-0.1,0-0.1,0.1
|
||||||
|
c0,0,0,0.1,0,0.1c0.2,0.6,0.4,1.1,0.6,1.6l0.7,1.3c0.2,0.4,0.4,0.8,0.7,1.2l1.9,2.7c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1
|
||||||
|
c0,0,0,0.1,0,0.1c0,0-0.1,0-0.1,0.1L120.8,31.7C120.9,31.7,120.9,31.7,120.8,31.7z" />
|
||||||
|
<path class="st0" d="M86.8,22.3h-1.5c-0.1,0-0.1,0-0.2-0.1c0,0-0.1-0.1-0.1-0.2v-4c0,0,0-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1h-4
|
||||||
|
c-0.1,0-0.1,0-0.2-0.1c0,0-0.1-0.1-0.1-0.2v-1.4c0-0.1,0-0.1,0.1-0.2c0,0,0.1-0.1,0.2-0.1h4c0,0,0.1,0,0.1-0.1c0,0,0.1-0.1,0.1-0.1
|
||||||
|
v-3.5c0-0.1,0-0.1,0.1-0.2c0,0,0.1-0.1,0.2-0.1h1.5c0.1,0,0.1,0,0.2,0.1c0,0,0.1,0.1,0.1,0.2V16c0,0,0,0.1,0.1,0.1
|
||||||
|
c0,0,0.1,0.1,0.1,0.1h4c0.1,0,0.1,0,0.2,0.1c0,0,0.1,0.1,0.1,0.2v1.4c0,0.1,0,0.1-0.1,0.2c0,0-0.1,0.1-0.2,0.1h-4c0,0,0,0-0.1,0
|
||||||
|
c0,0,0,0-0.1,0c0,0,0,0,0,0.1c0,0,0,0,0,0.1v4c0,0.1,0,0.1-0.1,0.2C86.9,22.3,86.9,22.3,86.8,22.3z" />
|
||||||
|
<path class="st0" d="M83,16.2c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1-0.1l-1.5-2.8c0-0.1,0-0.1,0-0.2c0-0.1,0.1-0.1,0.1-0.1l1.4-0.7
|
||||||
|
c0.1,0,0.1,0,0.2,0c0.1,0,0.1,0.1,0.1,0.1l1.5,2.8c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.1-0.1,0.1-0.1,0.1L83,16.2
|
||||||
|
C83,16.2,83,16.2,83,16.2z" />
|
||||||
|
<path class="st0" d="M89,16.2c0,0-0.1,0-0.1,0l-1.2-1c0,0-0.1-0.1-0.1-0.1c0-0.1,0-0.1,0-0.2l2.1-2.5c0,0,0.1-0.1,0.1-0.1
|
||||||
|
c0.1,0,0.1,0,0.2,0l1.2,1c0,0,0.1,0.1,0.1,0.1c0,0.1,0,0.1,0,0.2l-2.1,2.5C89.1,16.1,89.1,16.2,89,16.2C89.1,16.2,89,16.2,89,16.2z" />
|
||||||
|
<path class="st0" d="M82.2,22c-0.1,0-0.1,0-0.1-0.1l-1.2-1c0,0,0,0-0.1-0.1c0,0,0-0.1,0-0.1c0-0.1,0-0.1,0.1-0.2l2.2-2.6
|
||||||
|
c0,0,0,0,0.1-0.1c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0l1.2,1c0,0,0,0,0.1,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1
|
||||||
|
c0,0,0,0.1,0,0.1l-2.2,2.6C82.4,21.9,82.3,21.9,82.2,22C82.3,22,82.3,22,82.2,22z" />
|
||||||
|
<path class="st0" d="M89.8,21.9C89.7,21.9,89.7,21.8,89.8,21.9c-0.1,0-0.2-0.1-0.2-0.1l-1.9-2.6c0,0,0,0,0-0.1c0,0,0-0.1,0-0.1
|
||||||
|
c0,0,0-0.1,0-0.1c0,0,0,0,0.1-0.1L89,18c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0,0,0.1,0.1l1.9,2.6c0,0,0,0.1,0,0.2
|
||||||
|
c0,0.1,0,0.1-0.1,0.1l-1.2,0.9C89.8,21.8,89.8,21.9,89.8,21.9z" />
|
||||||
|
<path class="st0" d="M91.6,30.2C91.6,30.2,91.7,30.2,91.6,30.2c0.1,0.1,0.1,0.1,0.1,0.2c0,0,0,0.1,0,0.1c0,0,0,0.1-0.1,0.1l-1.2,1
|
||||||
|
c-0.1,0.1-0.1,0.1-0.2,0.1c-0.1,0-0.2,0-0.3,0l-3.6-1.7c0,0-0.1,0-0.1,0s-0.1,0-0.1,0l-1.4,0.6c-0.6,0.3-1.3,0.5-1.9,0.7l-1.7,0.5
|
||||||
|
c-0.1,0-0.1,0-0.2,0c-0.1,0-0.1-0.1-0.1-0.1l-0.3-1.1c0-0.1,0-0.1,0-0.2c0-0.1,0-0.1,0-0.2c0-0.1,0.1-0.1,0.1-0.1
|
||||||
|
c0,0,0.1-0.1,0.2-0.1l1.8-0.6c0.3-0.1,0.7-0.2,1-0.4l0.4-0.2l-0.8-0.4l-1.8-0.8c0,0-0.1,0-0.1-0.1c0,0,0,0,0-0.1c0,0,0-0.1,0-0.1
|
||||||
|
c0,0,0-0.1,0-0.1l1-1.8c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1c0,0-0.1-0.1-0.1-0.1c0,0-0.1,0-0.1,0h-1.1c-0.1,0-0.2,0-0.3-0.1
|
||||||
|
c-0.1-0.1-0.1-0.2-0.1-0.3v-0.9c0-0.1,0-0.2,0.1-0.3c0.1-0.1,0.2-0.1,0.3-0.1h2.5l0.3-0.5c0,0,0-0.1,0.1-0.1c0,0,0.1,0,0.1,0H86
|
||||||
|
c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1L86,23.3h5.2c0.1,0,0.2,0,0.3,0.1c0.1,0.1,0.1,0.2,0.1,0.3v1.6
|
||||||
|
c0,0.1,0,0.2,0,0.2c0,0.1-0.1,0.1-0.1,0.2l-1,1c-0.7,0.7-1.4,1.3-2.2,1.8L91.6,30.2z M84.4,26.9l1.8,0.8c0,0,0.1,0,0.1,0
|
||||||
|
c0,0,0.1,0,0.1,0l0.9-0.6c0.6-0.4,1.1-0.9,1.7-1.4l0.6-0.6h-4.4c-0.1,0-0.2,0-0.2,0.1c-0.1,0-0.1,0.1-0.2,0.2l-0.6,1
|
||||||
|
c0,0,0,0.1-0.1,0.2c0,0.1,0,0.1,0,0.2c0,0.1,0,0.1,0.1,0.1C84.3,26.9,84.3,26.9,84.4,26.9z" />
|
||||||
|
<path class="st0" d="M98,28l2.1,2.3c0,0,0.1,0.1,0.1,0.2c0,0.1,0,0.1-0.1,0.2l-1.1,1c0,0,0,0-0.1,0c0,0-0.1,0-0.1,0
|
||||||
|
c-0.1,0-0.1,0-0.2-0.1L96.2,29c0,0-0.1,0-0.1-0.1c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1,0.1l-2.5,2.7c0,0-0.1,0.1-0.2,0.1
|
||||||
|
c-0.1,0-0.1,0-0.2-0.1l-1.1-1c0,0,0,0,0-0.1c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1l2-2.1l0.8-0.9c0,0,0.1-0.1,0.1-0.1
|
||||||
|
c0-0.1,0-0.1-0.1-0.1l-0.2-0.3c-0.8-1-1.3-2.2-1.6-3.5l-0.2-0.7c-0.1-0.7-0.2-1.3-0.2-2v-4c0-0.1,0-0.1,0.1-0.2c0,0,0,0,0.1,0
|
||||||
|
c0,0,0.1,0,0.1,0h1.5c0.1,0,0.1,0,0.2,0.1c0,0,0.1,0.1,0.1,0.2v3.8c0,0.6,0.1,1.3,0.2,1.9l0.1,0.3c0.2,1,0.6,2,1.2,2.9
|
||||||
|
c0,0,0,0,0.1,0c0,0,0,0,0.1,0c0,0,0,0,0.1,0c0,0,0,0,0.1,0c0.6-0.9,1-1.9,1.3-3l0-0.1c0.2-0.7,0.2-1.3,0.2-2v-4.4c0,0,0-0.1,0-0.1
|
||||||
|
c0,0-0.1,0-0.1,0h-4.9c-0.1,0-0.1,0-0.2-0.1c0,0-0.1-0.1-0.1-0.2v-2.9c0-0.1,0-0.1,0.1-0.2c0,0,0.1-0.1,0.2-0.1h1.5
|
||||||
|
c0.1,0,0.1,0,0.2,0.1c0,0,0.1,0.1,0.1,0.2v1.3h5c0.1,0,0.1,0,0.2,0.1c0,0,0.1,0.1,0.1,0.2v1.4v5c0,0.7-0.1,1.4-0.2,2.2l-0.2,0.7
|
||||||
|
c-0.3,1.2-0.8,2.4-1.6,3.4L97.4,27c0,0-0.1,0.1-0.1,0.2c0,0.1,0,0.1,0.1,0.2L98,28z" />
|
||||||
|
<path class="st1" d="M22,44c12.2,0,22-9.8,22-22C44,9.8,34.2,0,22,0C9.8,0,0,9.8,0,22C0,34.2,9.8,44,22,44z" />
|
||||||
|
<path class="st2" d="M32,11.3l-21.2,3.6c-0.2,0-0.4,0.1-0.6,0.3c-0.1,0.2-0.2,0.4-0.3,0.6l-0.2,1.8c0,0.2,0,0.3,0,0.4
|
||||||
|
c0.1,0.1,0.1,0.3,0.2,0.4c0.1,0.1,0.2,0.2,0.4,0.2c0.1,0,0.3,0.1,0.4,0l4.1-0.7v14.7c0,0.2,0,0.4,0.1,0.5c0.1,0.2,0.2,0.3,0.4,0.4
|
||||||
|
c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.4-0.1,0.5-0.2l1.6-1c0.2-0.1,0.3-0.3,0.4-0.5c0.1-0.2,0.2-0.4,0.2-0.6v-14l5.7-0.9v10.3
|
||||||
|
c0,0.6,0.2,1.3,0.5,1.8l2.5,4.7c0.1,0.1,0.2,0.3,0.3,0.3c0.1,0.1,0.3,0.1,0.4,0.1h2.2c0.1,0,0.3,0,0.4-0.1c0.1-0.1,0.2-0.2,0.3-0.3
|
||||||
|
c0.1-0.1,0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4l-2.5-4.9C28.2,27,28,26.4,28,25.7V15.7l4-0.7c0.2,0,0.4-0.1,0.5-0.3
|
||||||
|
c0.1-0.2,0.2-0.4,0.3-0.6l0.2-1.8c0-0.2,0-0.3,0-0.5s-0.1-0.3-0.2-0.4s-0.2-0.2-0.4-0.2C32.3,11.3,32.1,11.2,32,11.3z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.7 KiB |
197
projects/web-fastapi/popup_api/templates/index.html
Normal file
197
projects/web-fastapi/popup_api/templates/index.html
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Todolist</title>
|
||||||
|
<link rel="stylesheet" href="../static/css/simple-v1.min.css" />
|
||||||
|
<link rel="stylesheet" href="../static/css/style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<nav class="navbar">
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://sspai.com" class="logo"><img src="../static/sspai-logo-light.svg" alt="logo"></a>
|
||||||
|
</li>
|
||||||
|
<li><a href="#">Home</a></li>
|
||||||
|
<li><a href="#">Task</a></li>
|
||||||
|
<li><a href="#">About</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<hr />
|
||||||
|
<div class="data-table">
|
||||||
|
<p v-if="tasks.length === 0">
|
||||||
|
🤔 Whoops……似乎任务都做完了?那么可以点击下方的按钮添加任务!
|
||||||
|
</p>
|
||||||
|
<table v-else>
|
||||||
|
<!-- Fields -->
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th v-for="field, index in fields" :key="index" scope="col">
|
||||||
|
[[ mappingFields(field) ]]
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<!-- Data -->
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="task in tasks">
|
||||||
|
<td>[[ task.id ]]</td>
|
||||||
|
<td>[[ task.name ]]</td>
|
||||||
|
<td>[[ mappingPriority(task.priority) ]]</td>
|
||||||
|
<td>[[ task.description ]]</td>
|
||||||
|
<td>[[ isDone(task.is_done) ]]</td>
|
||||||
|
<td>[[ mappingGroups(task.group_id) ]]</td>
|
||||||
|
<td>
|
||||||
|
<div class="ops">
|
||||||
|
<span @click="updateTask(task.id)">更新</span>
|
||||||
|
<span @click="deleteTask(task.id)">删除</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="btn-add-task" @click="showForm">
|
||||||
|
<button>+</button>
|
||||||
|
</div>
|
||||||
|
<div class="task-form">
|
||||||
|
<form v-bind:action="apis.createTask" method="post" class="form">
|
||||||
|
<div>
|
||||||
|
<label for="taskName">任务名称:</label>
|
||||||
|
<input type="text" name="taskName" id="taskName" placeholder="请输入待办事项名称" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="taskPriority">优先级:</label>
|
||||||
|
<select name="taskPriority" id="taskPriority">
|
||||||
|
<option value="1" selected>一般</option>
|
||||||
|
<option value="2">优先</option>
|
||||||
|
<option value="3">紧急</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="taskGroup"> 分组: </label>
|
||||||
|
<select name="taskGroup" id="taskGroup">
|
||||||
|
<option :value="index" v-for="([index, group]) in Object.entries(groups)">
|
||||||
|
[[ group ]]
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="taskDescription">任务备注:</label>
|
||||||
|
<textarea name="taskDescription" id="taskDescription" rows="5" placeholder="备注内容(可选)"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="btn-submit">
|
||||||
|
<input ref="submit" type="submit" value="添加" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<footer id="footer">
|
||||||
|
<p class="copyright">© 100gle & sspai</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="../static/js/vue.min.js"></script>
|
||||||
|
<script src="../static/js/axios.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const address = "http://localhost:8000";
|
||||||
|
const app = new Vue({
|
||||||
|
el: ".container",
|
||||||
|
delimiters: ["[[", "]]"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tasks: [],
|
||||||
|
fields: [],
|
||||||
|
apis: {
|
||||||
|
getAllTasks: `${address}/api/tasks`,
|
||||||
|
createTask: `${address}/api/tasks/create`,
|
||||||
|
updateTask: `${address}/api/tasks/update`,
|
||||||
|
deleteTask: `${address}/api/tasks/delete`,
|
||||||
|
getGroups: `${address}/api/groups`
|
||||||
|
},
|
||||||
|
groups: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showForm(e) {
|
||||||
|
const formDiv = document.querySelector(".task-form");
|
||||||
|
if (formDiv.style.display === "") {
|
||||||
|
formDiv.style.display = "none";
|
||||||
|
}
|
||||||
|
formDiv.style.display = formDiv.style.display === "none" ? "flex" : "none";
|
||||||
|
},
|
||||||
|
getAllTasks() {
|
||||||
|
axios.get(this.apis.getAllTasks)
|
||||||
|
.then(res => {
|
||||||
|
let json = res.data.data
|
||||||
|
this.tasks = json
|
||||||
|
|
||||||
|
let fields = Object.keys(json[0])
|
||||||
|
fields.push("operators")
|
||||||
|
|
||||||
|
this.fields = fields
|
||||||
|
})
|
||||||
|
.catch(err => console.error)
|
||||||
|
},
|
||||||
|
updateTask(task_id) {
|
||||||
|
axios.put(`${this.apis.updateTask}/${task_id}`).then(res => {
|
||||||
|
this.getAllTasks();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteTask(task_id) {
|
||||||
|
axios.delete(`${this.apis.deleteTask}/${task_id}`).then(res => {
|
||||||
|
this.getAllTasks();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
mappingFields(field) {
|
||||||
|
let mapping = {
|
||||||
|
id: "序号",
|
||||||
|
name: "任务名称",
|
||||||
|
priority: "优先级",
|
||||||
|
description: "任务描述",
|
||||||
|
is_done: "是否完成",
|
||||||
|
group_id: "分组",
|
||||||
|
operators: "操作",
|
||||||
|
}
|
||||||
|
return mapping[field];
|
||||||
|
},
|
||||||
|
isDone(done) {
|
||||||
|
return done === 1 ? "完成" : "未完成"
|
||||||
|
},
|
||||||
|
mappingPriority(value) {
|
||||||
|
let mapping = {
|
||||||
|
0: "一般",
|
||||||
|
1: "优先",
|
||||||
|
3: "紧急"
|
||||||
|
}
|
||||||
|
return mapping[value]
|
||||||
|
},
|
||||||
|
mappingGroups(value) {
|
||||||
|
return this.groups[value]
|
||||||
|
},
|
||||||
|
getGroups() {
|
||||||
|
axios.get(this.apis.getGroups)
|
||||||
|
.then(res => {
|
||||||
|
let data = res.data.data
|
||||||
|
let mappings = {}
|
||||||
|
data.reduce((id, group) => {
|
||||||
|
Object.assign(mappings, {
|
||||||
|
[group.id]: group.name
|
||||||
|
})
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
this.groups = mappings
|
||||||
|
})
|
||||||
|
.catch(err => console.error)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted: function (e) {
|
||||||
|
this.getAllTasks();
|
||||||
|
this.getGroups();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
30
projects/web-fastapi/tests/conftest.py
Normal file
30
projects/web-fastapi/tests/conftest.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm.session import sessionmaker
|
||||||
|
|
||||||
|
# isort: off
|
||||||
|
from popup_api.main import app
|
||||||
|
from popup_api.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
engine = create_engine(
|
||||||
|
url=settings.DATABASE_URL,
|
||||||
|
echo=settings.DATABASE_ECHO,
|
||||||
|
connect_args=settings.DATABASE_CONNECT_ARGS,
|
||||||
|
)
|
||||||
|
TestSession = sessionmaker(bind=engine)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="package")
|
||||||
|
def client():
|
||||||
|
yield TestClient(app)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def db():
|
||||||
|
sess = TestSession()
|
||||||
|
try:
|
||||||
|
yield sess
|
||||||
|
finally:
|
||||||
|
sess.close()
|
||||||
103
projects/web-fastapi/tests/test_api.py
Normal file
103
projects/web-fastapi/tests/test_api.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from popup_api.models import Task
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_all_tasks(client: TestClient):
|
||||||
|
resp = client.get("/api/tasks")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
data = resp.json()
|
||||||
|
assert data["code"] == 0
|
||||||
|
assert data["message"] == "query all tasks successfully."
|
||||||
|
assert len(data["data"]) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_all_groups(client: TestClient):
|
||||||
|
resp = client.get("/api/groups")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
data = resp.json()
|
||||||
|
assert data["code"] == 0
|
||||||
|
assert data["message"] == "query all groups successfully."
|
||||||
|
assert len(data["data"]) > 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"data",
|
||||||
|
[
|
||||||
|
dict(
|
||||||
|
taskName="测试 1",
|
||||||
|
taskPriority=1,
|
||||||
|
taskDescription="测试描述",
|
||||||
|
taskGroup=0,
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
taskName="测试 2",
|
||||||
|
taskPriority=2,
|
||||||
|
taskDescription="测试一下最长的情况如何" * 100,
|
||||||
|
taskGroup=1,
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
taskName="测试 3",
|
||||||
|
taskPriority=1,
|
||||||
|
taskDescription="测试描述",
|
||||||
|
taskGroup=-1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_create_task(client: TestClient, db, data):
|
||||||
|
resp = client.post("/api/tasks/create", data=data)
|
||||||
|
assert resp.status_code == 307
|
||||||
|
|
||||||
|
tasks = db.query(Task).filter(Task.name.like("%测试%")).all()
|
||||||
|
assert len(tasks) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_task(client: TestClient, db):
|
||||||
|
pre_resp = client.get("/api/tasks")
|
||||||
|
pre_json = pre_resp.json()["data"]
|
||||||
|
task_id = pre_json[0]["id"]
|
||||||
|
is_done = pre_json[0]["is_done"]
|
||||||
|
|
||||||
|
resp = client.put(f"/api/tasks/update/{task_id}")
|
||||||
|
json = resp.json()
|
||||||
|
assert json["data"]["id"] == task_id
|
||||||
|
assert json["message"] == f"update #{task_id} task successfully."
|
||||||
|
|
||||||
|
task = db.get(Task, task_id)
|
||||||
|
assert task.is_done == (not is_done)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("invalid_id", [-1, -10])
|
||||||
|
def test_update_task_with_invalid_task_id(client: TestClient, invalid_id: int):
|
||||||
|
resp = client.put(f"/api/tasks/update/{invalid_id}")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
json = resp.json()
|
||||||
|
assert json["code"] == 10001
|
||||||
|
assert json["message"] == f"can't found the task which id is: {invalid_id}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_task(client: TestClient, db):
|
||||||
|
pre_resp = client.get("/api/tasks")
|
||||||
|
pre_json = pre_resp.json()["data"]
|
||||||
|
task_id = pre_json[0]["id"]
|
||||||
|
resp = client.delete(f"/api/tasks/delete/{task_id}")
|
||||||
|
|
||||||
|
json = resp.json()
|
||||||
|
assert json["data"]["id"] == task_id
|
||||||
|
assert json["message"] == f"delete #{task_id} task successfully."
|
||||||
|
|
||||||
|
task = db.get(Task, task_id)
|
||||||
|
assert not task
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("invalid_id", [-1, -10])
|
||||||
|
def test_delete_task_With_invalid_task_id(client: TestClient, invalid_id):
|
||||||
|
resp = client.delete(f"/api/tasks/delete/{invalid_id}")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
json = resp.json()
|
||||||
|
assert json["code"] == 10001
|
||||||
|
assert json["message"] == f"can't found the task which id is: {invalid_id}"
|
||||||
20
projects/web-fastapi/tests/test_schema.py
Normal file
20
projects/web-fastapi/tests/test_schema.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from popup_api.schema import Response
|
||||||
|
|
||||||
|
|
||||||
|
def test_Response():
|
||||||
|
|
||||||
|
resp1 = Response(code=200, message="success", data=[{"foo": 1}])
|
||||||
|
assert resp1.dict() == dict(code=200, message="success", data=[{"foo": 1}])
|
||||||
|
assert resp1.json() == '{"code": 200, "message": "success", "data": [{"foo": 1}]}'
|
||||||
|
|
||||||
|
resp2 = Response(code=200, message="success", data=[])
|
||||||
|
assert resp2.dict() == dict(code=200, message="success", data=[])
|
||||||
|
assert resp2.json() == '{"code": 200, "message": "success", "data": []}'
|
||||||
|
|
||||||
|
resp3 = Response(code=200, message="success", data="foo")
|
||||||
|
assert resp3.dict() == dict(code=200, message="success", data="foo")
|
||||||
|
assert resp3.json() == '{"code": 200, "message": "success", "data": "foo"}'
|
||||||
|
|
||||||
|
resp4 = Response(code=400, message="failed")
|
||||||
|
assert resp4.dict() == dict(code=400, message="failed", data=None)
|
||||||
|
assert resp4.json() == '{"code": 400, "message": "failed", "data": null}'
|
||||||
10
projects/web-fastapi/typehint/basic.py
Normal file
10
projects/web-fastapi/typehint/basic.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
name: str = "100gle"
|
||||||
|
number: int = 1
|
||||||
|
yes_or_no: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
def greet(name: str = '') -> str:
|
||||||
|
if not name:
|
||||||
|
name = "world"
|
||||||
|
|
||||||
|
return f"Hello, {name}"
|
||||||
39
projects/web-fastapi/typehint/pydantic_usage.py
Normal file
39
projects/web-fastapi/typehint/pydantic_usage.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import random
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
random.seed(233)
|
||||||
|
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
name: str
|
||||||
|
email: Optional[str] = None
|
||||||
|
age: int
|
||||||
|
telephone: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
users = {
|
||||||
|
1: User(name="John", age=42),
|
||||||
|
2: User(name="Jane", age=36, email="jane@example.com"),
|
||||||
|
3: User(name="Jack", age=40, telephone="555-555-5555"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/users/create/", response_model=User)
|
||||||
|
def create_user(user: User):
|
||||||
|
user_id = random.randint(4, 100)
|
||||||
|
users[user_id] = user
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/users", response_model=List[User])
|
||||||
|
def get_users():
|
||||||
|
return [user for user in users.values()]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
uvicorn.run("pydantic_usage:app")
|
||||||
Reference in New Issue
Block a user