Compare commits
2
Commits
6eee3f885f
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db6f713ba6 | ||
|
|
e7a9a8adec |
@@ -20,7 +20,7 @@ STACK_NAME=BaiPanSou
|
|||||||
BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.tiangolo.com"
|
BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.tiangolo.com"
|
||||||
SECRET_KEY=baomihua200712
|
SECRET_KEY=baomihua200712
|
||||||
FIRST_SUPERUSER=mzaxd0712@gmail.com
|
FIRST_SUPERUSER=mzaxd0712@gmail.com
|
||||||
FIRST_SUPERUSER_PASSWORD=baomihua8434386
|
FIRST_SUPERUSER_PASSWORD=200712
|
||||||
|
|
||||||
# Emails
|
# Emails
|
||||||
SMTP_HOST=
|
SMTP_HOST=
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from app.core import security
|
|||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.security import get_password_hash
|
from app.core.security import get_password_hash
|
||||||
from app.models import Message, NewPassword, Token, UserPublic
|
from app.models import Message, NewPassword, Token, UserPublic
|
||||||
from app.utils.common import (
|
from app.utils import (
|
||||||
generate_password_reset_token,
|
generate_password_reset_token,
|
||||||
generate_reset_password_email,
|
generate_reset_password_email,
|
||||||
send_email,
|
send_email,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from app.models import (
|
|||||||
UserUpdate,
|
UserUpdate,
|
||||||
UserUpdateMe,
|
UserUpdateMe,
|
||||||
)
|
)
|
||||||
from app.utils.common import generate_new_account_email, send_email
|
from app.utils import generate_new_account_email, send_email
|
||||||
|
|
||||||
router = APIRouter(prefix="/users", tags=["users"])
|
router = APIRouter(prefix="/users", tags=["users"])
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from pydantic.networks import EmailStr
|
|||||||
|
|
||||||
from app.api.deps import get_current_active_superuser
|
from app.api.deps import get_current_active_superuser
|
||||||
from app.models import Message
|
from app.models import Message
|
||||||
from app.utils.common import generate_test_email, send_email
|
from app.utils import generate_test_email, send_email
|
||||||
|
|
||||||
router = APIRouter(prefix="/utils", tags=["utils"])
|
router = APIRouter(prefix="/utils", tags=["utils"])
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import uuid
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
from app.core.security import get_password_hash
|
from app.core.security import get_password_hash, verify_password
|
||||||
from app.models import User, UserCreate, UserUpdate
|
from app.models import Item, ItemCreate, User, UserCreate, UserUpdate
|
||||||
|
|
||||||
|
|
||||||
def create_user(*, session: Session, user_create: UserCreate) -> User:
|
def create_user(*, session: Session, user_create: UserCreate) -> User:
|
||||||
@@ -34,3 +35,20 @@ def get_user_by_email(*, session: Session, email: str) -> User | None:
|
|||||||
statement = select(User).where(User.email == email)
|
statement = select(User).where(User.email == email)
|
||||||
session_user = session.exec(statement).first()
|
session_user = session.exec(statement).first()
|
||||||
return session_user
|
return session_user
|
||||||
|
|
||||||
|
|
||||||
|
def authenticate(*, session: Session, email: str, password: str) -> User | None:
|
||||||
|
db_user = get_user_by_email(session=session, email=email)
|
||||||
|
if not db_user:
|
||||||
|
return None
|
||||||
|
if not verify_password(password, db_user.hashed_password):
|
||||||
|
return None
|
||||||
|
return db_user
|
||||||
|
|
||||||
|
|
||||||
|
def create_item(*, session: Session, item_in: ItemCreate, owner_id: uuid.UUID) -> Item:
|
||||||
|
db_item = Item.model_validate(item_in, update={"owner_id": owner_id})
|
||||||
|
session.add(db_item)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(db_item)
|
||||||
|
return db_item
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!doctype html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office"><head><title></title><!--[if !mso]><!-- --><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]--><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style type="text/css">#outlook a { padding:0; }
|
||||||
|
.ReadMsgBody { width:100%; }
|
||||||
|
.ExternalClass { width:100%; }
|
||||||
|
.ExternalClass * { line-height:100%; }
|
||||||
|
body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }
|
||||||
|
table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }
|
||||||
|
img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }
|
||||||
|
p { display:block;margin:13px 0; }</style><!--[if !mso]><!--><style type="text/css">@media only screen and (max-width:480px) {
|
||||||
|
@-ms-viewport { width:320px; }
|
||||||
|
@viewport { width:320px; }
|
||||||
|
}</style><!--<![endif]--><!--[if mso]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG/>
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]--><!--[if lte mso 11]>
|
||||||
|
<style type="text/css">
|
||||||
|
.outlook-group-fix { width:100% !important; }
|
||||||
|
</style>
|
||||||
|
<![endif]--><!--[if !mso]><!--><link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css"><style type="text/css">@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);</style><!--<![endif]--><style type="text/css">@media only screen and (min-width:480px) {
|
||||||
|
.mj-column-per-100 { width:100% !important; max-width: 100%; }
|
||||||
|
}</style><style type="text/css"></style></head><body style="background-color:#fafbfc;"><div style="background-color:#fafbfc;"><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="background:#ffffff;background-color:#ffffff;Margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:40px 20px;text-align:center;vertical-align:top;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:middle;width:560px;" ><![endif]--><div class="mj-column-per-100 outlook-group-fix" style="font-size:13px;text-align:left;direction:ltr;display:inline-block;vertical-align:middle;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:middle;" width="100%"><tr><td align="center" style="font-size:0px;padding:35px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:center;color:#333333;">{{ project_name }} - New Account</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;"><span>Welcome to your new account!</span></div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">Here are your account details:</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">Username: {{ username }}</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">Password: {{ password }}</div></td></tr><tr><td align="center" vertical-align="middle" style="font-size:0px;padding:15px 30px;word-break:break-word;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;"><tr><td align="center" bgcolor="#009688" role="presentation" style="border:none;border-radius:8px;cursor:auto;padding:10px 25px;background:#009688;" valign="middle"><a href="{{ link }}" style="background:#009688;color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:18px;font-weight:normal;line-height:120%;Margin:0;text-decoration:none;text-transform:none;" target="_blank">Go to Dashboard</a></td></tr></table></td></tr><tr><td style="font-size:0px;padding:10px 25px;word-break:break-word;"><p style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:100%;"></p><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;">
|
||||||
|
</td></tr></table><![endif]--></td></tr></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></div></body></html>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!doctype html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office"><head><title></title><!--[if !mso]><!-- --><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]--><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style type="text/css">#outlook a { padding:0; }
|
||||||
|
.ReadMsgBody { width:100%; }
|
||||||
|
.ExternalClass { width:100%; }
|
||||||
|
.ExternalClass * { line-height:100%; }
|
||||||
|
body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }
|
||||||
|
table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }
|
||||||
|
img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }
|
||||||
|
p { display:block;margin:13px 0; }</style><!--[if !mso]><!--><style type="text/css">@media only screen and (max-width:480px) {
|
||||||
|
@-ms-viewport { width:320px; }
|
||||||
|
@viewport { width:320px; }
|
||||||
|
}</style><!--<![endif]--><!--[if mso]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG/>
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]--><!--[if lte mso 11]>
|
||||||
|
<style type="text/css">
|
||||||
|
.outlook-group-fix { width:100% !important; }
|
||||||
|
</style>
|
||||||
|
<![endif]--><!--[if !mso]><!--><link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css"><style type="text/css">@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);</style><!--<![endif]--><style type="text/css">@media only screen and (min-width:480px) {
|
||||||
|
.mj-column-per-100 { width:100% !important; max-width: 100%; }
|
||||||
|
}</style><style type="text/css"></style></head><body style="background-color:#fafbfc;"><div style="background-color:#fafbfc;"><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="background:#ffffff;background-color:#ffffff;Margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:40px 20px;text-align:center;vertical-align:top;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:middle;width:560px;" ><![endif]--><div class="mj-column-per-100 outlook-group-fix" style="font-size:13px;text-align:left;direction:ltr;display:inline-block;vertical-align:middle;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:middle;" width="100%"><tr><td align="center" style="font-size:0px;padding:35px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:20px;line-height:1;text-align:center;color:#333333;">{{ project_name }} - Password Recovery</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;"><span>Hello {{ username }}</span></div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">We've received a request to reset your password. You can do it by clicking the button below:</div></td></tr><tr><td align="center" vertical-align="middle" style="font-size:0px;padding:15px 30px;word-break:break-word;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;"><tr><td align="center" bgcolor="#009688" role="presentation" style="border:none;border-radius:8px;cursor:auto;padding:10px 25px;background:#009688;" valign="middle"><a href="{{ link }}" style="background:#009688;color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:18px;font-weight:normal;line-height:120%;Margin:0;text-decoration:none;text-transform:none;" target="_blank">Reset password</a></td></tr></table></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">Or copy and paste the following link into your browser:</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;"><a href="{{ link }}">{{ link }}</a></div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">This password will expire in {{ valid_hours }} hours.</div></td></tr><tr><td style="font-size:0px;padding:10px 25px;word-break:break-word;"><p style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:100%;"></p><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;">
|
||||||
|
</td></tr></table><![endif]--></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:14px;line-height:1;text-align:center;color:#555555;">If you didn't request a password recovery you can disregard this email.</div></td></tr></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></div></body></html>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!doctype html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office"><head><title></title><!--[if !mso]><!-- --><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]--><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style type="text/css">#outlook a { padding:0; }
|
||||||
|
.ReadMsgBody { width:100%; }
|
||||||
|
.ExternalClass { width:100%; }
|
||||||
|
.ExternalClass * { line-height:100%; }
|
||||||
|
body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }
|
||||||
|
table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }
|
||||||
|
img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }
|
||||||
|
p { display:block;margin:13px 0; }</style><!--[if !mso]><!--><style type="text/css">@media only screen and (max-width:480px) {
|
||||||
|
@-ms-viewport { width:320px; }
|
||||||
|
@viewport { width:320px; }
|
||||||
|
}</style><!--<![endif]--><!--[if mso]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG/>
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]--><!--[if lte mso 11]>
|
||||||
|
<style type="text/css">
|
||||||
|
.outlook-group-fix { width:100% !important; }
|
||||||
|
</style>
|
||||||
|
<![endif]--><style type="text/css">@media only screen and (min-width:480px) {
|
||||||
|
.mj-column-per-100 { width:100% !important; max-width: 100%; }
|
||||||
|
}</style><style type="text/css"></style></head><body style="background-color:#fafbfc;"><div style="background-color:#fafbfc;"><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="background:#ffffff;background-color:#ffffff;Margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:40px 20px;text-align:center;vertical-align:top;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:middle;width:560px;" ><![endif]--><div class="mj-column-per-100 outlook-group-fix" style="font-size:13px;text-align:left;direction:ltr;display:inline-block;vertical-align:middle;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:middle;" width="100%"><tr><td align="center" style="font-size:0px;padding:35px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:20px;line-height:1;text-align:center;color:#333333;">{{ project_name }}</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;"><span>Test email for: {{ email }}</span></div></td></tr><tr><td style="font-size:0px;padding:10px 25px;word-break:break-word;"><p style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:100%;"></p><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;">
|
||||||
|
</td></tr></table><![endif]--></td></tr></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></div></body></html>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<mjml>
|
||||||
|
<mj-body background-color="#fafbfc">
|
||||||
|
<mj-section background-color="#fff" padding="40px 20px">
|
||||||
|
<mj-column vertical-align="middle" width="100%">
|
||||||
|
<mj-text align="center" padding="35px" font-size="20px" color="#333">{{ project_name }} - New Account</mj-text>
|
||||||
|
<mj-text align="center" font-size="16px" padding-left="25px" padding-right="25px" font-family="Arial, Helvetica, sans-serif" color="#555"><span>Welcome to your new account!</span></mj-text>
|
||||||
|
<mj-text align="center" font-size="16px" padding-left="25px" padding-right="25px" font-family="Arial, Helvetica, sans-serif" color="#555">Here are your account details:</mj-text>
|
||||||
|
<mj-text align="center" font-size="16px" padding-left="25px" padding-right="25px" font-family="Arial, Helvetica, sans-serif" color="#555">Username: {{ username }}</mj-text>
|
||||||
|
<mj-text align="center" font-size="16px" padding-left="25px" padding-right="25px" font-family="Arial, Helvetica, sans-serif" color="#555">Password: {{ password }}</mj-text>
|
||||||
|
<mj-button align="center" font-size="18px" background-color="#009688" border-radius="8px" color="#fff" href="{{ link }}" padding="15px 30px">Go to Dashboard</mj-button>
|
||||||
|
<mj-divider border-color="#ccc" border-width="2px"></mj-divider>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
</mj-body>
|
||||||
|
</mjml>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<mjml>
|
||||||
|
<mj-body background-color="#fafbfc">
|
||||||
|
<mj-section background-color="#fff" padding="40px 20px">
|
||||||
|
<mj-column vertical-align="middle" width="100%">
|
||||||
|
<mj-text align="center" padding="35px" font-size="20px" font-family="Arial, Helvetica, sans-serif" color="#333">{{ project_name }} - Password Recovery</mj-text>
|
||||||
|
<mj-text align="center" font-size="16px" padding-left="25px" padding-right="25px" font-family="Arial, Helvetica, sans-serif" color="#555"><span>Hello {{ username }}</span></mj-text>
|
||||||
|
<mj-text align="center" font-size="16px" padding-left="25px" padding-right="25px" font-family="Arial, Helvetica, sans-serif" color="#555">We've received a request to reset your password. You can do it by clicking the button below:</mj-text>
|
||||||
|
<mj-button align="center" font-size="18px" background-color="#009688" border-radius="8px" color="#fff" href="{{ link }}" padding="15px 30px">Reset password</mj-button>
|
||||||
|
<mj-text align="center" font-size="16px" padding-left="25px" padding-right="25px" font-family="Arial, Helvetica, sans-serif" color="#555">Or copy and paste the following link into your browser:</mj-text>
|
||||||
|
<mj-text align="center" font-size="16px" padding-left="25px" padding-right="25px" font-family="Arial, Helvetica, sans-serif" color="#555"><a href="{{ link }}">{{ link }}</a></mj-text>
|
||||||
|
<mj-text align="center" font-size="16px" padding-left="25px" padding-right="25px" font-family="Arial, Helvetica, sans-serif" color="#555">This password will expire in {{ valid_hours }} hours.</mj-text>
|
||||||
|
<mj-divider border-color="#ccc" border-width="2px"></mj-divider>
|
||||||
|
<mj-text align="center" font-size="14px" padding-left="25px" padding-right="25px" font-family="Arial, Helvetica, sans-serif" color="#555">If you didn't request a password recovery you can disregard this email.</mj-text>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
</mj-body>
|
||||||
|
</mjml>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<mjml>
|
||||||
|
<mj-body background-color="#fafbfc">
|
||||||
|
<mj-section background-color="#fff" padding="40px 20px">
|
||||||
|
<mj-column vertical-align="middle" width="100%">
|
||||||
|
<mj-text align="center" padding="35px" font-size="20px" font-family="Arial, Helvetica, sans-serif" color="#333">{{ project_name }}</mj-text>
|
||||||
|
<mj-text align="center" font-size="16px" padding-left="25px" padding-right="25px" font-family=", sans-serif" color="#555"><span>Test email for: {{ email }}</span></mj-text>
|
||||||
|
<mj-divider border-color="#ccc" border-width="2px"></mj-divider>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
</mj-body>
|
||||||
|
</mjml>
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from pydantic import EmailStr
|
from pydantic import EmailStr
|
||||||
from sqlmodel import Field, Relationship, SQLModel
|
from sqlmodel import Field, Relationship, SQLModel
|
||||||
|
|
||||||
from app.models.item import Item
|
|
||||||
|
|
||||||
|
|
||||||
# Shared properties
|
# Shared properties
|
||||||
class UserBase(SQLModel):
|
class UserBase(SQLModel):
|
||||||
@@ -44,7 +43,7 @@ class UpdatePassword(SQLModel):
|
|||||||
class User(UserBase, table=True):
|
class User(UserBase, table=True):
|
||||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||||
hashed_password: str
|
hashed_password: str
|
||||||
items: list[Item] = Relationship(back_populates="owner", cascade_delete=True)
|
items: list["Item"] = Relationship(back_populates="owner", cascade_delete=True)
|
||||||
|
|
||||||
|
|
||||||
# Properties to return via API, id is always required
|
# Properties to return via API, id is always required
|
||||||
@@ -55,3 +54,60 @@ class UserPublic(UserBase):
|
|||||||
class UsersPublic(SQLModel):
|
class UsersPublic(SQLModel):
|
||||||
data: list[UserPublic]
|
data: list[UserPublic]
|
||||||
count: int
|
count: int
|
||||||
|
|
||||||
|
|
||||||
|
# Shared properties
|
||||||
|
class ItemBase(SQLModel):
|
||||||
|
title: str = Field(min_length=1, max_length=255)
|
||||||
|
description: str | None = Field(default=None, max_length=255)
|
||||||
|
|
||||||
|
|
||||||
|
# Properties to receive on item creation
|
||||||
|
class ItemCreate(ItemBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Properties to receive on item update
|
||||||
|
class ItemUpdate(ItemBase):
|
||||||
|
title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
# Database model, database table inferred from class name
|
||||||
|
class Item(ItemBase, table=True):
|
||||||
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||||
|
owner_id: uuid.UUID = Field(
|
||||||
|
foreign_key="user.id", nullable=False, ondelete="CASCADE"
|
||||||
|
)
|
||||||
|
owner: User | None = Relationship(back_populates="items")
|
||||||
|
|
||||||
|
|
||||||
|
# Properties to return via API, id is always required
|
||||||
|
class ItemPublic(ItemBase):
|
||||||
|
id: uuid.UUID
|
||||||
|
owner_id: uuid.UUID
|
||||||
|
|
||||||
|
|
||||||
|
class ItemsPublic(SQLModel):
|
||||||
|
data: list[ItemPublic]
|
||||||
|
count: int
|
||||||
|
|
||||||
|
|
||||||
|
# Generic message
|
||||||
|
class Message(SQLModel):
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
# JSON payload containing access token
|
||||||
|
class Token(SQLModel):
|
||||||
|
access_token: str
|
||||||
|
token_type: str = "bearer"
|
||||||
|
|
||||||
|
|
||||||
|
# Contents of JWT token
|
||||||
|
class TokenPayload(SQLModel):
|
||||||
|
sub: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class NewPassword(SQLModel):
|
||||||
|
token: str
|
||||||
|
new_password: str = Field(min_length=8, max_length=40)
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
from sqlmodel import SQLModel, Field
|
|
||||||
|
|
||||||
|
|
||||||
# JSON payload containing access token
|
|
||||||
class Token(SQLModel):
|
|
||||||
access_token: str
|
|
||||||
token_type: str = "bearer"
|
|
||||||
|
|
||||||
|
|
||||||
# Contents of JWT token
|
|
||||||
class TokenPayload(SQLModel):
|
|
||||||
sub: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class NewPassword(SQLModel):
|
|
||||||
token: str
|
|
||||||
new_password: str = Field(min_length=8, max_length=40)
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import uuid
|
|
||||||
from sqlmodel import Field, Relationship, SQLModel
|
|
||||||
|
|
||||||
from app.models.user import User
|
|
||||||
|
|
||||||
|
|
||||||
# Shared properties
|
|
||||||
class ItemBase(SQLModel):
|
|
||||||
title: str = Field(min_length=1, max_length=255)
|
|
||||||
description: str | None = Field(default=None, max_length=255)
|
|
||||||
|
|
||||||
|
|
||||||
# Properties to receive on item creation
|
|
||||||
class ItemCreate(ItemBase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Properties to receive on item update
|
|
||||||
class ItemUpdate(ItemBase):
|
|
||||||
title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
# Database model, database table inferred from class name
|
|
||||||
class Item(ItemBase, table=True):
|
|
||||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
||||||
owner_id: uuid.UUID = Field(
|
|
||||||
foreign_key="user.id", nullable=False, ondelete="CASCADE"
|
|
||||||
)
|
|
||||||
owner: User | None = Relationship(back_populates="items")
|
|
||||||
|
|
||||||
|
|
||||||
# Properties to return via API, id is always required
|
|
||||||
class ItemPublic(ItemBase):
|
|
||||||
id: uuid.UUID
|
|
||||||
owner_id: uuid.UUID
|
|
||||||
|
|
||||||
|
|
||||||
class ItemsPublic(SQLModel):
|
|
||||||
data: list[ItemPublic]
|
|
||||||
count: int
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import uuid
|
|
||||||
from sqlmodel import Session
|
|
||||||
|
|
||||||
from app.models import Item, ItemCreate
|
|
||||||
|
|
||||||
|
|
||||||
def create_item(*, session: Session, item_in: ItemCreate, owner_id: uuid.UUID) -> Item:
|
|
||||||
db_item = Item.model_validate(item_in, update={"owner_id": owner_id})
|
|
||||||
session.add(db_item)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(db_item)
|
|
||||||
return db_item
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
from sqlmodel import Session
|
|
||||||
|
|
||||||
from app.core.security import verify_password
|
|
||||||
from app.repositories.user import get_user_by_email
|
|
||||||
|
|
||||||
|
|
||||||
def authenticate(*, session: Session, email: str, password: str) -> User | None:
|
|
||||||
db_user = get_user_by_email(session=session, email=email)
|
|
||||||
if not db_user:
|
|
||||||
return None
|
|
||||||
if not verify_password(password, db_user.hashed_password):
|
|
||||||
return None
|
|
||||||
return db_user
|
|
||||||
@@ -9,7 +9,7 @@ from app.crud import create_user
|
|||||||
from app.models import UserCreate
|
from app.models import UserCreate
|
||||||
from app.tests.utils.user import user_authentication_headers
|
from app.tests.utils.user import user_authentication_headers
|
||||||
from app.tests.utils.utils import random_email, random_lower_string
|
from app.tests.utils.utils import random_email, random_lower_string
|
||||||
from app.utils.common import generate_password_reset_token
|
from app.utils import generate_password_reset_token
|
||||||
|
|
||||||
|
|
||||||
def test_get_access_token(client: TestClient) -> None:
|
def test_get_access_token(client: TestClient) -> None:
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
pydantic>=2.8.2
|
|
||||||
sqlmodel>=0.0.14
|
|
||||||
python-jose[cryptography]>=3.3.0
|
|
||||||
passlib[bcrypt]>=1.7.4
|
|
||||||
python-multipart>=0.0.6
|
|
||||||
email-validator>=2.1.0.post1
|
|
||||||
Generated
+1536
-1797
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@
|
|||||||
"@tanstack/react-query": "^5.28.14",
|
"@tanstack/react-query": "^5.28.14",
|
||||||
"@tanstack/react-query-devtools": "^5.28.14",
|
"@tanstack/react-query-devtools": "^5.28.14",
|
||||||
"@tanstack/react-router": "1.19.1",
|
"@tanstack/react-router": "1.19.1",
|
||||||
"axios": "^1.8.4",
|
"axios": "1.7.4",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@@ -37,6 +37,6 @@
|
|||||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^6.2.3"
|
"vite": "^5.4.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user