Skip to content
Merged

Dev #39

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 62 additions & 9 deletions .github/workflows/cd-frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,96 @@ on:
paths:
- "frontend/**"
- ".github/workflows/cd-frontend.yml"
- "!backend/**"

workflow_run:
workflows: ["Deploy Backend"]
types:
- completed

jobs:
deploy-frontend:
runs-on: ubuntu-latest

if: |
github.event_name == 'push' ||
(github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success')

defaults:
run:
working-directory: ./frontend

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
- name: Download OpenAPI schema from deployed backend
run: |
curl -f -sS ${{ vars.API_URL }}/openapi.json -o openapi.json

- name: Compute checksum of OpenAPI schema
run: |
sha256sum openapi.json | awk '{print $1}' > openapi.sha256

- name: Download last OpenAPI checksum artifact
id: download
continue-on-error: true
uses: actions/download-artifact@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
name: openapi-checksum
path: frontend/openapi-checksum

- name: Download OpenAPI schema from deployed backend
- name: Decide whether frontend deploy is needed
id: should-deploy
run: |
curl -s ${{ vars.API_URL }}/openapi.json -o frontend/openapi.json
if [ "${{ github.event_name }}" = "push" ]; then
echo "deploy=true" >> $GITHUB_OUTPUT
exit 0
fi

if [ ! -f openapi-checksum/openapi.sha256 ]; then
echo "deploy=true" >> $GITHUB_OUTPUT
elif cmp -s openapi.sha256 openapi-checksum/openapi.sha256; then
echo "deploy=false" >> $GITHUB_OUTPUT
else
echo "deploy=true" >> $GITHUB_OUTPUT
fi

- name: Build React frontend
id: build-frontend
if: steps.should-deploy.outputs.deploy == 'true'
env:
VITE_API_URL: ${{ vars.API_URL }}
working-directory: ./frontend
run: |
npm ci
npm run generate-client
npm run build

- name: Configure AWS credentials
if: steps.should-deploy.outputs.deploy == 'true'
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}

- name: Sync frontend build to S3
working-directory: ./frontend
if: steps.should-deploy.outputs.deploy == 'true'
run: |
aws s3 sync dist/ s3://${{ secrets.S3_BUCKET_NAME }} --delete

- name: Invalidate CloudFront cache
if: steps.should-deploy.outputs.deploy == 'true'
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DIST_ID }} \
--paths "/*"

- name: Upload OpenAPI checksum artifact
if: steps.should-deploy.outputs.deploy == 'true'
uses: actions/upload-artifact@v4
with:
name: openapi-checksum
path: frontend/openapi.sha256
retention-days: 90
7 changes: 6 additions & 1 deletion backend/app/core/domain/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from datetime import UTC, datetime
from enum import Enum

from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, computed_field


class OAuthProvider(str, Enum):
Expand All @@ -23,3 +23,8 @@ class User(BaseModel):
time_create: datetime = Field(default_factory=lambda: datetime.now(UTC))
time_update: datetime = Field(default_factory=lambda: datetime.now(UTC))
is_active: bool = False

@computed_field # type: ignore[prop-decorator]
@property
def is_password_set(self) -> bool:
return self.password is not None
1 change: 1 addition & 0 deletions backend/app/core/dto/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class UserRead(BaseModelDTO):
time_create: datetime
time_update: datetime
is_active: bool = True
is_password_set: bool


class UserLogin(BaseModelDTO):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ async def update(self, user_id: int, **update_data) -> User | None:
return User.model_validate(updated_user, from_attributes=True) if updated_user else None

async def create(self, user: User) -> User:
user_model = self.model(**user.model_dump())
user_model = self.model(**user.model_dump(exclude_computed_fields=True))
self.session.add(user_model)
await self.session.flush()
return User.model_validate(user_model, from_attributes=True)
Expand Down
67 changes: 38 additions & 29 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@hey-api/client-axios": "^0.9.1",
"@hookform/resolvers": "^3.9.1",
"@hookform/resolvers": "^5.2.2",
"@mui/icons-material": "^6.4.7",
"@mui/material": "^6.4.7",
"@mui/x-date-pickers": "^8.10.2",
Expand All @@ -35,7 +35,7 @@
"@apidevtools/json-schema-ref-parser": "^11.9.3",
"@eslint/js": "^9.15.0",
"@hey-api/openapi-ts": "0.80.18",
"@hookform/devtools": "^4.3.3",
"@hookform/devtools": "^4.4.0",
"@tanstack/router-plugin": "^1.133.13",
"@types/node": "^24.2.1",
"@types/react": "^18.3.12",
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/entities/user/ui/PasswordField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ import { zUserCreate } from 'shared/api/gen/zod.gen';

const passwordHelp = zUserCreate.shape.password.description;

const PasswordField: FieldComponent = ({ label = 'Password', ...props }) => {
const PasswordField: FieldComponent = ({
label = 'Password',
helperText,
...props
}) => {
const [showPassword, setShowPassword] = useState(false);
const handleClickShowPassword = () => setShowPassword((show) => !show);
const controller = useController(props);
return (
<TextInput
label={label}
helperText={passwordHelp}
helperText={helperText ?? passwordHelp}
type={showPassword ? 'text' : 'password'}
slotProps={{
input: {
Expand Down
18 changes: 11 additions & 7 deletions frontend/src/features/user/ui/AccountMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import Tooltip from '@mui/material/Tooltip';
import { useState } from 'react';
import Link from '@mui/material/Link';
import { getRouteApi, Link as RouterLink } from '@tanstack/react-router';
import { getRouteApi } from '@tanstack/react-router';
import { LinkButton } from 'shared/ui/LinkButton';

const MenuStyle = {
paper: {
Expand Down Expand Up @@ -77,14 +77,18 @@ export function AccountMenu() {
slotProps={MenuStyle}
transformOrigin={{ horizontal: 'center', vertical: 'top' }}
anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }}>
<Divider />
<MenuItem onClick={handleClose}>
<Link
component={RouterLink}
<LinkButton
to='/profile'
underline='none'
sx={{ display: 'flex', alignItems: 'center', color: 'inherit' }}>
sx={{
display: 'flex',
alignItems: 'center',
color: 'inherit',
padding: 0,
}}>
<Avatar /> Profile
</Link>
</LinkButton>
</MenuItem>
<Divider />
<MenuItem
Expand Down
Loading
Loading