Compare commits
23 Commits
e6b56e3b40
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| cb89d2c3be | |||
| 8ac3a2a27a | |||
| 73fa85b9f1 | |||
| 5832bb6b3c | |||
| 18f0f78de0 | |||
| 8cd87a2741 | |||
| 2c013eee24 | |||
|
|
8f47e551b0 | ||
| a3ec4f2c78 | |||
|
|
df56e15290 | ||
|
|
a78146f25f | ||
| 16c0c24fae | |||
| 9b2f9f6da1 | |||
| 6264778ea0 | |||
| 953edb3d0c | |||
|
|
5eb89d3b99 | ||
|
|
55b6265985 | ||
|
|
214bc1ae85 | ||
|
|
633496a033 | ||
| ac841ba8e0 | |||
| b938f296c1 | |||
| 1c205c69ac | |||
| e6923277ed |
34
.dockerignore
Normal file
34
.dockerignore
Normal file
@@ -0,0 +1,34 @@
|
||||
.git
|
||||
.github
|
||||
.dockerignore
|
||||
.gitattributes
|
||||
.gitignore
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
htmlcov/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.pylintrc
|
||||
.pytest_cache/
|
||||
.venv
|
||||
venv
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
._*
|
||||
|
||||
*/node_modules
|
||||
*/dist
|
||||
*/data/db
|
||||
*/mealie/test
|
||||
*/mealie/.temp
|
||||
|
||||
model.crfmodel
|
||||
|
||||
crowdin.yml
|
||||
21
.gitea/workflows/test-self.yaml
Normal file
21
.gitea/workflows/test-self.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Gitea Actions Demo
|
||||
run-name: ${{ gitea.actor }} is fuware be STG
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [develop]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: self-hosted
|
||||
defaults:
|
||||
run:
|
||||
working-directory: /home/raysam/act-runner-host/repo
|
||||
steps:
|
||||
- run: git --version
|
||||
- run: rm -rf fuware-be
|
||||
- run: git clone ssh://git@thamluu.synology.me:222/sam/fuware-be.git fuware-be
|
||||
- name: run docker
|
||||
working-directory: /home/raysam/act-runner-host/repo/fuware-be
|
||||
run: git switch develop && docker compose up --build -d
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
104
Dockerfile
Normal file
104
Dockerfile
Normal file
@@ -0,0 +1,104 @@
|
||||
###############################################
|
||||
# Base Image - Python
|
||||
###############################################
|
||||
FROM python:3.10-slim AS python-base
|
||||
|
||||
ENV FUWARE_HOME="/app"
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PIP_NO_CACHE_DIR=off \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||
PIP_DEFAULT_TIMEOUT=100 \
|
||||
POETRY_HOME="/opt/poetry" \
|
||||
POETRY_VIRTUALENVS_IN_PROJECT=true \
|
||||
POETRY_NO_INTERACTION=1 \
|
||||
PYSETUP_PATH="/opt/pysetup" \
|
||||
VENV_PATH="/opt/pysetup/.venv"
|
||||
|
||||
# prepend poetry and venv to path
|
||||
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
|
||||
|
||||
# create user account
|
||||
RUN useradd -u 911 -U -d $FUWARE_HOME -s /bin/bash abc \
|
||||
&& usermod -G users abc \
|
||||
&& mkdir $FUWARE_HOME
|
||||
|
||||
###############################################
|
||||
# Builder Image
|
||||
###############################################
|
||||
FROM python-base AS builder-base
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
curl \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
libwebp-dev \
|
||||
# LDAP Dependencies
|
||||
libsasl2-dev libldap2-dev libssl-dev \
|
||||
gnupg gnupg2 gnupg1 \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& pip install -U --no-cache-dir pip
|
||||
|
||||
# install poetry - respects $POETRY_VERSION & $POETRY_HOME
|
||||
ENV POETRY_VERSION=1.3.1
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
# copy project requirement files here to ensure they will be cached.
|
||||
WORKDIR $PYSETUP_PATH
|
||||
COPY ./poetry.lock ./pyproject.toml ./
|
||||
|
||||
# install runtime deps - uses $POETRY_VIRTUALENVS_IN_PROJECT internally
|
||||
RUN poetry install --only main
|
||||
|
||||
###############################################
|
||||
# Production Image
|
||||
###############################################
|
||||
FROM python-base AS production
|
||||
ENV PRODUCTION=true
|
||||
ENV TESTING=false
|
||||
|
||||
ARG COMMIT
|
||||
ENV GIT_COMMIT_HASH=$COMMIT
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
gosu \
|
||||
iproute2 \
|
||||
libldap-common \
|
||||
libldap-2.5 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# create directory used for Docker Secrets
|
||||
RUN mkdir -p /run/secrets
|
||||
|
||||
# copying poetry and venv into image
|
||||
COPY --from=builder-base $POETRY_HOME $POETRY_HOME
|
||||
COPY --from=builder-base $PYSETUP_PATH $PYSETUP_PATH
|
||||
|
||||
# copy backend
|
||||
COPY ./backend $FUWARE_HOME/backend
|
||||
COPY ./poetry.lock ./pyproject.toml $FUWARE_HOME/
|
||||
|
||||
# Alembic
|
||||
COPY ./alembic $FUWARE_HOME/alembic
|
||||
COPY ./alembic.ini $FUWARE_HOME/
|
||||
|
||||
# venv already has runtime deps installed we get a quicker install
|
||||
WORKDIR $FUWARE_HOME
|
||||
RUN . $VENV_PATH/bin/activate && poetry install --only main
|
||||
WORKDIR /
|
||||
|
||||
VOLUME [ "$FUWARE_HOME/data/" ]
|
||||
ENV APP_PORT=9000
|
||||
|
||||
EXPOSE ${APP_PORT}
|
||||
|
||||
# ----------------------------------
|
||||
ENV HOST 0.0.0.0
|
||||
|
||||
EXPOSE ${APP_PORT}
|
||||
COPY ./docker/entry.sh $FUWARE_HOME/run.sh
|
||||
|
||||
RUN chmod +x $FUWARE_HOME/run.sh
|
||||
ENTRYPOINT ["/app/run.sh"]
|
||||
13
Taskfile.yml
13
Taskfile.yml
@@ -21,13 +21,8 @@ tasks:
|
||||
desc: runs the backend server
|
||||
cmds:
|
||||
- poetry run python backend/app.py
|
||||
fe:i:
|
||||
desc: runs the install package
|
||||
dir: frontend
|
||||
docker:
|
||||
desc: builds and runs the production docker image locally
|
||||
dir: docker
|
||||
cmds:
|
||||
- pnpm i
|
||||
fe:
|
||||
desc: runs the frontend server
|
||||
dir: frontend
|
||||
cmds:
|
||||
- pnpm dev
|
||||
- docker compose -f docker-compose.yml -p fuware up -d --build
|
||||
|
||||
@@ -53,6 +53,7 @@ def run_migrations_offline() -> None:
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
render_as_batch=True
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
|
||||
@@ -7,6 +7,7 @@ Create Date: ${create_date}
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import backend.db.migration_types
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
64
alembic/versions/0fbca538155d_create_house_and_area_table.py
Normal file
64
alembic/versions/0fbca538155d_create_house_and_area_table.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""create house and area table
|
||||
|
||||
Revision ID: 0fbca538155d
|
||||
Revises: 4b90a6ac504b
|
||||
Create Date: 2024-06-30 05:38:20.062935
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import backend.db.migration_types
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '0fbca538155d'
|
||||
down_revision: Union[str, None] = '4b90a6ac504b'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('houses',
|
||||
sa.Column('id', backend.db.models.guid.GUID(), nullable=False),
|
||||
sa.Column('icon', sa.String(), nullable=False),
|
||||
sa.Column('name', sa.String(), nullable=False),
|
||||
sa.Column('address', sa.String(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_houses_created_at'), 'houses', ['created_at'], unique=False)
|
||||
op.create_index(op.f('ix_houses_id'), 'houses', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_houses_name'), 'houses', ['name'], unique=False)
|
||||
op.create_table('areas',
|
||||
sa.Column('id', backend.db.models.guid.GUID(), nullable=False),
|
||||
sa.Column('house_id', backend.db.models.guid.GUID(), nullable=False),
|
||||
sa.Column('name', sa.String(), nullable=False),
|
||||
sa.Column('desc', sa.String(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['house_id'], ['houses.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_areas_created_at'), 'areas', ['created_at'], unique=False)
|
||||
op.create_index(op.f('ix_areas_id'), 'areas', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_areas_name'), 'areas', ['name'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_areas_name'), table_name='areas')
|
||||
op.drop_index(op.f('ix_areas_id'), table_name='areas')
|
||||
op.drop_index(op.f('ix_areas_created_at'), table_name='areas')
|
||||
op.drop_table('areas')
|
||||
op.drop_index(op.f('ix_houses_name'), table_name='houses')
|
||||
op.drop_index(op.f('ix_houses_id'), table_name='houses')
|
||||
op.drop_index(op.f('ix_houses_created_at'), table_name='houses')
|
||||
op.drop_table('houses')
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,18 +1,19 @@
|
||||
"""create user table
|
||||
"""create users table
|
||||
|
||||
Revision ID: 68d05d045e6e
|
||||
Revises:
|
||||
Create Date: 2024-05-24 04:12:25.599139
|
||||
Revision ID: 4b90a6ac504b
|
||||
Revises:
|
||||
Create Date: 2024-06-25 09:14:34.465698
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import backend.db.migration_types
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '68d05d045e6e'
|
||||
revision: str = '4b90a6ac504b'
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
@@ -21,12 +22,12 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('users',
|
||||
sa.Column('id', sa.UUID(), nullable=False),
|
||||
sa.Column('id', backend.db.models.guid.GUID(), nullable=False),
|
||||
sa.Column('username', sa.String(), nullable=False),
|
||||
sa.Column('password', sa.String(), nullable=False),
|
||||
sa.Column('name', sa.String(), nullable=True),
|
||||
sa.Column('is_admin', sa.Boolean(), nullable=True),
|
||||
sa.Column('is_lock', sa.Boolean(), nullable=True),
|
||||
sa.Column('is_lock', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
@@ -1,6 +1,7 @@
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
@@ -15,7 +16,7 @@ settings = get_app_settings()
|
||||
logger = get_logger()
|
||||
|
||||
description = f"""
|
||||
fuware is a web application for managing your hours items and tracking them.
|
||||
fuware is a web application for managing your house items and tracking them.
|
||||
"""
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -52,16 +53,15 @@ app = FastAPI(
|
||||
|
||||
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
||||
|
||||
if not settings.PRODUCTION:
|
||||
allowed_origins = ["http://localhost:3000"]
|
||||
allowed_origins = ["http://localhost:5001", "https://stg.samliu.io.vn"]
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=allowed_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=allowed_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@app.exception_handler(HTTPException)
|
||||
async def unicorn_exception_handler(request: Request, exc: HTTPException):
|
||||
@@ -70,6 +70,11 @@ async def unicorn_exception_handler(request: Request, exc: HTTPException):
|
||||
content={"status": exc.status_code, "data": exc.detail},
|
||||
)
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
print(exc)
|
||||
return JSONResponse(status_code=422, content={"status": 422, "data": str(exc)})
|
||||
|
||||
def api_routers():
|
||||
app.include_router(router)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ async def is_logged_in(token: str = Depends(oauth2_scheme_soft_fail)) -> bool:
|
||||
user = user_service.get_by_id(user_id)
|
||||
if not user:
|
||||
raise credentials_exception
|
||||
if user.is_lock is True:
|
||||
if user.is_lock is not None:
|
||||
raise HTTPException(status_code=status.HTTP_423_LOCKED, detail=MessageCode.ACCOUNT_LOCK)
|
||||
except Exception:
|
||||
return credentials_exception
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"level": "DEBUG",
|
||||
"formatter": "detailed",
|
||||
"filename": "${DATA_DIR}/mealie.log",
|
||||
"filename": "data/fuware.log",
|
||||
"maxBytes": 10000,
|
||||
"backupCount": 3
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
|
||||
class MessageCode():
|
||||
CREATED_USER: str = 'CREATED_USER'
|
||||
WRONG_INPUT: str = 'LOGIN_WRONG'
|
||||
ACCOUNT_LOCK: str = 'USER_LOCK'
|
||||
CREATE_USER_SUCCESS: str = 'message_create_user_success'
|
||||
CREATED_USER: str = 'message_created_user'
|
||||
WRONG_INPUT: str = 'message_login_wrong'
|
||||
ACCOUNT_LOCK: str = 'message_user_lock'
|
||||
REFRESH_TOKEN_EXPIRED: str = 'REFRESH_TOKEN_EXPIRED'
|
||||
CREATE_HOUSE_FAIL: str = 'message_create_house_fail'
|
||||
CREATE_HOUSE_SUCCESS: str = 'message_create_house_success'
|
||||
HOUSE_NOT_FOUND: str = 'HOUSE_NOT_FOUND'
|
||||
|
||||
@@ -12,6 +12,7 @@ from backend.core.config import get_app_settings
|
||||
from backend.db.db_setup import session_context
|
||||
from backend.repos.repository_users import RepositoryUsers
|
||||
from backend.repos.seeder import default_users_init
|
||||
from backend.repos.seeder.init_house import default_house_init
|
||||
|
||||
PROJECT_DIR = Path(__file__).parent.parent.parent
|
||||
|
||||
@@ -20,6 +21,7 @@ logger = root_logger.get_logger()
|
||||
def init_db(db) -> None:
|
||||
logger.info("Initializing user data...")
|
||||
default_users_init(db)
|
||||
default_house_init(db)
|
||||
|
||||
def db_is_at_head(alembic_cfg: config.Config) -> bool:
|
||||
settings = get_app_settings()
|
||||
|
||||
1
backend/db/migration_types.py
Normal file
1
backend/db/migration_types.py
Normal file
@@ -0,0 +1 @@
|
||||
from backend.db.models.guid import GUID # noqa: F401
|
||||
@@ -1 +1,2 @@
|
||||
from .users import *
|
||||
from .houses import *
|
||||
|
||||
@@ -18,3 +18,6 @@ class SqlAlchemyBase(Model):
|
||||
@classmethod
|
||||
def normalize(cls, val: str) -> str:
|
||||
return unidecode(val).lower().strip()
|
||||
|
||||
class DeleteMixin:
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
56
backend/db/models/guid.py
Normal file
56
backend/db/models/guid.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import Dialect
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.types import CHAR, TypeDecorator
|
||||
|
||||
|
||||
class GUID(TypeDecorator):
|
||||
"""Platform-independent GUID type.
|
||||
Uses PostgreSQL's UUID type, otherwise uses
|
||||
CHAR(32), storing as stringified hex values.
|
||||
"""
|
||||
|
||||
impl = CHAR
|
||||
cache_ok = True
|
||||
|
||||
@staticmethod
|
||||
def generate():
|
||||
return uuid.uuid4()
|
||||
|
||||
@staticmethod
|
||||
def convert_value_to_guid(value: Any, dialect: Dialect) -> str | None:
|
||||
if value is None:
|
||||
return value
|
||||
elif dialect.name == "postgresql":
|
||||
return str(value)
|
||||
else:
|
||||
if not isinstance(value, uuid.UUID):
|
||||
return "%.32x" % uuid.UUID(value).int
|
||||
else:
|
||||
# hexstring
|
||||
return "%.32x" % value.int
|
||||
|
||||
def load_dialect_impl(self, dialect):
|
||||
if dialect.name == "postgresql":
|
||||
return dialect.type_descriptor(UUID())
|
||||
else:
|
||||
return dialect.type_descriptor(CHAR(32))
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
return self.convert_value_to_guid(value, dialect)
|
||||
|
||||
def _uuid_value(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
else:
|
||||
if not isinstance(value, uuid.UUID):
|
||||
value = uuid.UUID(value)
|
||||
return value
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
return self._uuid_value(value)
|
||||
|
||||
def sort_key_function(self, value):
|
||||
return self._uuid_value(value)
|
||||
1
backend/db/models/houses/__init__.py
Normal file
1
backend/db/models/houses/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .houses import *
|
||||
33
backend/db/models/houses/houses.py
Normal file
33
backend/db/models/houses/houses.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from typing import List
|
||||
from sqlalchemy import ForeignKey, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from backend.db.models.guid import GUID
|
||||
|
||||
from .._model_base import SqlAlchemyBase, DeleteMixin
|
||||
|
||||
class Houses(SqlAlchemyBase, DeleteMixin):
|
||||
__tablename__ = 'houses'
|
||||
|
||||
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate, index=True)
|
||||
icon: Mapped[str | None] = mapped_column(String, nullable=False)
|
||||
name: Mapped[str | None] = mapped_column(String, index=True, nullable=False)
|
||||
address: Mapped[str | None] = mapped_column(String, nullable=False)
|
||||
|
||||
areas: Mapped[List["Areas"]] = relationship("Areas", back_populates="house", cascade="all, delete", passive_deletes=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}, name: {self.name}"
|
||||
|
||||
class Areas(SqlAlchemyBase, DeleteMixin):
|
||||
__tablename__ = 'areas'
|
||||
|
||||
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate, index=True)
|
||||
house_id: Mapped[GUID] = mapped_column(GUID, ForeignKey('houses.id', ondelete='CASCADE'), nullable=False)
|
||||
name: Mapped[str | None] = mapped_column(String, index=True, nullable=False)
|
||||
desc: Mapped[str | None] = mapped_column(String, nullable=False)
|
||||
|
||||
house: Mapped['Houses'] = relationship(back_populates="areas")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} id={self.id} name={self.name} house_id={self.house_id}, desc={self.desc}>"
|
||||
@@ -1,20 +1,21 @@
|
||||
from uuid import uuid4
|
||||
from sqlalchemy import Boolean, ForeignKey, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy import Boolean, String, DateTime
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from backend.db.models.guid import GUID
|
||||
|
||||
from .._model_base import SqlAlchemyBase
|
||||
|
||||
class User(SqlAlchemyBase):
|
||||
class Users(SqlAlchemyBase):
|
||||
__tablename__ = 'users'
|
||||
|
||||
id: Mapped[UUID] = mapped_column(UUID, primary_key=True, default=uuid4, index=True)
|
||||
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate, index=True)
|
||||
username: Mapped[str | None] = mapped_column(String, unique=True, index=True, nullable=False)
|
||||
password: Mapped[str | None] = mapped_column(String, index=True, nullable=False)
|
||||
name: Mapped[str | None] = mapped_column(String, index=True, nullable=True)
|
||||
is_admin: Mapped[bool | None] = mapped_column(Boolean, default=False)
|
||||
is_lock: Mapped[bool | None] = mapped_column(Boolean, default=False)
|
||||
is_lock: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}, name: {self.name}, username: {self.username}"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from .repository_users import *
|
||||
from .repository_houses import *
|
||||
|
||||
90
backend/repos/repository_houses.py
Normal file
90
backend/repos/repository_houses.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from datetime import datetime
|
||||
from backend.db.models.houses import Houses, Areas
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from backend.schemas.house import HouseCreate, HouseUpdate, AreaUpdate
|
||||
|
||||
class RepositoryHouses:
|
||||
def __init__(self):
|
||||
self.houses = Houses()
|
||||
self.areas = Areas()
|
||||
|
||||
def get_all(self, skip: int = 0, limit: int = 100):
|
||||
return self.houses.query.filter_by(deleted_at=None).offset(skip*limit).limit(limit).all()
|
||||
|
||||
def get_all_areas(self, house_id: str, skip: int = 0, limit: int = 100):
|
||||
return self.areas.query.filter_by(deleted_at=None, house_id=house_id).offset(skip).limit(limit).all()
|
||||
|
||||
def get_count_all(self):
|
||||
return self.houses.query.filter_by(deleted_at=None).count()
|
||||
|
||||
def get_by_id(self, house_id: str):
|
||||
return self.houses.query.filter_by(id=house_id).one()
|
||||
|
||||
def create(self, db: Session, house: HouseCreate):
|
||||
try:
|
||||
areas = getattr(house, "areas")
|
||||
db_house = Houses(**house.dict(exclude={"areas"}))
|
||||
for area in areas:
|
||||
db_house.areas.append(Areas(**area.dict()))
|
||||
db.add(db_house)
|
||||
db.commit()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
|
||||
db.refresh(db_house)
|
||||
return db_house
|
||||
|
||||
def update_area(self, db: Session, areas: list[AreaUpdate], house_id: str):
|
||||
db_house = self.get_by_id(house_id)
|
||||
if not db_house:
|
||||
return None
|
||||
try:
|
||||
db_areaId = [area.id for area in db_house.areas]
|
||||
area_update_id = [area.id for area in areas if area.id is not None]
|
||||
area_delete_id = [area_id for area_id in db_areaId if area_id not in area_update_id]
|
||||
if area_delete_id:
|
||||
self.areas.query.where(Areas.id.in_(area_delete_id)).delete()
|
||||
|
||||
for area in areas:
|
||||
area_obj = Areas(**area.dict(), house_id=house_id)
|
||||
db.merge(area_obj)
|
||||
|
||||
# areaList = [dict(**area.dict(), house_id=house_id) for area in areas]
|
||||
# db.execute(update(Areas), areaList)
|
||||
# self.areas.query.update(areaList)
|
||||
db.commit()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
|
||||
|
||||
def update(self, db: Session, house: HouseUpdate):
|
||||
db_house = self.get_by_id(house.id)
|
||||
if not db_house:
|
||||
return None
|
||||
try:
|
||||
self.houses.query.where(Houses.id == house.id).update(house.dict(exclude={"id", 'areas'}, exclude_unset=True, exclude_none=True))
|
||||
db.commit()
|
||||
self.update_area(db, house.areas, house.id)
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
|
||||
db.refresh(db_house)
|
||||
return db_house
|
||||
|
||||
def delete(self, db: Session, house_id: str):
|
||||
db_house = self.get_by_id(house_id)
|
||||
if not db_house:
|
||||
return None
|
||||
try:
|
||||
self.houses.query.where(Houses.id == house_id).update({"deleted_at": datetime.utcnow()})
|
||||
db.commit()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
|
||||
db.refresh(db_house)
|
||||
return db_house
|
||||
@@ -1,6 +1,6 @@
|
||||
from backend.core.config import get_app_settings
|
||||
from backend.core.security.security import hash_password
|
||||
from backend.db.models import User
|
||||
from backend.db.models import Users
|
||||
from backend.schemas import UserCreate, UserProfile
|
||||
from sqlalchemy.orm import Session
|
||||
from uuid import UUID
|
||||
@@ -11,7 +11,7 @@ settings = get_app_settings()
|
||||
|
||||
class RepositoryUsers:
|
||||
def __init__(self):
|
||||
self.user = User()
|
||||
self.user = Users()
|
||||
|
||||
def get_all(self, skip: int = 0, limit: int = 100):
|
||||
return self.user.query.offset(skip).limit(limit).all()
|
||||
@@ -25,7 +25,7 @@ class RepositoryUsers:
|
||||
def create(self, db: Session, user: UserCreate | UserSeeds):
|
||||
try:
|
||||
password = getattr(user, "password")
|
||||
db_user = User(**user.dict(exclude={"password"}), password=hash_password(password))
|
||||
db_user = Users(**user.dict(exclude={"password"}), password=hash_password(password))
|
||||
db.add(db_user)
|
||||
db.commit()
|
||||
except Exception:
|
||||
@@ -41,7 +41,7 @@ class RepositoryUsers:
|
||||
return None
|
||||
try:
|
||||
user.update_password()
|
||||
self.user.query.where(User.id == user_id).update(user.dict(exclude_unset=True, exclude_none=True))
|
||||
self.user.query.where(Users.id == user_id).update(user.dict(exclude_unset=True, exclude_none=True))
|
||||
db.commit()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from .init_users import default_users_init
|
||||
from .init_house import default_house_init
|
||||
|
||||
26
backend/repos/seeder/init_house.py
Normal file
26
backend/repos/seeder/init_house.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from backend.core.config import get_app_settings
|
||||
from backend.core.root_logger import get_logger
|
||||
from backend.repos.repository_houses import RepositoryHouses
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from backend.schemas.house.house import HouseCreate
|
||||
|
||||
|
||||
logger = get_logger("init_house")
|
||||
settings = get_app_settings()
|
||||
|
||||
def dev_houses() -> list[dict]:
|
||||
list = []
|
||||
for x in range(20):
|
||||
list.append({
|
||||
"icon": "IconAccessible",
|
||||
"name": f"Home{x+1}",
|
||||
"address": f"Address{x+1}",
|
||||
"areas": [{"name": f"Area{x+1}", "desc": "Description"}],
|
||||
})
|
||||
return list
|
||||
|
||||
def default_house_init(session: Session):
|
||||
houses = RepositoryHouses()
|
||||
for house in dev_houses():
|
||||
houses.create(session, HouseCreate(**house))
|
||||
@@ -12,25 +12,16 @@ settings = get_app_settings()
|
||||
def dev_users() -> list[dict]:
|
||||
return [
|
||||
{
|
||||
"username": "sam",
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
"name": "Sam",
|
||||
"is_admin": True,
|
||||
"is_lock": False,
|
||||
},
|
||||
{
|
||||
"username": "duy",
|
||||
"password": "admin",
|
||||
"name": "Duy",
|
||||
"is_admin": True,
|
||||
"is_lock": False,
|
||||
},
|
||||
{
|
||||
"username": "sam1",
|
||||
"username": "admin1",
|
||||
"password": "admin",
|
||||
"name": "Sam1",
|
||||
"is_admin": False,
|
||||
"is_lock": False,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
|
||||
from . import (auth, user)
|
||||
from . import (auth, user, house)
|
||||
|
||||
router = APIRouter(prefix='/api')
|
||||
|
||||
router.include_router(auth.router)
|
||||
router.include_router(user.router)
|
||||
router.include_router(house.router)
|
||||
|
||||
7
backend/routes/house/__init__.py
Normal file
7
backend/routes/house/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
from fastapi import APIRouter
|
||||
from . import house
|
||||
|
||||
router = APIRouter(prefix='/house')
|
||||
|
||||
router.include_router(house.public_router)
|
||||
50
backend/routes/house/house.py
Normal file
50
backend/routes/house/house.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from typing import Annotated, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from backend.core.config import get_app_settings
|
||||
from backend.core.dependencies import is_logged_in
|
||||
from backend.core.message_code import MessageCode
|
||||
from backend.db.db_setup import generate_session
|
||||
from backend.schemas.common import ReturnValue
|
||||
from backend.schemas.house import HouseCreate
|
||||
from backend.schemas.house.house import HouseUpdate, HousesListResponse, HouseResponse
|
||||
from backend.schemas.user import ProfileResponse
|
||||
from backend.services.house import HouseService
|
||||
|
||||
public_router = APIRouter(tags=["Houses: Control"])
|
||||
house_service = HouseService()
|
||||
settings = get_app_settings()
|
||||
|
||||
db_dependency = Annotated[Session, Depends(generate_session)]
|
||||
current_user_token = Annotated[ProfileResponse, Depends(is_logged_in)]
|
||||
|
||||
@public_router.post("/create", response_model=ReturnValue[Any])
|
||||
def create_house(house: HouseCreate, db: db_dependency, current_user: current_user_token) -> ReturnValue[Any]:
|
||||
try:
|
||||
house_service.create(db=db, house=house)
|
||||
except Exception:
|
||||
raise HTTPException(status_code=400, detail=MessageCode.CREATE_HOUSE_FAIL)
|
||||
return ReturnValue(status=200, data=MessageCode.CREATE_HOUSE_SUCCESS)
|
||||
|
||||
@public_router.get("/all", response_model=ReturnValue[HousesListResponse])
|
||||
def get_all_house(page: int, pageSize: int, current_user: current_user_token) -> ReturnValue[HousesListResponse]:
|
||||
housesCount = house_service.get_all_count()
|
||||
houses = house_service.get_all(skip=page-1, limit=pageSize)
|
||||
return ReturnValue(status=200, data={'total': housesCount, 'list': list(houses)})
|
||||
|
||||
@public_router.get("/{house_id}", response_model=ReturnValue[HouseResponse])
|
||||
def get_house_by_id(house_id: str, current_user: current_user_token) -> ReturnValue[HouseCreate]:
|
||||
house = house_service.get_by_id(id=house_id)
|
||||
if not house:
|
||||
raise HTTPException(status_code=404, detail=MessageCode.HOUSE_NOT_FOUND)
|
||||
return ReturnValue(status=200, data=house)
|
||||
|
||||
@public_router.put("/update", response_model=ReturnValue[HouseCreate])
|
||||
def update_house(house: HouseUpdate, current_user: current_user_token, db: db_dependency) -> ReturnValue[Any]:
|
||||
db_house = house_service.update(db=db, house=house)
|
||||
return ReturnValue(status=200, data=db_house)
|
||||
|
||||
@public_router.delete("/delete/{house_id}", response_model=ReturnValue[Any])
|
||||
def delete_house(house_id: str, current_user: current_user_token, db: db_dependency) -> ReturnValue[Any]:
|
||||
db_house = house_service.delete(db=db, house_id=house_id)
|
||||
return ReturnValue(status=200, data="Deleted")
|
||||
@@ -23,7 +23,7 @@ def register_user(user: UserCreate, db: db_dependency) -> ReturnValue[Any]:
|
||||
if db_user:
|
||||
raise HTTPException(status_code=400, detail=MessageCode.CREATED_USER)
|
||||
user_service.create(db=db, user=user)
|
||||
return ReturnValue(status=200, data="created")
|
||||
return ReturnValue(status=200, data=MessageCode.CREATE_USER_SUCCESS)
|
||||
|
||||
@public_router.get("/me", response_model=ReturnValue[ProfileResponse])
|
||||
def get_user(current_user: current_user_token) -> ReturnValue[Any]:
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
from .common import *
|
||||
from .user import *
|
||||
from .house import *
|
||||
|
||||
1
backend/schemas/house/__init__.py
Normal file
1
backend/schemas/house/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .house import *
|
||||
42
backend/schemas/house/house.py
Normal file
42
backend/schemas/house/house.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import ConfigDict
|
||||
from backend.schemas.main_model import MainModel
|
||||
|
||||
class HouseBase(MainModel):
|
||||
icon: str
|
||||
name: str
|
||||
address: str
|
||||
|
||||
class AreaBase(MainModel):
|
||||
name: str
|
||||
desc: str
|
||||
|
||||
class AreaUpdate(AreaBase):
|
||||
id: UUID | None = None
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
class HouseCreate(HouseBase):
|
||||
areas: list[AreaBase]
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
class HouseUpdate(HouseBase):
|
||||
id: UUID
|
||||
areas: list[AreaUpdate]
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
class HouseResponse(HouseBase):
|
||||
id: UUID
|
||||
areas: list[AreaUpdate]
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
class HousesList(HouseBase):
|
||||
id: UUID
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
deleted_at: datetime | None
|
||||
|
||||
class HousesListResponse(MainModel):
|
||||
total: int
|
||||
list: list[HousesList]
|
||||
@@ -27,13 +27,12 @@ class UserProfile(MainModel):
|
||||
|
||||
class UserSeeds(UserCreate):
|
||||
is_admin: bool
|
||||
is_lock: bool
|
||||
|
||||
class PrivateUser(UserBase):
|
||||
id: UUID
|
||||
name: str
|
||||
is_admin: bool
|
||||
is_lock: bool
|
||||
is_lock: datetime
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
1
backend/services/house/__init__.py
Normal file
1
backend/services/house/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .house_service import *
|
||||
26
backend/services/house/house_service.py
Normal file
26
backend/services/house/house_service.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from backend.repos import RepositoryHouses
|
||||
from backend.schemas import HouseCreate, HouseUpdate
|
||||
from backend.services._base_service import BaseService
|
||||
|
||||
class HouseService(BaseService):
|
||||
def __init__(self):
|
||||
self.repos = RepositoryHouses()
|
||||
|
||||
def create(self, db: Session, house: HouseCreate):
|
||||
return self.repos.create(db=db, house=house)
|
||||
|
||||
def get_all(self, skip: int = 0, limit: int = 100):
|
||||
return self.repos.get_all(skip=skip, limit=limit)
|
||||
|
||||
def get_all_count(self):
|
||||
return self.repos.get_count_all()
|
||||
|
||||
def get_by_id(self, id: str):
|
||||
return self.repos.get_by_id(house_id=id)
|
||||
|
||||
def update(self, db: Session, house: HouseUpdate):
|
||||
return self.repos.update(db=db, house=house)
|
||||
|
||||
def delete(self, db: Session, house_id: str):
|
||||
return self.repos.delete(db=db, house_id=house_id)
|
||||
@@ -31,7 +31,6 @@ class UserService(BaseService):
|
||||
return self.repos.create(db=db, user=user)
|
||||
|
||||
def check_exist(self, user: UserRequest):
|
||||
print(f"user: {user}")
|
||||
db_user = self.get_by_username(username=user.username)
|
||||
if not db_user:
|
||||
return False
|
||||
|
||||
24
docker-compose.yml
Normal file
24
docker-compose.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
services:
|
||||
fuware:
|
||||
container_name: fuware-be
|
||||
image: fuware-be
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
restart: always
|
||||
volumes:
|
||||
- /home/raysam/act-runner-host/repo/dbdetail:/app/data/
|
||||
ports:
|
||||
- 16002:9000
|
||||
environment:
|
||||
ALLOW_SIGNUP: "false"
|
||||
LOG_LEVEL: "DEBUG"
|
||||
# =====================================
|
||||
# Email Configuration
|
||||
# SMTP_HOST=
|
||||
# SMTP_PORT=587
|
||||
# SMTP_FROM_NAME=Mealie
|
||||
# SMTP_AUTH_STRATEGY=TLS # Options: 'TLS', 'SSL', 'NONE'
|
||||
# SMTP_FROM_EMAIL=
|
||||
# SMTP_USER=
|
||||
# SMTP_PASSWORD=
|
||||
44
docker/entry.sh
Normal file
44
docker/entry.sh
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
# Start Backend API
|
||||
|
||||
# Get PUID/PGID
|
||||
PUID=${PUID:-911}
|
||||
PGID=${PGID:-911}
|
||||
BASH_SOURCE=${BASH_SOURCE:-$0}
|
||||
|
||||
add_user() {
|
||||
groupmod -o -g "$PGID" abc
|
||||
usermod -o -u "$PUID" abc
|
||||
}
|
||||
|
||||
change_user() {
|
||||
if [ "$(id -u)" = $PUID ]; then
|
||||
echo "
|
||||
User uid: $PUID
|
||||
User gid: $PGID
|
||||
"
|
||||
elif [ "$(id -u)" = "0" ]; then
|
||||
# If container is started as root then create a new user and switch to it
|
||||
add_user
|
||||
chown -R $PUID:$PGID /app
|
||||
|
||||
echo "Switching to dedicated user"
|
||||
exec gosu $PUID "$BASH_SOURCE" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
init() {
|
||||
# $FUWARE_HOME directory
|
||||
cd /app
|
||||
|
||||
# Activate our virtual environment here
|
||||
. /opt/pysetup/.venv/bin/activate
|
||||
}
|
||||
|
||||
change_user
|
||||
init
|
||||
|
||||
# Start API
|
||||
HOST_IP=`/sbin/ip route|awk '/default/ { print $3 }'`
|
||||
|
||||
exec python /app/backend/main.py
|
||||
@@ -1,7 +0,0 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
README.md
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
||||
@@ -1,32 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
plugins: ['solid'],
|
||||
extends: ['eslint:recommended', 'plugin:solid/recommended'],
|
||||
overrides: [
|
||||
{
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
files: ['.eslintrc.{js,cjs}'],
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
},
|
||||
},
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
indent: ['warn', 2],
|
||||
quotes: ['error', 'single'],
|
||||
semi: ['error', 'never'],
|
||||
'max-lines-per-function': [1, 1000],
|
||||
'no-unused-vars': ['warn'],
|
||||
},
|
||||
}
|
||||
24
frontend/.gitignore
vendored
24
frontend/.gitignore
vendored
@@ -1,24 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"src/**/*.{js,jsx}": ["pnpm eslint", "pnpm prettier"]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
/node_modules
|
||||
/public
|
||||
/build
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
FROM node:18.19.0-alpine3.19
|
||||
|
||||
WORKDIR /app/client
|
||||
|
||||
COPY package*.json .
|
||||
RUN npm i
|
||||
|
||||
COPY . .
|
||||
ENV NODE_ENV=production
|
||||
ENV VITE_LOGIN_KEY=7fo24CMyIc
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["npm", "run", "dev"]
|
||||
@@ -1,17 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Fuware</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"extends": "./jsconfig.paths.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": false,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@api/*": ["src/api/*"],
|
||||
"@pages/*": ["src/pages/*"],
|
||||
"@components/*": ["src/components/*"],
|
||||
"@routes/*": ["src/routes/*"],
|
||||
"@utils/*": ["src/utils/*"],
|
||||
"@assets/*": ["src/assets/*"],
|
||||
"@context/*": ["src/context/*"],
|
||||
"@lang/*": ["src/lang/*"],
|
||||
"@hooks/*": ["src/hooks/*"],
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "fuware",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.20.2"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||
"eslint": "eslint \"src/**/*.{js,jsx}\" --fix",
|
||||
"prettier": "prettier \"src/**/*.{js,jsx}\" --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.13.3",
|
||||
"@stitches/core": "^1.2.8",
|
||||
"@tabler/icons-solidjs": "^3.3.0",
|
||||
"axios": "^1.6.8",
|
||||
"crypto-js": "^4.2.0",
|
||||
"solid-form-handler": "^1.2.3",
|
||||
"solid-js": "^1.8.15",
|
||||
"solid-toast": "^0.5.0",
|
||||
"yup": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.19",
|
||||
"daisyui": "^4.11.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-solid": "^0.14.0",
|
||||
"lint-staged": "15.2.2",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "3.2.5",
|
||||
"sass": "^1.77.4",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"vite": "^5.2.0",
|
||||
"vite-plugin-mkcert": "^1.17.5",
|
||||
"vite-plugin-solid": "^2.10.2"
|
||||
},
|
||||
"proxy": "http://localhost:9000"
|
||||
}
|
||||
3336
frontend/pnpm-lock.yaml
generated
3336
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 909 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,23 +0,0 @@
|
||||
import { MetaProvider } from '@solidjs/meta'
|
||||
import { Toaster } from 'solid-toast'
|
||||
import './App.scss'
|
||||
import { SiteContextProvider } from './context/SiteContext'
|
||||
|
||||
function App(props) {
|
||||
return (
|
||||
<MetaProvider>
|
||||
<SiteContextProvider>
|
||||
<Toaster
|
||||
containerStyle={
|
||||
props.location?.pathname.indexOf('/login') >= 0
|
||||
? null
|
||||
: { 'margin-top': '60px' }
|
||||
}
|
||||
/>
|
||||
{props.children}
|
||||
</SiteContextProvider>
|
||||
</MetaProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -1,37 +0,0 @@
|
||||
#root {
|
||||
margin: 0 auto;
|
||||
|
||||
--white: #fff;
|
||||
--black: #212121;
|
||||
--primary: #03c9d7;
|
||||
--green: #05b187;
|
||||
--orange: #fb9678;
|
||||
--yellow: #fec90f;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#main-page {
|
||||
height: calc(100svh - 64px);
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#main-page .main-content {
|
||||
max-height: calc(100svh - 64px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { protocol } from './index'
|
||||
import { POST_LOGIN, POST_LOGOUT, POST_REFRESH } from './url'
|
||||
|
||||
export const postLogin = (payload) => {
|
||||
return protocol.post(POST_LOGIN, payload)
|
||||
}
|
||||
|
||||
export const getLogout = () => {
|
||||
return protocol.get(POST_LOGOUT, {})
|
||||
}
|
||||
|
||||
export const refreshToken = () => {
|
||||
return protocol.get(POST_REFRESH, {})
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import { LOGIN_KEY } from '@utils/enum'
|
||||
import axios from 'axios'
|
||||
import { Helpers } from '../utils/helper'
|
||||
import { refreshToken } from './auth'
|
||||
|
||||
const protocol = axios.create({
|
||||
baseURL: '/',
|
||||
})
|
||||
|
||||
const forceLogout = () => {
|
||||
Helpers.clearStorage()
|
||||
window.location.href = '/login'
|
||||
}
|
||||
|
||||
protocol.interceptors.request.use(async (config) => {
|
||||
config.headers.set(
|
||||
'Content-Type',
|
||||
config.headers.get('Content-Type') ?? 'application/json',
|
||||
)
|
||||
|
||||
if (
|
||||
config.url.indexOf('/login') >= 0 ||
|
||||
config.url.indexOf('/refresh') >= 0
|
||||
) {
|
||||
return config
|
||||
}
|
||||
|
||||
const { accessToken, exp } = await JSON.parse(
|
||||
Helpers.decrypt(localStorage.getItem(LOGIN_KEY)),
|
||||
)
|
||||
if (accessToken && !Helpers.checkTokenExpired(exp)) {
|
||||
config.headers.set('Authorization', `Bearer ${accessToken}`)
|
||||
}
|
||||
|
||||
return config
|
||||
})
|
||||
|
||||
protocol.interceptors.response.use(
|
||||
(response) => {
|
||||
return response.data || {}
|
||||
},
|
||||
async (error) => {
|
||||
const {
|
||||
response: { status, data },
|
||||
config,
|
||||
} = error
|
||||
|
||||
if (
|
||||
config.url.indexOf('/login') >= 0 ||
|
||||
config.url.indexOf('/refresh') >= 0
|
||||
) {
|
||||
return Promise.reject(data)
|
||||
}
|
||||
|
||||
if (status === 401 && !config._retry) {
|
||||
config._retry = true
|
||||
try {
|
||||
// call refresh token
|
||||
const resp = await refreshToken()
|
||||
if (resp.status === 200) {
|
||||
const { data } = resp
|
||||
localStorage.setItem(LOGIN_KEY, Helpers.encrypt(JSON.stringify(data)))
|
||||
|
||||
config.headers['Authorization'] = `Bearer ${data.accessToken}`
|
||||
return protocol(config)
|
||||
}
|
||||
} catch (error) {
|
||||
forceLogout()
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
if (status === 403) {
|
||||
forceLogout()
|
||||
}
|
||||
|
||||
return Promise.reject(data)
|
||||
},
|
||||
)
|
||||
|
||||
export { protocol }
|
||||
@@ -1,5 +0,0 @@
|
||||
export const POST_LOGIN = '/api/auth/login'
|
||||
export const POST_LOGOUT = '/api/auth/logout'
|
||||
export const POST_REFRESH = '/api/auth/refresh'
|
||||
export const GET_USER_PROFILE = '/api/user/me'
|
||||
export const PUT_UPDATE_USER_PROFILE = '/api/user/update-profile'
|
||||
@@ -1,10 +0,0 @@
|
||||
import { protocol } from './index'
|
||||
import { GET_USER_PROFILE, PUT_UPDATE_USER_PROFILE } from './url'
|
||||
|
||||
export const getProfile = () => {
|
||||
return protocol.get(GET_USER_PROFILE, {})
|
||||
}
|
||||
|
||||
export const putUpdateProfile = (payload) => {
|
||||
return protocol.put(PUT_UPDATE_USER_PROFILE, payload)
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
.input:focus,
|
||||
.input:focus-within {
|
||||
outline-color: var(--primary);
|
||||
}
|
||||
@@ -1,585 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="1835.000000pt" height="2012.000000pt" viewBox="0 0 1835.000000 2012.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,2012.000000) scale(0.100000,-0.100000)"
|
||||
fill="#ffffff" stroke="none">
|
||||
<path d="M17135 19631 c-3 -5 -20 -13 -38 -16 -17 -4 -36 -10 -42 -14 -5 -4
|
||||
-30 -13 -55 -20 -25 -7 -53 -17 -62 -22 -10 -5 -25 -9 -33 -9 -9 0 -26 -6 -38
|
||||
-14 -12 -8 -44 -18 -72 -21 -27 -4 -55 -11 -60 -16 -11 -8 -67 -27 -145 -49
|
||||
-25 -6 -56 -18 -70 -24 -14 -7 -45 -18 -70 -25 -25 -7 -53 -17 -62 -22 -10 -5
|
||||
-25 -9 -33 -9 -8 0 -23 -4 -33 -9 -47 -26 -87 -41 -109 -41 -12 0 -23 -4 -23
|
||||
-10 0 -5 -16 -10 -35 -10 -19 0 -35 -4 -35 -10 0 -5 -5 -10 -11 -10 -6 0 -27
|
||||
-7 -47 -16 -72 -31 -141 -54 -164 -54 -9 0 -21 -7 -28 -15 -7 -8 -20 -15 -30
|
||||
-15 -19 0 -108 -33 -151 -56 -14 -7 -45 -16 -67 -20 -23 -4 -42 -10 -42 -14 0
|
||||
-5 -15 -12 -32 -15 -18 -4 -37 -10 -43 -14 -5 -4 -28 -13 -50 -20 -22 -7 -49
|
||||
-16 -60 -21 -11 -5 -38 -16 -60 -25 -22 -9 -48 -21 -57 -26 -10 -5 -25 -9 -33
|
||||
-9 -8 0 -23 -4 -33 -9 -9 -5 -35 -17 -57 -26 -22 -10 -48 -22 -57 -26 -10 -5
|
||||
-24 -9 -32 -9 -8 0 -36 -11 -63 -24 -26 -13 -75 -34 -108 -46 -33 -12 -78 -31
|
||||
-101 -41 -22 -11 -48 -19 -57 -19 -9 0 -19 -4 -22 -10 -3 -5 -17 -10 -30 -10
|
||||
-13 0 -26 -4 -30 -9 -8 -14 -65 -41 -85 -41 -20 0 -77 -27 -85 -41 -4 -5 -15
|
||||
-9 -26 -9 -19 0 -28 -4 -76 -28 -13 -7 -29 -12 -37 -12 -7 0 -19 -7 -26 -15
|
||||
-7 -8 -23 -15 -36 -15 -13 0 -24 -4 -24 -10 0 -5 -9 -10 -19 -10 -11 0 -23 -4
|
||||
-26 -10 -3 -5 -16 -10 -27 -10 -12 0 -29 -6 -37 -13 -21 -18 -119 -57 -142
|
||||
-57 -11 0 -19 -4 -19 -8 0 -11 -95 -52 -119 -52 -10 0 -24 -7 -31 -14 -6 -8
|
||||
-28 -20 -48 -27 -20 -6 -41 -15 -47 -20 -5 -5 -21 -9 -36 -9 -15 0 -29 -7 -33
|
||||
-15 -3 -8 -12 -15 -21 -15 -8 0 -24 -4 -35 -10 -11 -5 -29 -14 -40 -20 -11 -5
|
||||
-29 -10 -39 -10 -11 0 -22 -4 -25 -9 -9 -14 -107 -61 -127 -61 -9 0 -22 -7
|
||||
-29 -15 -7 -8 -18 -15 -25 -15 -6 0 -27 -7 -46 -16 -52 -25 -122 -56 -144 -64
|
||||
-11 -4 -45 -19 -76 -33 -31 -15 -70 -30 -87 -33 -18 -4 -32 -10 -32 -15 0 -5
|
||||
-11 -9 -24 -9 -13 0 -26 -6 -29 -13 -3 -7 -24 -19 -48 -27 -24 -7 -48 -17 -54
|
||||
-22 -5 -4 -19 -8 -32 -8 -13 0 -23 -4 -23 -9 0 -11 -57 -41 -77 -41 -12 0 -29
|
||||
-7 -73 -31 -8 -5 -31 -16 -50 -25 -19 -8 -43 -20 -52 -25 -10 -5 -27 -9 -37
|
||||
-9 -11 0 -23 -4 -26 -10 -5 -8 -45 -28 -100 -50 -11 -4 -50 -22 -86 -39 -37
|
||||
-17 -74 -31 -84 -31 -10 0 -23 -6 -29 -14 -6 -8 -40 -24 -76 -36 -36 -12 -72
|
||||
-28 -80 -34 -8 -7 -28 -18 -45 -25 -16 -7 -38 -17 -47 -22 -10 -5 -27 -9 -37
|
||||
-9 -11 0 -23 -4 -26 -10 -5 -8 -43 -27 -100 -50 -22 -9 -88 -39 -207 -94 -43
|
||||
-20 -83 -36 -90 -36 -7 0 -28 -9 -46 -20 -18 -11 -37 -20 -43 -20 -6 0 -17 -7
|
||||
-25 -15 -8 -8 -31 -17 -52 -21 -20 -4 -44 -12 -52 -18 -8 -7 -26 -17 -40 -23
|
||||
-14 -6 -59 -27 -101 -47 -42 -20 -84 -36 -93 -36 -9 0 -16 -4 -16 -10 0 -5
|
||||
-11 -10 -25 -10 -14 0 -25 -3 -25 -7 0 -12 -59 -43 -80 -43 -10 0 -22 -4 -25
|
||||
-10 -3 -5 -13 -10 -21 -10 -8 0 -31 -10 -52 -23 -20 -13 -86 -43 -147 -67 -60
|
||||
-24 -117 -49 -125 -55 -8 -6 -28 -17 -45 -24 -16 -7 -38 -17 -47 -22 -10 -5
|
||||
-27 -9 -37 -9 -11 0 -22 -4 -26 -9 -7 -13 -64 -41 -82 -41 -7 0 -13 -4 -13
|
||||
-10 0 -5 -11 -10 -25 -10 -14 0 -25 -4 -25 -10 0 -5 -4 -10 -10 -10 -10 0
|
||||
-153 -63 -200 -89 -46 -25 -62 -31 -79 -31 -11 0 -23 -4 -26 -10 -3 -5 -15
|
||||
-10 -25 -10 -10 0 -20 -4 -22 -9 -3 -9 -159 -84 -198 -95 -39 -12 -89 -36 -95
|
||||
-46 -3 -6 -15 -10 -26 -10 -10 0 -19 -4 -19 -10 0 -5 -11 -10 -25 -10 -14 0
|
||||
-25 -4 -25 -10 0 -5 -6 -10 -12 -10 -7 0 -38 -12 -68 -26 -30 -15 -66 -31 -80
|
||||
-37 -14 -6 -32 -16 -40 -23 -8 -7 -46 -22 -85 -35 -38 -13 -74 -29 -78 -36 -4
|
||||
-7 -13 -13 -20 -13 -7 0 -39 -14 -72 -31 -33 -16 -89 -40 -125 -51 -36 -12
|
||||
-69 -28 -73 -35 -4 -7 -17 -13 -27 -13 -11 0 -20 -4 -20 -10 0 -5 -7 -10 -15
|
||||
-10 -14 0 -35 -9 -75 -32 -8 -4 -22 -11 -30 -14 -8 -3 -24 -9 -35 -14 -11 -4
|
||||
-38 -17 -60 -28 -22 -11 -56 -28 -75 -37 -19 -9 -43 -21 -52 -26 -10 -5 -27
|
||||
-9 -37 -9 -11 0 -23 -4 -26 -10 -3 -5 -23 -17 -43 -26 -20 -9 -48 -21 -62 -27
|
||||
-14 -6 -31 -16 -38 -21 -19 -14 -113 -46 -134 -46 -10 0 -18 -4 -18 -10 0 -12
|
||||
-97 -60 -122 -60 -10 0 -18 -4 -18 -8 0 -9 -70 -42 -88 -42 -13 0 -53 -20 -62
|
||||
-31 -5 -6 -65 -33 -105 -48 -11 -5 -45 -20 -75 -35 -30 -14 -61 -26 -67 -26
|
||||
-7 0 -13 -4 -13 -10 0 -5 -9 -10 -19 -10 -11 0 -22 -4 -26 -9 -8 -14 -65 -41
|
||||
-85 -41 -9 0 -51 -18 -93 -40 -41 -22 -84 -40 -95 -40 -11 0 -22 -7 -26 -15
|
||||
-3 -8 -12 -15 -21 -15 -8 0 -24 -4 -35 -10 -11 -5 -29 -14 -40 -20 -11 -5 -27
|
||||
-10 -35 -10 -8 0 -15 -4 -15 -8 0 -5 -19 -16 -42 -26 -24 -9 -51 -21 -60 -27
|
||||
-10 -5 -24 -9 -32 -9 -7 0 -19 -7 -26 -15 -7 -8 -21 -15 -31 -15 -11 0 -19 -4
|
||||
-19 -10 0 -5 -11 -10 -25 -10 -14 0 -25 -4 -25 -10 0 -5 -9 -10 -20 -10 -11 0
|
||||
-20 -4 -20 -8 0 -10 -71 -42 -93 -42 -9 0 -19 -4 -22 -10 -3 -5 -13 -10 -21
|
||||
-10 -8 0 -32 -11 -53 -25 -22 -13 -52 -27 -68 -31 -15 -4 -35 -12 -43 -19 -8
|
||||
-7 -33 -19 -55 -26 -22 -7 -44 -17 -49 -21 -6 -4 -18 -8 -27 -8 -10 0 -19 -4
|
||||
-21 -8 -5 -12 -109 -62 -130 -62 -10 0 -18 -4 -18 -9 0 -5 -13 -12 -30 -16
|
||||
-16 -4 -30 -11 -30 -16 0 -5 -11 -9 -25 -9 -14 0 -25 -3 -25 -7 0 -7 -94 -54
|
||||
-145 -74 -11 -4 -54 -23 -96 -43 -42 -20 -82 -36 -88 -36 -6 0 -11 -4 -11 -10
|
||||
0 -5 -5 -10 -10 -10 -6 0 -30 -9 -52 -20 -23 -11 -47 -20 -54 -20 -12 0 -73
|
||||
-29 -84 -40 -14 -14 -52 -30 -70 -30 -11 0 -20 -4 -20 -10 0 -5 -11 -12 -25
|
||||
-16 -14 -3 -25 -10 -25 -15 0 -5 -7 -9 -15 -9 -8 0 -23 -4 -33 -9 -9 -5 -44
|
||||
-21 -77 -36 -33 -15 -77 -36 -98 -46 -22 -10 -45 -19 -52 -19 -7 0 -15 -7 -19
|
||||
-15 -3 -8 -12 -15 -19 -15 -8 0 -27 -6 -43 -14 -16 -8 -51 -24 -79 -36 -27
|
||||
-12 -77 -35 -110 -51 -33 -16 -66 -29 -73 -29 -6 0 -12 -4 -12 -10 0 -5 -6
|
||||
-10 -13 -10 -18 0 -75 -28 -82 -41 -4 -5 -15 -9 -26 -9 -10 0 -19 -4 -19 -10
|
||||
0 -5 -6 -10 -14 -10 -8 0 -38 -12 -68 -26 -29 -15 -64 -31 -78 -36 -34 -14
|
||||
-47 -21 -103 -51 -26 -15 -54 -27 -62 -27 -7 0 -15 -3 -17 -7 -3 -8 -129 -73
|
||||
-141 -73 -3 0 -23 -11 -43 -25 -20 -14 -47 -25 -60 -25 -13 0 -24 -4 -24 -9 0
|
||||
-5 -8 -11 -17 -15 -10 -3 -25 -9 -33 -14 -37 -22 -61 -32 -74 -32 -8 0 -16 -3
|
||||
-18 -7 -3 -8 -32 -22 -193 -98 -44 -20 -96 -45 -115 -55 -19 -11 -51 -26 -70
|
||||
-35 -97 -46 -105 -52 -108 -73 -2 -12 -8 -22 -13 -22 -5 0 -9 -10 -9 -23 0
|
||||
-13 -6 -32 -14 -42 -12 -17 -12 -23 0 -47 8 -15 14 -40 14 -57 0 -16 5 -33 10
|
||||
-36 6 -3 10 -15 10 -26 0 -18 18 -58 31 -69 6 -5 22 -35 51 -90 7 -14 17 -28
|
||||
23 -32 5 -4 19 -31 30 -61 21 -57 85 -137 109 -137 8 0 36 -21 62 -47 42 -41
|
||||
53 -46 91 -45 42 0 103 20 103 34 0 9 59 38 77 38 7 0 13 5 13 10 0 6 11 10
|
||||
25 10 14 0 25 4 25 10 0 5 26 21 58 35 31 14 64 30 72 35 8 6 38 22 65 36 28
|
||||
14 73 38 100 54 28 16 56 29 63 29 7 1 28 12 46 26 18 14 39 25 47 26 8 0 28
|
||||
9 44 19 52 32 88 50 102 50 7 0 13 5 13 10 0 6 9 10 19 10 11 0 22 4 26 9 7
|
||||
13 64 41 82 41 7 0 13 5 13 10 0 6 9 10 19 10 11 0 22 4 25 9 3 5 59 34 123
|
||||
66 65 31 127 61 138 67 62 34 115 58 129 58 9 0 16 4 16 9 0 5 24 19 53 31 56
|
||||
24 213 97 237 111 8 4 40 20 70 34 30 15 66 33 80 41 52 31 114 55 127 50 11
|
||||
-5 13 -34 9 -153 -3 -82 -11 -337 -18 -568 -7 -240 -17 -426 -23 -435 -5 -8
|
||||
-13 -109 -17 -225 -4 -115 -12 -313 -18 -440 -6 -126 -14 -327 -18 -445 -4
|
||||
-141 -11 -223 -19 -237 -9 -15 -13 -82 -13 -230 0 -121 -4 -213 -10 -219 -5
|
||||
-5 -12 -108 -14 -229 -11 -449 -21 -680 -31 -695 -10 -15 -20 -254 -31 -690
|
||||
-2 -124 -9 -229 -14 -234 -6 -6 -10 -87 -10 -187 0 -148 -3 -179 -16 -190 -28
|
||||
-23 -196 -109 -214 -109 -6 0 -10 -4 -10 -8 0 -5 -28 -21 -62 -37 -35 -16 -79
|
||||
-39 -98 -51 -19 -11 -52 -26 -72 -33 -21 -7 -38 -16 -38 -19 0 -6 -52 -34 -70
|
||||
-38 -11 -2 -57 -26 -85 -43 -16 -11 -38 -20 -47 -20 -10 -1 -18 -5 -18 -11 0
|
||||
-5 -9 -10 -19 -10 -11 0 -22 -4 -26 -9 -7 -13 -64 -41 -82 -41 -7 0 -13 -4
|
||||
-13 -10 0 -5 -9 -10 -20 -10 -11 0 -23 -7 -26 -15 -4 -8 -15 -15 -25 -15 -10
|
||||
0 -19 -4 -19 -10 0 -5 -7 -10 -15 -10 -8 0 -36 -12 -62 -27 -53 -29 -64 -34
|
||||
-108 -54 -16 -7 -37 -19 -45 -25 -8 -7 -18 -13 -22 -14 -14 -2 -83 -30 -98
|
||||
-40 -14 -9 -50 -26 -110 -53 -14 -7 -39 -22 -56 -34 -17 -13 -36 -23 -42 -23
|
||||
-6 0 -33 -12 -59 -27 -26 -15 -66 -36 -88 -47 -22 -12 -42 -23 -45 -26 -3 -3
|
||||
-41 -24 -85 -46 -44 -22 -89 -46 -100 -52 -45 -25 -191 -92 -202 -92 -6 0 -13
|
||||
-4 -15 -8 -4 -11 -108 -62 -125 -62 -7 0 -13 -4 -13 -10 0 -5 -11 -12 -25 -16
|
||||
-14 -3 -25 -10 -25 -15 0 -5 -9 -9 -19 -9 -11 0 -23 -4 -26 -10 -3 -5 -14 -10
|
||||
-25 -10 -10 0 -20 -7 -24 -15 -3 -8 -11 -15 -18 -15 -7 0 -24 -9 -38 -20 -14
|
||||
-11 -31 -20 -39 -20 -8 0 -28 -10 -45 -23 -31 -22 -53 -34 -113 -59 -18 -7
|
||||
-33 -16 -33 -20 0 -3 -17 -12 -37 -18 -21 -7 -55 -23 -75 -36 -21 -13 -42 -24
|
||||
-47 -24 -10 0 -62 -28 -153 -81 -27 -16 -55 -29 -63 -29 -8 0 -15 -4 -15 -9 0
|
||||
-11 -56 -41 -77 -41 -7 0 -13 -4 -13 -10 0 -5 -9 -10 -20 -10 -11 0 -23 -7
|
||||
-26 -15 -4 -8 -12 -15 -20 -15 -7 0 -29 -9 -49 -20 -20 -11 -42 -20 -50 -20
|
||||
-8 0 -15 -4 -15 -9 0 -6 -21 -21 -47 -34 -100 -51 -129 -66 -163 -82 -64 -30
|
||||
-140 -74 -156 -90 -8 -8 -22 -15 -30 -15 -8 0 -14 -4 -14 -10 0 -5 -6 -10 -13
|
||||
-10 -18 0 -75 -28 -82 -41 -4 -5 -15 -9 -25 -9 -10 0 -20 -4 -22 -9 -6 -16
|
||||
-165 -101 -188 -101 -6 0 -10 -4 -10 -9 0 -5 -24 -21 -52 -35 -29 -15 -71 -38
|
||||
-93 -52 -22 -13 -50 -29 -62 -34 -13 -5 -23 -14 -23 -20 0 -5 -7 -10 -15 -10
|
||||
-17 0 -76 -32 -120 -66 -18 -13 -39 -24 -47 -24 -8 0 -23 -9 -33 -20 -9 -10
|
||||
-40 -31 -69 -46 -28 -14 -55 -33 -59 -40 -4 -8 -14 -14 -22 -14 -8 0 -30 -13
|
||||
-48 -30 -19 -16 -39 -30 -43 -30 -21 -1 -64 -51 -64 -74 0 -14 -5 -28 -10 -31
|
||||
-13 -8 -13 -132 0 -140 6 -3 10 -22 10 -41 0 -19 4 -34 9 -34 11 0 41 -56 41
|
||||
-76 0 -7 11 -21 25 -32 14 -11 25 -24 25 -29 0 -11 42 -97 57 -115 6 -7 16
|
||||
-29 22 -49 13 -40 81 -118 121 -139 27 -14 59 -41 93 -77 39 -43 129 -21 194
|
||||
45 10 10 38 30 63 43 25 13 54 32 65 42 11 10 38 27 60 37 22 10 49 28 59 39
|
||||
11 12 25 21 33 21 16 0 50 18 78 42 11 9 52 33 90 53 39 20 74 40 80 46 5 5
|
||||
17 9 27 9 10 0 18 4 18 9 0 11 56 41 77 41 7 0 13 5 13 10 0 6 5 10 11 10 6 0
|
||||
25 11 43 25 18 14 40 25 49 25 10 0 17 5 17 10 0 6 9 10 20 10 11 0 20 4 20
|
||||
10 0 5 27 22 60 37 33 14 60 30 60 35 0 4 5 8 12 8 6 0 19 6 27 13 9 8 32 20
|
||||
51 27 19 8 37 17 40 21 3 3 30 19 60 33 30 15 78 40 105 56 28 16 58 29 68 29
|
||||
9 1 17 5 17 10 0 11 56 41 77 41 7 0 13 5 13 10 0 6 11 10 24 10 13 0 26 7 30
|
||||
15 3 8 31 27 62 42 86 42 94 47 94 55 0 5 7 8 15 8 8 0 36 12 62 27 54 30 64
|
||||
35 113 58 35 17 114 59 203 108 26 15 54 27 62 27 8 0 15 3 15 8 0 10 58 42
|
||||
76 42 8 0 14 5 14 10 0 6 11 10 25 10 14 0 25 4 25 9 0 4 39 29 88 53 154 80
|
||||
202 106 215 116 6 5 23 13 37 17 14 4 43 19 64 33 22 13 44 25 50 27 17 3 58
|
||||
23 91 44 17 11 36 20 43 20 7 1 29 12 50 26 39 26 145 83 252 136 36 18 67 36
|
||||
68 41 2 4 10 8 18 8 7 0 29 9 49 20 20 11 42 20 49 20 8 0 16 6 19 14 3 7 18
|
||||
17 34 21 15 3 53 22 84 41 31 19 64 34 73 34 9 0 16 4 16 9 0 5 8 11 18 15 9
|
||||
3 31 14 47 25 17 11 38 20 48 20 9 1 17 5 17 10 0 5 8 11 18 15 9 3 24 9 32
|
||||
14 38 22 61 32 75 32 8 0 15 4 15 9 0 5 8 11 18 15 9 3 24 10 32 15 8 5 40 21
|
||||
70 36 30 15 82 42 115 60 106 59 124 67 134 60 18 -11 14 -450 -4 -503 -11
|
||||
-32 -15 -92 -14 -257 0 -118 -3 -221 -8 -227 -8 -12 -16 -244 -28 -733 -3
|
||||
-115 -10 -240 -15 -277 -6 -37 -10 -148 -10 -247 0 -99 -4 -224 -10 -278 -5
|
||||
-54 -12 -217 -15 -363 -3 -146 -9 -429 -13 -630 -4 -214 -11 -371 -17 -380 -6
|
||||
-9 -13 -190 -18 -450 -38 -2158 -40 -2240 -54 -2273 -9 -19 -13 -86 -13 -209
|
||||
0 -161 2 -182 19 -204 18 -22 19 -23 35 -5 12 13 16 37 16 94 0 42 5 79 10 82
|
||||
6 4 10 54 10 119 0 61 4 116 9 121 5 6 11 61 13 123 2 73 7 115 15 120 7 5 14
|
||||
48 17 112 14 259 18 306 27 315 5 5 9 48 9 95 0 56 5 92 14 105 9 14 16 72 21
|
||||
190 5 112 12 179 21 197 8 16 14 58 14 106 0 43 4 82 9 87 5 6 12 53 15 105 4
|
||||
52 9 137 12 189 3 57 11 103 20 115 9 14 14 48 14 108 0 48 5 90 10 93 6 3 10
|
||||
48 10 100 0 52 4 97 10 100 6 3 10 50 10 105 0 55 4 102 10 105 6 3 10 42 10
|
||||
87 0 49 6 92 15 111 8 18 17 77 20 132 3 55 8 145 11 200 3 55 10 104 15 110
|
||||
5 5 9 50 9 100 0 65 4 97 15 111 10 14 14 47 15 121 0 57 4 105 10 108 6 3 10
|
||||
52 10 110 0 58 4 107 10 110 6 3 10 52 10 110 0 76 4 107 14 118 14 14 15 30
|
||||
32 357 3 58 9 109 15 115 5 5 9 53 9 106 0 60 5 109 14 130 10 23 16 101 21
|
||||
244 4 116 11 215 15 220 4 6 10 60 12 121 2 65 8 117 15 125 6 8 14 63 17 124
|
||||
3 61 10 166 16 235 5 69 13 180 17 247 5 75 13 127 21 135 8 8 12 49 12 128 0
|
||||
64 4 120 9 125 6 6 12 62 15 125 14 307 20 353 46 391 14 22 39 42 60 49 19 7
|
||||
46 21 59 32 14 10 43 28 65 38 23 11 64 33 91 49 28 16 57 30 66 30 9 1 19 8
|
||||
23 16 3 8 12 15 20 15 9 0 31 9 51 20 20 11 42 20 50 20 8 0 15 4 15 10 0 5
|
||||
26 21 58 36 31 14 59 29 62 32 11 15 89 52 109 52 11 0 21 4 21 10 0 10 95 60
|
||||
114 60 6 0 22 12 35 25 13 14 32 25 42 25 10 0 19 3 19 8 0 8 18 18 145 79 17
|
||||
7 44 22 60 33 17 10 38 19 48 19 9 1 17 4 17 9 0 8 7 12 90 55 30 16 78 41
|
||||
105 57 28 15 60 29 73 29 12 1 22 5 22 10 0 11 87 61 106 61 8 0 14 4 14 9 0
|
||||
10 97 61 117 61 7 0 13 3 13 8 0 8 57 42 71 42 5 0 32 15 61 33 29 18 79 46
|
||||
110 61 32 15 60 31 63 36 3 5 20 12 38 15 17 4 34 11 37 15 10 14 90 60 104
|
||||
60 8 0 16 4 18 8 5 14 97 62 118 62 11 0 20 4 20 10 0 5 19 19 43 30 23 11 60
|
||||
31 82 44 22 14 78 44 125 68 47 23 103 54 125 67 60 38 86 51 100 51 7 0 15 4
|
||||
17 8 3 9 107 62 120 62 5 0 19 9 31 20 25 24 74 50 94 50 7 0 13 3 13 8 0 8
|
||||
11 14 77 45 21 10 45 23 53 28 140 85 152 89 180 64 11 -10 20 -24 20 -32 0
|
||||
-8 11 -26 25 -41 14 -15 25 -32 25 -39 0 -7 5 -13 10 -13 6 0 10 -9 10 -19 0
|
||||
-11 3 -21 8 -23 12 -5 62 -87 62 -102 0 -8 3 -16 8 -18 8 -3 62 -84 62 -94 0
|
||||
-3 15 -29 34 -57 18 -29 44 -71 56 -94 13 -24 27 -43 32 -43 4 0 8 -11 8 -25
|
||||
0 -14 5 -25 10 -25 6 0 18 -16 27 -35 9 -19 20 -35 25 -35 4 0 8 -7 8 -15 0
|
||||
-8 16 -37 35 -64 19 -26 35 -52 35 -55 0 -4 15 -31 33 -59 18 -29 41 -68 51
|
||||
-87 10 -19 26 -44 36 -55 9 -11 24 -36 33 -56 9 -20 23 -42 32 -49 8 -7 15
|
||||
-21 15 -31 0 -11 5 -19 10 -19 6 0 10 -6 10 -14 0 -8 11 -28 25 -44 14 -17 25
|
||||
-37 25 -46 0 -9 5 -16 10 -16 6 0 10 -6 10 -13 0 -7 11 -24 25 -39 14 -15 25
|
||||
-31 25 -36 0 -15 30 -69 45 -82 18 -15 45 -68 45 -87 0 -7 7 -16 16 -20 8 -3
|
||||
26 -30 40 -59 13 -29 28 -55 33 -59 12 -7 41 -64 41 -80 0 -7 7 -15 15 -19 8
|
||||
-3 26 -29 39 -58 14 -30 34 -62 45 -72 12 -11 21 -27 21 -38 0 -10 5 -18 10
|
||||
-18 6 0 10 -9 10 -19 0 -10 6 -21 13 -24 13 -5 57 -81 57 -99 0 -6 11 -19 25
|
||||
-30 14 -11 25 -27 26 -36 0 -9 8 -29 17 -43 29 -44 56 -87 77 -121 11 -18 28
|
||||
-44 37 -58 9 -14 17 -35 17 -47 1 -13 6 -23 12 -23 16 0 38 -31 39 -52 0 -10
|
||||
5 -18 10 -18 6 0 10 -7 10 -16 0 -9 11 -25 25 -36 14 -11 25 -26 25 -34 0 -17
|
||||
18 -50 43 -78 21 -25 47 -74 47 -91 0 -7 7 -15 15 -19 8 -3 26 -30 40 -60 14
|
||||
-30 30 -58 36 -62 6 -3 20 -25 30 -48 10 -23 27 -51 39 -61 11 -10 20 -26 20
|
||||
-37 0 -10 5 -18 10 -18 6 0 10 -8 10 -18 0 -10 11 -29 25 -42 14 -13 25 -32
|
||||
25 -42 0 -10 5 -18 10 -18 6 0 10 -6 10 -13 0 -7 11 -24 25 -39 14 -15 25 -32
|
||||
25 -38 0 -6 15 -32 32 -58 18 -26 44 -67 57 -92 13 -25 27 -47 31 -50 6 -5 21
|
||||
-31 52 -90 7 -14 15 -27 18 -30 4 -3 12 -20 19 -37 8 -18 18 -33 23 -33 5 0
|
||||
18 -20 29 -45 11 -24 25 -50 32 -57 19 -20 47 -66 47 -78 0 -6 8 -20 18 -30
|
||||
10 -11 29 -40 41 -65 12 -25 28 -51 34 -58 19 -20 36 -49 48 -80 6 -15 15 -27
|
||||
19 -27 4 0 13 -12 19 -28 17 -41 45 -86 59 -95 6 -4 12 -15 12 -23 0 -8 9 -24
|
||||
20 -36 11 -12 20 -30 20 -40 0 -10 4 -18 9 -18 9 0 35 -39 74 -110 10 -19 28
|
||||
-44 38 -56 10 -11 19 -26 19 -32 0 -7 16 -35 35 -62 19 -27 35 -55 35 -60 0
|
||||
-6 8 -20 18 -30 10 -11 28 -40 41 -65 12 -24 26 -47 31 -50 11 -7 40 -64 40
|
||||
-79 0 -6 11 -20 25 -30 14 -10 25 -27 25 -37 0 -11 5 -19 10 -19 6 0 10 -6 10
|
||||
-14 0 -7 11 -25 25 -40 14 -15 25 -36 25 -47 0 -10 4 -19 8 -19 4 0 16 -15 27
|
||||
-32 10 -18 21 -35 24 -38 4 -3 12 -16 19 -30 7 -14 21 -38 32 -55 35 -54 49
|
||||
-80 62 -108 7 -15 16 -27 20 -27 5 0 19 -24 33 -53 13 -30 31 -60 40 -67 9 -7
|
||||
24 -32 35 -54 10 -23 28 -50 39 -60 12 -11 21 -27 21 -37 0 -10 5 -21 10 -24
|
||||
15 -10 60 -82 60 -97 0 -7 11 -22 25 -32 14 -10 25 -25 25 -33 0 -13 65 -121
|
||||
90 -150 5 -7 10 -19 10 -28 0 -8 4 -15 9 -15 10 0 61 -86 61 -102 0 -6 11 -19
|
||||
25 -30 14 -11 25 -28 25 -38 0 -10 3 -20 8 -22 13 -6 62 -76 62 -89 0 -7 10
|
||||
-23 21 -35 24 -26 42 -55 61 -96 7 -16 16 -28 20 -28 4 0 21 -26 37 -58 16
|
||||
-32 36 -64 45 -71 9 -8 16 -19 16 -26 0 -7 11 -23 24 -37 12 -14 26 -32 29
|
||||
-39 4 -8 18 -32 32 -54 14 -22 28 -47 32 -55 4 -8 15 -24 25 -34 10 -11 18
|
||||
-26 18 -34 0 -8 11 -23 25 -34 14 -11 25 -27 25 -37 0 -10 11 -27 25 -39 14
|
||||
-12 25 -28 25 -35 0 -8 9 -22 20 -32 11 -10 20 -23 20 -30 0 -6 11 -22 25 -35
|
||||
14 -13 25 -29 25 -35 0 -7 11 -24 24 -38 14 -15 27 -36 31 -47 3 -11 11 -20
|
||||
16 -20 5 0 9 -7 9 -15 0 -8 5 -15 11 -15 14 0 4 77 -13 97 -17 20 -58 105 -58
|
||||
118 0 7 -7 18 -15 25 -8 7 -15 23 -15 36 0 12 -4 26 -10 29 -5 3 -10 15 -10
|
||||
26 0 10 -4 19 -10 19 -5 0 -10 9 -10 20 0 11 -7 23 -15 26 -8 4 -15 15 -15 25
|
||||
0 10 -4 19 -10 19 -5 0 -10 9 -10 19 0 11 -4 23 -10 26 -5 3 -10 14 -10 25 0
|
||||
10 -7 20 -15 24 -8 3 -15 12 -15 21 0 8 -4 23 -10 33 -33 61 -81 170 -86 199
|
||||
-3 18 -11 37 -18 41 -6 4 -17 21 -25 37 -7 17 -17 38 -22 47 -5 10 -9 25 -9
|
||||
33 0 8 -4 15 -9 15 -11 0 -41 56 -41 77 0 7 -4 13 -10 13 -5 0 -10 11 -10 25
|
||||
0 14 -4 25 -9 25 -11 0 -41 56 -41 77 0 7 -4 13 -10 13 -5 0 -10 9 -10 19 0
|
||||
11 -4 22 -9 26 -6 3 -22 31 -36 63 -15 31 -31 64 -36 72 -18 30 -59 121 -59
|
||||
130 0 6 -3 10 -8 10 -9 0 -52 93 -52 114 0 9 -4 16 -9 16 -11 0 -41 56 -41 77
|
||||
0 7 -4 13 -10 13 -5 0 -10 11 -10 25 0 14 -4 25 -9 25 -11 0 -41 56 -41 77 0
|
||||
7 -4 13 -10 13 -5 0 -10 9 -10 19 0 11 -4 23 -10 26 -5 3 -23 34 -40 68 -16
|
||||
34 -51 98 -76 142 -25 44 -52 96 -60 115 -8 19 -19 42 -24 50 -23 37 -60 115
|
||||
-60 126 0 7 -4 14 -8 16 -11 4 -62 108 -62 125 0 7 -4 13 -10 13 -5 0 -12 11
|
||||
-16 25 -3 14 -10 25 -15 25 -5 0 -9 7 -9 15 0 14 -38 87 -70 135 -9 14 -24 41
|
||||
-34 60 -9 19 -21 42 -26 50 -5 8 -21 41 -35 72 -14 31 -29 59 -34 63 -5 3 -13
|
||||
20 -17 38 -3 17 -17 48 -30 67 -13 19 -24 40 -24 47 0 7 -4 13 -10 13 -5 0
|
||||
-10 7 -10 17 0 9 -10 30 -23 47 -13 17 -36 57 -51 88 -15 32 -31 60 -37 63 -5
|
||||
4 -9 15 -9 26 0 10 -4 19 -10 19 -5 0 -10 6 -10 13 0 14 -6 26 -70 142 -22 39
|
||||
-51 95 -66 125 -15 30 -29 57 -33 60 -3 3 -12 19 -20 35 -7 17 -17 38 -22 47
|
||||
-5 10 -9 25 -9 33 0 8 -4 15 -9 15 -11 0 -41 56 -41 76 0 7 -4 14 -9 16 -13 5
|
||||
-61 86 -61 103 0 8 -4 15 -10 15 -5 0 -10 6 -10 14 0 7 -7 19 -15 26 -8 7 -15
|
||||
21 -15 31 0 11 -4 19 -10 19 -5 0 -10 8 -11 18 0 9 -9 31 -19 47 -11 17 -31
|
||||
53 -45 80 -15 28 -39 73 -55 100 -16 28 -29 58 -29 68 -1 10 -8 20 -16 23 -8
|
||||
4 -15 10 -15 16 0 5 -9 28 -20 50 -11 23 -20 47 -20 54 0 6 -6 14 -12 17 -7 2
|
||||
-26 29 -41 58 -16 30 -37 68 -47 84 -10 17 -19 38 -19 48 -1 9 -5 17 -11 17
|
||||
-5 0 -10 8 -10 18 0 10 -5 23 -11 29 -6 6 -23 35 -38 64 -51 100 -92 174 -102
|
||||
183 -5 6 -9 18 -9 28 0 10 -4 18 -8 18 -4 0 -14 16 -21 35 -7 19 -28 58 -47
|
||||
86 -19 28 -34 57 -34 65 0 8 -4 14 -10 14 -5 0 -12 11 -16 25 -3 14 -10 25
|
||||
-15 25 -5 0 -9 9 -9 19 0 11 -4 23 -10 26 -5 3 -10 14 -10 25 0 10 -7 20 -15
|
||||
24 -8 3 -15 14 -15 25 0 11 -3 21 -7 23 -10 4 -33 45 -85 148 -23 47 -45 87
|
||||
-50 88 -4 2 -8 15 -8 28 0 13 -4 24 -10 24 -5 0 -10 7 -10 15 0 8 -4 15 -8 15
|
||||
-5 0 -23 28 -41 63 -18 34 -45 85 -61 112 -15 28 -29 58 -29 68 -1 9 -5 17
|
||||
-10 17 -5 0 -14 12 -20 28 -16 38 -60 114 -77 133 -8 8 -14 20 -14 26 0 11
|
||||
-74 156 -90 178 -20 27 -60 107 -60 120 0 8 -4 15 -10 15 -5 0 -12 11 -16 25
|
||||
-3 14 -10 25 -15 25 -5 0 -9 6 -9 13 0 20 -28 73 -45 87 -9 7 -22 29 -31 49
|
||||
-19 45 -24 54 -57 114 -15 26 -27 54 -27 62 0 8 -4 15 -9 15 -5 0 -21 24 -36
|
||||
53 -15 28 -35 66 -45 82 -10 17 -19 38 -19 48 -1 9 -5 17 -11 17 -5 0 -10 7
|
||||
-10 15 0 8 -16 39 -35 69 -19 30 -35 58 -35 61 0 4 -7 13 -15 22 -8 8 -13 19
|
||||
-9 24 3 5 -2 12 -10 15 -9 3 -16 12 -16 20 0 13 -43 97 -63 122 -11 14 14 48
|
||||
48 64 33 15 66 35 78 47 6 6 19 11 29 11 10 0 21 7 24 15 4 8 10 15 16 15 5 0
|
||||
28 9 50 20 23 11 47 20 54 20 6 0 14 6 17 13 2 6 29 25 58 40 30 16 68 37 84
|
||||
47 17 10 38 19 48 19 9 1 17 6 17 11 0 6 8 10 19 10 10 0 24 7 31 15 7 8 17
|
||||
15 22 15 6 0 29 13 52 28 23 16 69 41 101 57 33 16 82 42 108 57 26 15 54 28
|
||||
62 28 8 0 15 4 15 9 0 10 97 61 116 61 7 0 17 6 21 13 10 16 49 37 68 37 8 0
|
||||
15 5 15 10 0 6 5 10 11 10 6 0 25 11 43 25 18 14 39 25 47 26 8 0 28 9 44 19
|
||||
47 29 87 50 97 50 6 0 22 8 36 19 43 30 102 61 116 61 8 0 16 7 20 15 3 8 15
|
||||
15 26 15 11 0 20 3 20 8 0 7 48 32 62 32 5 0 19 9 31 20 12 12 32 23 44 26 13
|
||||
4 23 10 23 15 0 5 9 9 19 9 11 0 23 5 26 10 8 13 104 60 122 60 7 0 13 4 13 8
|
||||
0 5 10 14 23 20 12 7 49 28 82 47 33 20 67 38 75 41 46 18 165 82 207 112 17
|
||||
12 38 22 47 22 9 0 16 5 16 10 0 6 11 10 25 10 14 0 25 4 25 9 0 5 26 22 58
|
||||
37 31 16 59 31 62 34 3 4 12 10 20 13 8 4 29 16 45 26 17 11 38 20 48 20 9 1
|
||||
17 5 17 10 0 5 8 11 18 15 9 3 24 9 32 14 8 5 35 20 60 32 57 29 137 72 196
|
||||
105 24 14 51 25 59 25 8 0 15 4 15 10 0 5 11 12 25 16 14 3 25 9 25 13 0 3 15
|
||||
12 33 20 17 8 34 17 37 20 7 9 41 28 75 45 94 44 92 42 266 138 75 42 109 60
|
||||
164 86 22 11 58 30 80 42 22 13 51 30 65 37 14 8 39 23 55 33 17 10 38 19 48
|
||||
19 9 1 17 6 17 11 0 6 9 10 20 10 11 0 23 7 26 15 4 8 12 15 20 15 7 0 30 10
|
||||
51 21 21 12 67 38 103 57 36 19 72 41 80 47 8 6 38 20 65 30 28 10 52 21 55
|
||||
25 3 4 23 15 45 25 22 10 51 26 65 35 46 31 121 70 134 70 8 0 16 7 20 15 3 8
|
||||
15 15 26 15 11 0 20 5 20 10 0 6 8 10 18 11 9 0 31 9 47 20 17 10 37 22 45 26
|
||||
8 3 47 25 85 48 39 23 87 48 108 54 20 7 37 16 37 19 0 4 15 13 33 20 58 24
|
||||
82 36 112 58 17 11 46 29 65 38 19 10 58 30 85 46 28 15 58 29 68 29 9 1 17 5
|
||||
17 11 0 5 11 12 25 16 14 3 25 10 25 14 0 4 11 11 24 14 14 3 31 12 39 20 16
|
||||
16 87 56 100 56 5 0 29 12 55 27 26 16 50 28 54 28 3 0 10 3 14 8 4 4 15 7 25
|
||||
7 10 0 22 6 26 13 11 19 104 71 213 120 14 7 39 21 55 32 17 12 44 28 60 36
|
||||
17 7 36 20 43 27 7 6 20 12 28 12 8 0 14 5 14 10 0 6 6 10 13 10 17 0 75 28
|
||||
82 40 5 8 34 23 85 44 8 3 33 18 55 31 22 14 48 28 58 32 9 3 17 9 17 13 0 10
|
||||
57 40 75 40 7 0 20 9 31 21 10 11 37 29 60 40 23 10 48 25 56 32 7 7 34 23 60
|
||||
36 25 12 52 29 58 37 7 7 17 14 23 14 6 0 19 8 29 17 11 9 42 29 71 44 40 20
|
||||
53 32 55 53 3 21 -1 26 -17 26 -12 0 -21 -4 -21 -10 0 -5 -17 -10 -38 -10 -21
|
||||
0 -46 -6 -57 -14 -11 -7 -40 -16 -65 -19 -102 -14 -130 -20 -136 -28 -3 -5
|
||||
-18 -9 -33 -9 -16 0 -34 -7 -41 -15 -7 -8 -27 -15 -46 -15 -18 0 -36 -4 -39
|
||||
-10 -3 -5 -22 -10 -41 -10 -19 0 -34 -4 -34 -10 0 -5 -14 -10 -30 -10 -18 0
|
||||
-33 -6 -36 -15 -4 -8 -19 -15 -34 -15 -16 0 -32 -4 -35 -10 -3 -5 -15 -10 -26
|
||||
-10 -19 0 -27 -3 -86 -34 -17 -9 -40 -16 -51 -16 -12 0 -24 -4 -27 -10 -3 -5
|
||||
-14 -10 -24 -10 -9 0 -55 -16 -101 -35 -47 -19 -102 -38 -122 -41 -21 -4 -43
|
||||
-13 -49 -20 -6 -8 -22 -14 -35 -14 -13 0 -24 -4 -24 -10 0 -5 -11 -10 -25 -10
|
||||
-23 0 -41 -7 -85 -32 -8 -4 -22 -11 -30 -14 -8 -3 -23 -10 -32 -15 -10 -5 -29
|
||||
-9 -43 -9 -14 0 -25 -4 -25 -10 0 -5 -11 -10 -24 -10 -13 0 -26 -7 -30 -15 -3
|
||||
-9 -18 -15 -36 -15 -16 0 -30 -4 -30 -10 0 -5 -11 -10 -25 -10 -14 0 -25 -4
|
||||
-25 -10 0 -5 -11 -10 -24 -10 -13 0 -29 -7 -36 -15 -7 -8 -19 -15 -26 -15 -8
|
||||
0 -22 -4 -32 -9 -9 -5 -35 -14 -57 -21 -22 -7 -47 -18 -55 -24 -8 -7 -42 -20
|
||||
-75 -30 -33 -10 -67 -23 -75 -30 -8 -7 -33 -18 -55 -25 -46 -14 -83 -29 -105
|
||||
-42 -8 -5 -22 -12 -30 -15 -8 -3 -26 -11 -40 -16 -14 -6 -36 -15 -50 -21 -14
|
||||
-5 -37 -16 -52 -23 -14 -8 -33 -14 -42 -14 -9 0 -16 -4 -16 -10 0 -5 -7 -10
|
||||
-15 -10 -8 0 -23 -4 -33 -9 -25 -13 -134 -62 -157 -71 -11 -4 -31 -12 -45 -18
|
||||
-90 -39 -132 -58 -188 -83 -34 -16 -76 -32 -92 -36 -17 -3 -30 -9 -30 -12 0
|
||||
-8 -52 -31 -115 -52 -22 -8 -44 -20 -48 -26 -4 -7 -17 -13 -27 -13 -11 0 -20
|
||||
-4 -20 -10 0 -5 -11 -10 -25 -10 -14 0 -25 -4 -25 -10 0 -5 -13 -10 -29 -10
|
||||
-16 0 -31 -6 -35 -15 -3 -8 -12 -15 -20 -15 -9 0 -31 -9 -51 -20 -20 -11 -42
|
||||
-20 -50 -20 -8 0 -15 -3 -15 -7 0 -5 -21 -16 -48 -26 -26 -10 -84 -36 -128
|
||||
-57 -45 -22 -88 -40 -95 -40 -8 0 -23 -7 -33 -15 -11 -8 -28 -15 -38 -15 -10
|
||||
0 -18 -4 -18 -10 0 -5 -9 -10 -19 -10 -11 0 -23 -4 -26 -10 -3 -5 -15 -10 -26
|
||||
-10 -10 0 -19 -4 -19 -9 0 -12 -58 -41 -82 -41 -10 0 -18 -4 -18 -10 0 -5 -13
|
||||
-10 -30 -10 -16 0 -30 -3 -30 -8 0 -4 -17 -16 -37 -26 -21 -10 -46 -22 -55
|
||||
-27 -10 -5 -25 -9 -33 -9 -8 0 -15 -4 -15 -9 0 -5 -15 -15 -32 -22 -18 -6 -46
|
||||
-17 -63 -24 -67 -25 -115 -49 -115 -57 0 -4 -6 -8 -14 -8 -8 0 -49 -18 -92
|
||||
-40 -43 -22 -84 -40 -91 -40 -7 0 -44 -16 -82 -35 -37 -19 -71 -35 -75 -35 -3
|
||||
0 -24 -11 -46 -25 -22 -14 -46 -25 -55 -25 -8 0 -15 -4 -15 -10 0 -5 -11 -10
|
||||
-24 -10 -14 0 -28 -4 -31 -10 -3 -5 -14 -10 -24 -10 -10 0 -24 -7 -31 -15 -7
|
||||
-8 -21 -15 -31 -15 -11 0 -19 -4 -19 -10 0 -5 -8 -10 -17 -11 -10 0 -31 -9
|
||||
-48 -19 -45 -28 -161 -82 -227 -106 -20 -7 -63 -26 -95 -42 -32 -16 -71 -36
|
||||
-88 -43 -16 -8 -32 -17 -35 -20 -3 -3 -23 -14 -45 -24 -45 -19 -141 -63 -167
|
||||
-76 -10 -5 -25 -9 -33 -9 -8 0 -15 -4 -15 -10 0 -5 -9 -10 -19 -10 -11 0 -22
|
||||
-4 -25 -9 -8 -12 -106 -61 -122 -61 -7 0 -17 -6 -21 -13 -4 -8 -30 -22 -58
|
||||
-32 -27 -10 -68 -28 -90 -40 -22 -12 -60 -28 -85 -36 -25 -7 -49 -19 -53 -26
|
||||
-4 -7 -17 -13 -27 -13 -11 0 -20 -4 -20 -10 0 -5 -9 -10 -19 -10 -11 0 -23 -4
|
||||
-26 -10 -3 -5 -14 -10 -25 -10 -10 0 -20 -7 -24 -15 -3 -8 -15 -15 -26 -15
|
||||
-11 0 -20 -4 -20 -10 0 -5 -6 -10 -13 -10 -18 0 -75 -28 -82 -41 -4 -5 -15 -9
|
||||
-26 -9 -10 0 -19 -4 -19 -10 0 -5 -7 -10 -15 -10 -8 0 -23 -4 -33 -9 -9 -5
|
||||
-42 -20 -72 -34 -78 -35 -121 -56 -150 -73 -41 -23 -60 -18 -60 15 0 17 5 33
|
||||
10 36 6 4 28 34 50 68 22 34 43 67 48 72 4 6 26 39 47 75 22 36 45 72 50 81 6
|
||||
9 27 42 48 73 20 31 37 61 37 67 0 5 7 12 15 15 8 4 15 11 15 18 0 6 9 22 20
|
||||
36 11 14 20 32 20 41 0 9 11 22 25 29 14 7 25 22 25 32 0 18 46 89 63 96 4 2
|
||||
7 11 7 20 0 8 7 24 17 34 17 19 33 44 55 88 7 14 15 27 19 30 3 3 14 20 24 38
|
||||
11 17 23 32 27 32 4 0 8 9 8 19 0 11 11 28 25 39 14 11 25 29 25 41 0 12 5 21
|
||||
10 21 6 0 10 6 10 14 0 7 11 25 25 40 14 15 25 33 25 40 0 8 7 20 16 27 9 7
|
||||
20 22 25 34 5 11 21 38 35 60 13 22 27 48 31 58 3 9 9 17 13 17 5 0 20 24 34
|
||||
53 13 29 34 61 45 71 12 11 21 26 21 34 0 8 16 36 35 62 19 26 35 51 35 55 0
|
||||
4 16 29 35 55 19 26 35 53 35 59 0 7 11 24 25 39 14 15 25 32 25 39 0 7 5 13
|
||||
10 13 6 0 10 9 10 19 0 11 11 28 25 39 14 11 25 28 26 38 0 11 8 30 17 44 9
|
||||
14 26 41 37 59 11 18 30 47 42 65 12 17 34 55 49 84 15 30 33 56 41 59 7 3 13
|
||||
12 13 19 0 12 44 88 60 104 3 3 16 25 29 50 13 25 30 52 37 61 8 8 14 21 14
|
||||
27 0 7 4 12 8 12 4 0 13 12 20 28 7 15 23 45 37 67 13 22 29 49 35 60 6 11 19
|
||||
34 30 50 11 17 25 41 32 55 7 14 18 31 25 38 7 7 13 20 13 28 0 8 4 14 8 14 5
|
||||
0 17 15 27 34 14 26 38 44 94 72 42 21 78 41 81 44 9 11 72 40 87 40 7 0 13 4
|
||||
13 9 0 5 8 11 18 15 9 3 31 14 47 25 17 11 40 20 51 20 12 1 24 7 27 14 3 8
|
||||
30 26 61 41 31 15 58 31 61 37 4 5 13 9 21 9 8 0 38 15 67 33 29 18 66 38 82
|
||||
46 17 7 37 17 45 22 8 5 22 12 30 16 8 4 17 9 20 13 3 3 21 14 40 23 19 10 42
|
||||
21 50 26 8 5 29 15 45 22 17 8 44 24 61 36 17 13 46 28 64 35 19 6 49 21 67
|
||||
32 96 62 123 77 152 82 17 4 31 10 31 14 0 5 26 22 58 37 31 16 73 39 92 51
|
||||
19 12 40 22 47 22 7 0 23 10 37 23 14 13 44 30 68 37 24 7 48 19 53 25 6 6 25
|
||||
18 43 26 17 7 32 17 32 20 0 4 8 9 18 13 9 3 24 10 32 15 8 5 29 15 45 22 17
|
||||
8 44 24 61 36 17 13 38 23 47 23 8 0 17 3 19 8 2 4 30 20 63 37 33 16 67 34
|
||||
75 41 30 24 91 54 109 54 10 0 21 7 25 15 3 8 12 15 21 15 8 0 15 4 15 9 0 5
|
||||
8 11 18 14 9 3 33 14 52 25 19 11 49 26 65 33 17 8 32 17 35 21 3 3 31 20 63
|
||||
36 31 16 63 34 70 39 6 5 28 16 47 23 19 8 40 19 47 25 31 26 40 32 73 45 19
|
||||
7 42 18 50 25 8 6 29 17 45 25 17 8 44 24 60 35 17 12 45 27 63 33 18 6 45 20
|
||||
60 30 15 11 43 27 62 36 19 9 67 35 105 56 39 22 87 47 108 57 20 10 37 21 37
|
||||
25 0 4 16 14 35 23 36 15 92 42 126 60 10 5 19 13 19 17 0 5 9 8 20 8 11 0 20
|
||||
5 20 10 0 10 22 21 98 52 17 7 32 16 32 20 0 3 25 19 55 33 30 15 59 33 66 41
|
||||
6 8 19 14 28 15 9 0 30 9 46 20 17 11 38 22 48 25 9 4 17 9 17 13 0 4 16 13
|
||||
35 21 19 8 35 17 35 21 0 5 17 15 38 24 20 10 53 26 72 37 19 10 43 22 53 25
|
||||
9 3 17 9 17 14 0 5 9 9 19 9 10 0 21 6 24 13 3 8 29 26 59 41 29 15 60 31 68
|
||||
35 8 5 36 19 63 32 26 12 52 28 58 36 6 7 15 13 20 13 5 0 36 18 70 40 34 22
|
||||
70 40 79 40 10 0 20 6 23 13 2 6 49 34 103 62 55 27 108 58 119 68 11 10 30
|
||||
20 42 23 13 4 23 10 23 15 0 5 7 9 14 9 8 0 27 11 42 25 15 14 35 25 45 25 11
|
||||
0 19 5 19 10 0 6 7 10 15 10 7 0 18 6 22 13 4 7 29 23 56 36 26 13 52 30 58
|
||||
38 6 7 18 13 25 13 8 0 14 5 14 10 0 6 9 10 20 10 11 0 20 4 20 8 0 5 18 17
|
||||
40 28 22 10 40 22 40 27 0 4 6 7 14 7 7 0 19 6 25 13 6 8 32 25 58 38 27 13
|
||||
52 29 56 36 4 7 13 13 19 13 5 0 27 13 47 29 20 16 60 41 89 56 29 15 57 36
|
||||
63 46 5 11 17 19 25 19 9 0 22 6 29 13 31 27 44 37 51 37 4 0 31 20 60 45 28
|
||||
25 57 45 63 45 11 0 31 32 31 49 0 18 -90 13 -124 -8 -17 -10 -53 -24 -81 -31
|
||||
-27 -7 -57 -19 -66 -27 -8 -7 -23 -13 -32 -13 -9 0 -19 -4 -22 -10 -3 -5 -17
|
||||
-10 -29 -10 -13 0 -29 -7 -36 -15 -7 -8 -23 -15 -36 -15 -12 0 -26 -4 -29 -10
|
||||
-3 -5 -17 -10 -29 -10 -13 0 -29 -7 -36 -15 -7 -8 -21 -15 -31 -15 -10 0 -21
|
||||
-4 -24 -10 -3 -5 -17 -10 -29 -10 -13 0 -29 -7 -36 -15 -7 -8 -17 -15 -22 -15
|
||||
-5 0 -28 -9 -50 -20 -23 -11 -47 -20 -54 -20 -6 0 -17 -7 -24 -15 -7 -8 -23
|
||||
-15 -36 -15 -12 0 -26 -4 -29 -10 -3 -5 -17 -10 -30 -10 -12 0 -25 -7 -29 -15
|
||||
-3 -8 -12 -15 -19 -15 -8 0 -44 -16 -81 -35 -37 -19 -73 -35 -80 -35 -7 0 -34
|
||||
-11 -60 -25 -26 -14 -56 -25 -67 -25 -10 0 -19 -4 -19 -10 0 -5 -6 -10 -14
|
||||
-10 -7 0 -19 -7 -26 -15 -7 -8 -23 -15 -36 -15 -13 0 -24 -4 -24 -10 0 -5 -11
|
||||
-10 -24 -10 -13 0 -29 -7 -36 -15 -7 -8 -23 -15 -35 -15 -13 0 -25 -3 -27 -7
|
||||
-5 -12 -111 -63 -131 -63 -9 0 -17 -4 -17 -10 0 -5 -6 -10 -14 -10 -17 0 -80
|
||||
-32 -84 -42 -2 -5 -14 -8 -27 -8 -12 0 -28 -7 -35 -15 -7 -8 -21 -15 -31 -15
|
||||
-10 0 -21 -4 -24 -10 -3 -5 -15 -10 -25 -10 -10 0 -20 -3 -22 -7 -5 -11 -67
|
||||
-43 -84 -43 -16 0 -56 -20 -65 -34 -8 -10 -77 -36 -96 -36 -7 0 -24 -11 -39
|
||||
-25 -15 -14 -34 -25 -43 -25 -10 0 -48 -16 -85 -35 -38 -19 -77 -35 -86 -35
|
||||
-10 0 -20 -6 -23 -13 -3 -7 -23 -19 -46 -27 -23 -7 -46 -19 -52 -27 -6 -7 -22
|
||||
-13 -35 -13 -13 0 -24 -3 -24 -7 0 -9 -48 -33 -66 -33 -6 0 -17 -7 -24 -15 -7
|
||||
-8 -23 -15 -36 -15 -13 0 -24 -4 -24 -10 0 -5 -9 -10 -19 -10 -10 0 -21 -7
|
||||
-25 -15 -3 -8 -14 -15 -25 -15 -19 0 -104 -42 -125 -61 -6 -5 -18 -9 -28 -9
|
||||
-22 0 -60 -20 -71 -37 -4 -7 -19 -13 -32 -13 -13 0 -27 -4 -30 -10 -3 -5 -15
|
||||
-10 -26 -10 -10 0 -19 -4 -19 -8 0 -5 -19 -16 -42 -26 -89 -37 -108 -47 -108
|
||||
-56 0 -6 -11 -10 -24 -10 -14 0 -28 -4 -31 -10 -3 -5 -14 -10 -25 -10 -10 0
|
||||
-20 -7 -24 -15 -3 -8 -14 -15 -25 -15 -11 0 -23 -4 -26 -10 -3 -5 -15 -10 -25
|
||||
-10 -10 0 -20 -3 -22 -7 -3 -8 -33 -23 -110 -57 -20 -9 -40 -21 -43 -26 -3 -6
|
||||
-17 -10 -31 -10 -13 0 -24 -4 -24 -10 0 -5 -7 -10 -16 -10 -8 0 -22 -7 -30
|
||||
-15 -9 -8 -24 -15 -35 -15 -10 0 -19 -4 -19 -10 0 -5 -6 -10 -13 -10 -18 0
|
||||
-75 -28 -82 -41 -4 -5 -17 -9 -31 -9 -13 0 -24 -4 -24 -10 0 -5 -9 -10 -19
|
||||
-10 -10 0 -21 -7 -25 -15 -3 -8 -14 -15 -25 -15 -11 0 -23 -4 -26 -10 -3 -5
|
||||
-15 -10 -25 -10 -10 0 -20 -3 -22 -7 -1 -5 -21 -16 -43 -27 -22 -10 -48 -22
|
||||
-57 -27 -10 -5 -24 -9 -32 -9 -8 0 -16 -7 -20 -15 -3 -8 -14 -15 -25 -15 -11
|
||||
0 -23 -4 -26 -10 -3 -5 -14 -10 -24 -10 -10 0 -24 -7 -31 -15 -7 -8 -21 -15
|
||||
-31 -15 -11 0 -19 -3 -19 -7 0 -11 -50 -33 -71 -33 -10 0 -22 -6 -26 -12 -11
|
||||
-18 -49 -38 -72 -38 -10 0 -24 -7 -31 -15 -7 -8 -17 -15 -22 -15 -14 0 -163
|
||||
-73 -166 -82 -2 -4 -14 -8 -27 -8 -13 0 -27 -4 -30 -10 -3 -5 -23 -17 -43 -26
|
||||
-20 -8 -44 -20 -52 -25 -8 -5 -26 -14 -40 -20 -14 -6 -38 -16 -55 -24 -16 -7
|
||||
-37 -16 -45 -19 -8 -3 -28 -15 -45 -25 -16 -11 -40 -20 -52 -20 -13 -1 -23 -5
|
||||
-23 -10 0 -12 -57 -41 -81 -41 -9 0 -19 -3 -21 -7 -1 -5 -28 -20 -58 -36 -89
|
||||
-45 -114 -36 -71 27 15 22 36 59 46 81 10 22 26 47 37 56 10 9 18 21 18 26 0
|
||||
18 29 69 50 88 11 10 20 24 20 32 0 14 18 50 35 69 6 6 22 33 35 60 14 27 27
|
||||
51 30 54 8 8 30 43 46 75 8 17 24 44 34 60 11 17 26 42 32 58 7 15 15 27 19
|
||||
27 4 0 10 11 13 25 4 14 11 25 16 25 6 0 10 8 10 18 0 10 9 28 20 40 11 12 20
|
||||
25 20 30 0 5 7 15 15 22 8 7 15 21 15 31 0 11 5 19 10 19 6 0 10 7 10 15 0 9
|
||||
11 27 25 41 14 14 25 29 25 34 0 12 37 78 61 109 11 13 19 30 19 37 0 7 6 19
|
||||
13 26 16 18 41 62 68 121 12 26 25 47 29 47 4 0 10 8 14 18 7 22 42 82 56 96
|
||||
5 5 10 18 10 28 0 10 5 18 10 18 6 0 10 6 10 14 0 7 11 25 25 40 14 15 25 33
|
||||
25 41 0 8 5 17 10 20 6 3 10 13 10 21 0 8 4 20 10 27 47 58 60 77 61 88 0 8 9
|
||||
28 19 44 10 17 31 53 46 80 14 28 30 52 33 55 4 3 13 21 21 40 7 19 23 46 35
|
||||
60 11 14 26 36 32 50 6 14 18 42 27 63 10 20 21 37 25 37 5 0 21 25 36 55 14
|
||||
30 31 55 36 55 5 0 9 8 9 18 0 9 9 28 20 42 11 14 20 31 20 38 0 7 7 15 15 18
|
||||
8 3 17 15 21 27 10 32 74 118 121 164 24 22 43 48 43 57 0 9 7 19 15 22 8 4
|
||||
15 18 15 33 0 14 9 37 20 51 11 14 20 30 20 37 0 7 7 16 16 21 14 8 14 12 3
|
||||
30 -11 18 -9 24 19 56 24 28 32 46 32 75 0 22 6 44 15 51 8 7 15 26 15 42 0
|
||||
31 21 58 60 74 14 6 43 21 65 32 22 12 65 32 95 47 30 14 62 30 70 35 25 16
|
||||
120 60 129 60 5 0 11 3 13 8 5 11 93 52 112 52 9 0 16 4 16 9 0 11 56 41 77
|
||||
41 7 0 13 5 13 10 0 6 11 10 25 10 14 0 25 4 25 9 0 11 56 41 77 41 7 0 13 5
|
||||
13 10 0 6 9 10 19 10 11 0 22 4 25 9 7 10 105 61 118 61 5 0 25 10 46 23 20
|
||||
13 53 29 72 37 19 8 37 16 40 19 10 12 98 51 113 51 9 0 20 6 24 13 10 16 49
|
||||
37 68 37 8 0 15 5 15 10 0 6 11 10 25 10 14 0 25 4 25 9 0 11 56 41 77 41 7 0
|
||||
13 5 13 10 0 6 11 10 25 10 14 0 25 4 25 8 0 4 26 21 58 36 31 16 73 37 92 47
|
||||
19 10 51 25 70 34 19 9 44 22 55 27 11 6 43 22 70 35 28 13 59 29 70 36 11 6
|
||||
45 24 75 41 30 16 82 44 115 63 33 18 68 36 78 39 9 4 17 10 17 15 0 5 11 9
|
||||
25 9 14 0 25 5 25 10 0 6 9 10 20 10 11 0 20 3 20 8 0 11 124 82 144 82 8 0
|
||||
16 7 20 15 3 8 30 26 59 40 30 13 60 31 67 40 7 8 18 15 25 15 7 0 23 10 37
|
||||
23 13 12 37 28 54 36 49 23 57 27 108 55 64 36 76 49 76 85 0 30 -21 43 -35
|
||||
22z m-3845 -2094 c0 -19 -30 -77 -40 -77 -4 0 -10 -8 -13 -17 -4 -10 -16 -34
|
||||
-29 -54 -13 -19 -38 -61 -56 -91 -18 -31 -40 -62 -47 -68 -8 -7 -15 -18 -15
|
||||
-24 0 -6 -16 -35 -35 -63 -19 -29 -35 -56 -35 -60 0 -5 -11 -20 -25 -35 -14
|
||||
-15 -25 -32 -25 -39 0 -6 -16 -34 -35 -60 -19 -27 -35 -54 -35 -59 0 -6 -4
|
||||
-10 -8 -10 -5 0 -14 -16 -22 -35 -8 -19 -17 -35 -21 -35 -4 0 -14 -12 -22 -27
|
||||
-34 -66 -46 -86 -54 -92 -5 -3 -21 -32 -36 -63 -15 -32 -31 -58 -35 -58 -5 0
|
||||
-16 -19 -26 -42 -10 -24 -29 -54 -41 -68 -12 -14 -29 -41 -38 -61 -9 -19 -25
|
||||
-43 -37 -54 -11 -10 -20 -24 -20 -32 0 -16 -44 -88 -60 -98 -5 -3 -10 -13 -10
|
||||
-21 0 -7 -16 -36 -35 -63 -19 -26 -35 -52 -35 -55 0 -4 -13 -27 -30 -51 -16
|
||||
-24 -30 -47 -30 -51 0 -4 -16 -31 -35 -60 -19 -29 -35 -57 -35 -62 0 -5 -9
|
||||
-17 -20 -27 -12 -11 -28 -35 -37 -54 -21 -44 -65 -115 -75 -119 -5 -2 -8 -12
|
||||
-8 -21 0 -10 -9 -26 -20 -36 -11 -10 -20 -24 -20 -30 0 -7 -7 -18 -15 -25 -8
|
||||
-7 -15 -20 -15 -30 0 -10 -7 -24 -16 -31 -9 -7 -20 -22 -25 -34 -11 -22 -21
|
||||
-40 -62 -102 -15 -24 -27 -46 -27 -49 0 -4 -16 -31 -35 -60 -19 -29 -35 -56
|
||||
-35 -60 0 -3 -16 -29 -35 -55 -19 -27 -35 -56 -35 -64 0 -8 -4 -15 -8 -15 -5
|
||||
0 -16 -16 -25 -35 -9 -19 -21 -35 -27 -35 -5 0 -10 -11 -10 -25 0 -14 -3 -25
|
||||
-8 -25 -4 0 -16 -16 -26 -35 -11 -19 -23 -35 -28 -35 -4 0 -8 -7 -8 -16 0 -9
|
||||
-14 -36 -30 -61 -17 -25 -42 -67 -56 -94 -13 -27 -28 -49 -32 -49 -4 0 -14
|
||||
-17 -21 -37 -7 -21 -23 -49 -35 -63 -27 -32 -46 -66 -46 -83 0 -8 -9 -22 -21
|
||||
-33 -11 -10 -31 -41 -44 -70 -13 -28 -29 -57 -37 -65 -8 -8 -26 -40 -42 -71
|
||||
-15 -32 -31 -58 -36 -58 -4 0 -13 -14 -19 -31 -9 -27 -36 -75 -98 -174 -7 -11
|
||||
-20 -35 -29 -52 -9 -18 -20 -33 -25 -33 -5 0 -9 -9 -9 -20 0 -11 -7 -23 -15
|
||||
-26 -8 -4 -15 -12 -15 -20 0 -13 -45 -92 -59 -104 -12 -10 -31 -50 -31 -64 0
|
||||
-8 -11 -23 -25 -34 -14 -11 -25 -27 -25 -35 0 -9 -4 -19 -10 -22 -5 -3 -10
|
||||
-13 -10 -22 0 -9 -11 -30 -23 -47 -13 -17 -32 -49 -42 -71 -10 -22 -24 -47
|
||||
-31 -55 -16 -20 -73 -120 -80 -142 -4 -10 -10 -18 -15 -18 -5 0 -9 -6 -9 -14
|
||||
0 -7 -11 -25 -25 -40 -14 -15 -25 -36 -25 -47 0 -10 -4 -19 -10 -19 -5 0 -10
|
||||
-7 -10 -17 0 -9 -11 -30 -24 -48 -34 -44 -66 -103 -66 -120 0 -8 -6 -18 -13
|
||||
-22 -6 -4 -22 -28 -35 -53 -49 -95 -91 -165 -101 -168 -6 -2 -11 -9 -11 -16 0
|
||||
-20 -49 -112 -70 -131 -11 -10 -20 -26 -20 -37 0 -10 -4 -18 -10 -18 -5 0 -10
|
||||
-9 -10 -19 0 -11 -4 -22 -9 -26 -6 -3 -22 -30 -36 -60 -15 -30 -33 -59 -41
|
||||
-66 -8 -6 -14 -18 -14 -25 0 -8 -4 -14 -10 -14 -5 0 -10 -8 -10 -17 0 -10 -10
|
||||
-34 -22 -53 -43 -67 -47 -75 -53 -90 -4 -8 -10 -22 -15 -30 -5 -8 -23 -42 -39
|
||||
-75 -16 -33 -39 -70 -50 -82 -12 -12 -21 -28 -22 -35 0 -7 -13 -35 -29 -63
|
||||
-15 -27 -40 -74 -56 -103 -15 -30 -33 -56 -41 -59 -7 -3 -13 -13 -13 -23 0
|
||||
-18 -29 -77 -96 -193 -19 -32 -34 -62 -34 -68 0 -5 -4 -9 -10 -9 -5 0 -10 -7
|
||||
-10 -15 0 -9 -11 -33 -25 -55 -14 -22 -25 -46 -25 -55 0 -8 -4 -15 -10 -15 -5
|
||||
0 -10 -7 -10 -15 0 -9 -16 -41 -35 -71 -19 -30 -35 -60 -35 -67 0 -8 -10 -23
|
||||
-22 -34 -30 -27 -60 -16 -78 29 -8 18 -20 37 -27 41 -7 4 -13 17 -13 27 0 11
|
||||
-4 20 -10 20 -5 0 -10 9 -10 19 0 11 -4 23 -10 26 -5 3 -10 14 -10 25 0 10 -6
|
||||
20 -12 23 -7 2 -26 29 -41 58 -16 30 -41 77 -57 104 -15 28 -29 57 -29 66 -1
|
||||
9 -8 23 -17 30 -16 13 -35 47 -89 159 -15 30 -33 62 -40 70 -20 23 -55 89 -55
|
||||
104 0 8 -7 16 -15 20 -8 3 -15 15 -15 26 0 11 -4 20 -8 20 -5 0 -14 16 -22 35
|
||||
-8 19 -16 35 -20 35 -5 0 -19 26 -54 100 -9 19 -34 62 -56 95 -21 33 -39 68
|
||||
-39 78 -1 9 -5 17 -10 17 -5 0 -14 14 -20 32 -6 17 -17 39 -25 47 -21 24 -56
|
||||
90 -56 106 0 8 -11 22 -25 31 -14 9 -25 24 -25 33 0 9 -20 43 -45 76 -51 67
|
||||
-58 113 -25 155 11 14 20 31 20 38 0 7 7 15 15 18 8 4 15 17 15 30 0 13 5 24
|
||||
10 24 6 0 10 7 10 15 0 14 26 62 40 75 3 3 11 16 18 30 7 14 21 39 32 55 11
|
||||
17 22 37 26 45 9 25 53 111 89 175 18 33 47 87 64 120 17 33 42 78 56 100 14
|
||||
22 30 50 35 63 5 12 14 22 20 22 5 0 10 11 10 25 0 14 5 25 10 25 6 0 10 6 10
|
||||
14 0 16 25 66 37 75 5 3 21 32 36 64 15 31 31 57 36 57 4 0 8 6 8 13 0 18 41
|
||||
105 54 112 5 4 21 30 35 58 14 29 33 66 42 82 10 17 24 46 32 65 8 19 20 40
|
||||
26 45 17 14 54 83 54 100 0 8 7 15 15 15 9 0 15 9 15 25 0 14 5 25 10 25 6 0
|
||||
10 6 10 14 0 17 25 67 38 75 5 3 21 31 35 61 15 30 33 66 41 80 7 14 22 42 32
|
||||
63 11 21 27 45 37 54 9 9 17 23 18 32 0 9 9 30 19 46 10 17 31 55 46 85 16 30
|
||||
38 69 51 86 13 17 23 36 23 42 0 7 5 12 10 12 6 0 10 6 10 14 0 15 55 122 75
|
||||
146 7 8 30 51 51 95 21 44 47 89 56 99 10 11 18 26 18 33 0 8 7 16 15 19 8 4
|
||||
15 12 15 20 0 8 4 22 9 32 5 9 17 34 27 55 10 20 21 37 25 37 3 0 16 21 29 48
|
||||
35 73 53 107 60 112 4 3 19 29 34 58 14 28 31 52 36 52 6 0 10 9 10 20 0 11 9
|
||||
29 20 40 11 11 20 23 20 27 0 14 73 158 82 161 4 2 8 11 8 21 0 22 51 71 73
|
||||
71 9 0 17 4 17 10 0 5 11 12 25 16 14 3 25 10 25 15 0 5 8 9 18 10 9 0 31 9
|
||||
47 19 17 11 46 26 65 35 38 18 96 47 139 69 14 7 49 25 76 38 28 14 126 64
|
||||
218 112 93 47 175 86 182 86 7 0 15 4 17 9 2 5 39 26 83 48 44 22 89 45 100
|
||||
51 11 6 70 35 130 65 61 30 130 65 155 77 25 13 56 28 70 34 14 6 27 14 30 17
|
||||
9 10 73 39 87 39 7 0 13 5 13 10 0 6 9 10 19 10 11 0 22 4 26 9 7 13 64 41 82
|
||||
41 7 0 13 5 13 10 0 6 6 10 14 10 7 0 19 7 26 15 7 8 21 15 31 15 11 0 19 5
|
||||
19 10 0 6 8 10 18 11 9 0 31 9 47 20 17 10 41 24 55 29 14 6 65 31 114 56 49
|
||||
24 95 44 102 44 7 0 14 4 16 8 4 10 108 62 124 62 6 0 14 7 18 15 3 8 15 15
|
||||
26 15 11 0 20 5 20 10 0 6 6 10 13 10 18 0 75 28 82 41 4 5 15 9 26 9 10 0 19
|
||||
5 19 10 0 6 8 10 18 11 9 0 31 9 47 19 17 11 45 26 63 35 72 34 101 49 192 96
|
||||
97 50 130 59 130 36z m-3972 -1998 c3 -18 -46 -132 -59 -137 -5 -2 -9 -9 -9
|
||||
-16 0 -16 -52 -120 -62 -124 -4 -2 -8 -10 -8 -17 0 -17 -50 -109 -62 -113 -4
|
||||
-2 -8 -11 -9 -20 0 -9 -14 -37 -30 -62 -16 -25 -32 -53 -35 -62 -4 -10 -10
|
||||
-18 -15 -18 -5 0 -9 -9 -9 -21 0 -11 -11 -37 -25 -57 -14 -20 -25 -40 -25 -45
|
||||
0 -11 -33 -78 -80 -162 -22 -38 -47 -86 -57 -105 -10 -19 -21 -36 -25 -38 -5
|
||||
-2 -8 -12 -8 -23 0 -11 -7 -22 -15 -25 -8 -4 -15 -12 -15 -18 0 -7 -9 -31 -20
|
||||
-54 -11 -22 -20 -45 -20 -50 0 -6 -7 -12 -15 -16 -8 -3 -15 -11 -15 -19 0 -20
|
||||
-50 -117 -61 -117 -5 0 -9 -5 -9 -12 0 -13 -31 -76 -60 -123 -10 -16 -19 -38
|
||||
-19 -47 -1 -10 -5 -18 -11 -18 -5 0 -10 -8 -10 -19 0 -10 -7 -24 -15 -31 -8
|
||||
-7 -15 -19 -15 -26 0 -8 -4 -14 -10 -14 -5 0 -10 -11 -10 -25 0 -14 -4 -25
|
||||
-10 -25 -5 0 -10 -6 -10 -14 0 -8 -6 -21 -13 -28 -7 -7 -26 -40 -42 -72 -45
|
||||
-89 -49 -96 -57 -96 -5 0 -8 -6 -8 -13 0 -21 -30 -77 -41 -77 -5 0 -9 -8 -9
|
||||
-18 0 -10 -4 -22 -9 -28 -5 -5 -14 -26 -21 -46 -6 -21 -15 -38 -19 -38 -4 0
|
||||
-13 -17 -20 -37 -8 -21 -28 -60 -45 -88 -18 -27 -40 -66 -49 -85 -10 -19 -26
|
||||
-48 -37 -65 -10 -16 -19 -38 -19 -47 -1 -10 -5 -18 -11 -18 -5 0 -10 -9 -10
|
||||
-20 0 -11 -7 -23 -15 -26 -8 -4 -15 -12 -15 -18 0 -12 -19 -52 -92 -186 -22
|
||||
-41 -49 -93 -59 -115 -11 -22 -24 -47 -29 -55 -5 -8 -21 -40 -35 -70 -14 -30
|
||||
-30 -62 -35 -70 -5 -8 -17 -31 -27 -50 -9 -19 -31 -57 -47 -85 -17 -27 -45
|
||||
-77 -61 -110 -17 -33 -33 -61 -37 -63 -5 -2 -8 -10 -8 -18 0 -7 -10 -30 -21
|
||||
-51 -12 -21 -51 -94 -86 -163 -36 -69 -71 -132 -79 -141 -8 -8 -14 -20 -14
|
||||
-25 0 -15 -50 -113 -61 -120 -5 -3 -9 -14 -9 -25 0 -10 -4 -19 -10 -19 -5 0
|
||||
-10 -6 -10 -14 0 -18 -32 -76 -42 -76 -5 0 -8 -7 -8 -14 0 -8 -10 -34 -23 -58
|
||||
-25 -45 -79 -145 -96 -175 -6 -10 -19 -36 -29 -58 -26 -55 -71 -146 -83 -167
|
||||
-9 -17 -29 -55 -119 -223 -23 -44 -46 -81 -51 -83 -5 -2 -9 -12 -9 -23 0 -10
|
||||
-4 -19 -10 -19 -5 0 -10 -8 -11 -17 0 -10 -9 -31 -20 -48 -11 -16 -22 -38 -25
|
||||
-47 -8 -22 -98 -200 -149 -293 -21 -38 -54 -100 -73 -137 -19 -38 -38 -68 -43
|
||||
-68 -5 0 -9 -11 -9 -25 0 -14 -4 -25 -10 -25 -5 0 -10 -6 -10 -13 0 -21 -30
|
||||
-77 -41 -77 -5 0 -9 -11 -9 -25 0 -14 -4 -25 -10 -25 -5 0 -10 -7 -10 -15 0
|
||||
-19 -21 -58 -37 -68 -7 -4 -13 -14 -13 -22 0 -21 -42 -115 -52 -115 -4 0 -8
|
||||
-4 -8 -10 0 -10 -44 -104 -60 -130 -5 -8 -21 -41 -35 -72 -14 -31 -30 -60 -35
|
||||
-63 -6 -3 -10 -15 -10 -26 0 -10 -4 -19 -10 -19 -5 0 -10 -9 -10 -20 0 -14 -7
|
||||
-20 -21 -20 -21 0 -21 1 -14 218 4 119 11 227 16 240 5 13 9 71 9 128 0 66 5
|
||||
113 13 127 11 21 20 127 33 397 3 69 10 129 15 135 5 5 9 64 9 131 0 96 3 124
|
||||
15 134 12 10 15 38 15 136 0 71 4 125 10 129 6 4 10 60 10 135 0 75 4 131 10
|
||||
135 6 4 10 56 10 124 0 88 4 122 15 137 11 14 14 50 15 144 0 70 4 130 9 135
|
||||
6 6 12 69 15 140 4 72 9 187 12 257 4 93 9 130 20 138 11 10 14 41 14 141 0
|
||||
74 4 130 10 134 6 4 10 60 10 135 0 75 4 131 10 135 6 4 10 58 10 129 0 96 3
|
||||
126 14 136 11 8 16 45 20 143 3 73 8 168 11 212 3 44 9 155 14 247 7 125 13
|
||||
173 25 190 9 12 16 38 16 58 0 33 4 37 58 63 31 15 65 27 74 27 9 0 18 4 20 8
|
||||
4 11 108 62 125 62 7 0 13 4 13 9 0 5 11 12 25 15 14 4 25 11 25 16 0 6 6 10
|
||||
13 10 17 0 121 51 125 62 2 4 9 8 16 8 7 0 26 6 42 14 16 8 56 26 89 41 90 40
|
||||
208 95 227 106 10 5 25 9 33 9 8 0 15 4 15 9 0 11 56 41 77 41 7 0 13 5 13 10
|
||||
0 6 11 10 24 10 13 0 26 7 30 15 3 8 15 15 26 15 11 0 20 5 20 10 0 6 8 10 18
|
||||
11 9 0 31 9 47 20 17 10 37 21 45 25 8 3 24 9 35 14 34 13 178 81 205 97 44
|
||||
24 132 63 145 63 7 0 15 7 19 15 3 8 12 15 20 15 9 0 31 9 51 20 20 11 42 20
|
||||
49 20 8 0 16 7 20 15 3 8 15 15 26 15 11 0 20 5 20 10 0 6 8 10 18 11 9 0 31
|
||||
9 47 19 17 11 44 26 60 33 70 33 102 49 135 66 19 10 76 37 125 61 185 87 240
|
||||
113 255 121 8 5 40 20 70 34 30 14 62 30 70 35 8 5 31 17 50 26 83 39 97 46
|
||||
125 64 35 22 58 21 63 -1z m-1168 -3709 c7 -22 22 -47 32 -57 10 -9 28 -35 39
|
||||
-57 11 -23 36 -68 54 -101 19 -33 39 -69 44 -80 5 -12 16 -27 25 -34 9 -7 16
|
||||
-22 16 -32 0 -11 5 -19 10 -19 6 0 15 -10 20 -22 5 -13 21 -41 34 -63 14 -22
|
||||
37 -62 52 -90 14 -27 33 -54 40 -58 8 -4 14 -15 14 -24 0 -8 14 -35 30 -60 26
|
||||
-39 51 -81 95 -164 5 -11 13 -19 17 -19 5 0 8 -10 8 -23 0 -12 11 -31 25 -41
|
||||
14 -10 25 -27 25 -37 0 -11 5 -19 10 -19 6 0 10 -8 10 -18 0 -10 8 -27 19 -38
|
||||
10 -11 31 -42 46 -70 15 -27 38 -63 51 -81 13 -17 24 -37 24 -44 0 -6 15 -32
|
||||
33 -58 18 -25 39 -60 46 -77 15 -39 5 -84 -19 -84 -11 0 -22 -4 -25 -10 -3 -5
|
||||
-23 -17 -43 -26 -20 -9 -106 -50 -190 -90 -84 -41 -161 -74 -172 -74 -11 0
|
||||
-20 -4 -20 -9 0 -11 -56 -41 -77 -41 -7 0 -13 -4 -13 -10 0 -5 -11 -10 -25
|
||||
-10 -14 0 -25 -4 -25 -9 0 -11 -56 -41 -77 -41 -7 0 -13 -4 -13 -10 0 -5 -9
|
||||
-10 -19 -10 -11 0 -22 -4 -25 -9 -7 -10 -105 -61 -118 -61 -5 0 -25 -10 -46
|
||||
-23 -20 -13 -53 -29 -72 -37 -19 -8 -37 -17 -40 -20 -4 -6 -62 -34 -110 -54
|
||||
-8 -3 -28 -15 -45 -25 -16 -11 -38 -20 -47 -20 -10 -1 -18 -5 -18 -11 0 -5
|
||||
-11 -10 -25 -10 -14 0 -25 -4 -25 -9 0 -11 -56 -41 -77 -41 -7 0 -13 -4 -13
|
||||
-10 0 -5 -7 -10 -15 -10 -9 0 -33 -11 -55 -25 -22 -14 -46 -25 -55 -25 -8 0
|
||||
-15 -4 -15 -10 0 -5 -7 -10 -15 -10 -15 0 -55 -19 -65 -31 -3 -3 -23 -14 -45
|
||||
-23 -22 -10 -49 -23 -60 -29 -54 -30 -231 -117 -238 -117 -4 0 -25 -11 -47
|
||||
-25 -22 -14 -46 -25 -55 -25 -8 0 -15 -4 -15 -10 0 -5 -6 -10 -13 -10 -18 0
|
||||
-75 -28 -82 -41 -4 -5 -15 -9 -26 -9 -10 0 -19 -4 -19 -10 0 -5 -6 -10 -13
|
||||
-10 -18 0 -75 -28 -82 -41 -9 -14 -41 -11 -54 5 -8 10 -8 16 3 25 8 7 26 36
|
||||
41 66 14 30 31 55 36 55 5 0 9 6 9 14 0 21 43 98 58 103 6 3 12 11 12 19 0 7
|
||||
9 29 20 49 11 20 20 42 20 50 0 8 6 18 13 22 12 8 27 33 80 138 17 33 38 71
|
||||
48 85 31 46 59 100 59 115 0 8 4 15 9 15 5 0 24 29 42 65 18 36 41 74 51 84
|
||||
10 11 18 29 18 41 0 11 5 20 10 20 6 0 10 8 10 19 0 10 7 24 15 31 8 7 15 19
|
||||
15 26 0 8 4 14 8 14 4 0 13 12 20 28 7 15 23 47 37 72 14 25 29 57 34 73 6 15
|
||||
15 27 20 27 6 0 11 6 11 14 0 7 9 25 20 39 11 14 20 32 20 41 0 9 7 19 15 22
|
||||
8 4 15 12 15 18 0 15 37 90 65 131 12 17 34 56 50 88 16 31 33 57 37 57 4 0 8
|
||||
8 8 18 0 23 28 82 40 82 4 0 17 21 29 48 31 67 83 163 106 197 12 17 27 44 34
|
||||
60 7 17 17 37 22 45 5 8 16 31 25 50 10 19 32 56 51 83 18 26 33 51 33 55 0
|
||||
16 52 112 61 112 5 0 9 7 9 16 0 9 13 38 28 63 15 25 36 62 46 81 17 35 33 63
|
||||
50 88 12 20 50 -19 66 -68z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 42 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 166 155.3"><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" fill="#76b3e1"/><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="27.5" y1="3" x2="152" y2="63.5"><stop offset=".1" stop-color="#76b3e1"/><stop offset=".3" stop-color="#dcf2fd"/><stop offset="1" stop-color="#76b3e1"/></linearGradient><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" opacity=".3" fill="url(#a)"/><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" fill="#518ac8"/><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="95.8" y1="32.6" x2="74" y2="105.2"><stop offset="0" stop-color="#76b3e1"/><stop offset=".5" stop-color="#4377bb"/><stop offset="1" stop-color="#1f3b77"/></linearGradient><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" opacity=".3" fill="url(#b)"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="18.4" y1="64.2" x2="144.3" y2="149.8"><stop offset="0" stop-color="#315aa9"/><stop offset=".5" stop-color="#518ac8"/><stop offset="1" stop-color="#315aa9"/></linearGradient><path d="M134 80a45 45 0 00-48-15L24 85 4 120l112 19 20-36c4-7 3-15-2-23z" fill="url(#c)"/><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="75.2" y1="74.5" x2="24.4" y2="260.8"><stop offset="0" stop-color="#4377bb"/><stop offset=".5" stop-color="#1a336b"/><stop offset="1" stop-color="#1a336b"/></linearGradient><path d="M114 115a45 45 0 00-48-15L4 120s53 40 94 30l3-1c17-5 23-21 13-34z" fill="url(#d)"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,81 +0,0 @@
|
||||
import { getProfile } from '@api/user'
|
||||
import Logo from '@assets/images/logo.svg'
|
||||
import { useSiteContext } from '@context/SiteContext'
|
||||
import useAuth from '@hooks/useAuth'
|
||||
import useToast from '@hooks/useToast'
|
||||
import { A } from '@solidjs/router'
|
||||
import { IconLogout, IconMenuDeep } from '@tabler/icons-solidjs'
|
||||
import { Show, onMount } from 'solid-js'
|
||||
|
||||
export default function Header() {
|
||||
const { store, setAuth, setUser } = useSiteContext()
|
||||
const { clickLogOut } = useAuth(setAuth)
|
||||
const notify = useToast()
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const resp = await getProfile()
|
||||
if (resp.status === 200) {
|
||||
setUser(resp.data)
|
||||
}
|
||||
} catch (error) {
|
||||
notify.error({
|
||||
title: 'Get profile fail!',
|
||||
closable: false,
|
||||
description: error?.data || 'Can not get user profile!',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const logOut = async () => {
|
||||
try {
|
||||
await clickLogOut()
|
||||
} catch (error) {
|
||||
console.log({
|
||||
status: 'danger',
|
||||
title: 'Logout fail!',
|
||||
closable: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<header class="w-full navbar py-3 px-4 items-center justify-between bg-emerald-500">
|
||||
<div class="flex items-center justify-end">
|
||||
<A href="/" class="text-white flex items-center hover:text-white">
|
||||
<img src={Logo} class="w-8" alt="Logo" />
|
||||
<span class="ml-2 text-2xl">Fuware</span>
|
||||
</A>
|
||||
</div>
|
||||
<Show when={store.auth}>
|
||||
<div class="flex items-center justify-end">
|
||||
<div class="avatar">
|
||||
<div class="w-9 rounded-full">
|
||||
<A href="/me" class="hidden lg:block">
|
||||
<img
|
||||
src={`https://ui-avatars.com/api/?name=${store.userInfo?.name}`}
|
||||
alt="avatar"
|
||||
/>
|
||||
</A>
|
||||
</div>
|
||||
</div>
|
||||
<A
|
||||
href="/me"
|
||||
class="mx-3 text-white hover:text-white hidden lg:block"
|
||||
>
|
||||
{store.userInfo?.name}
|
||||
</A>
|
||||
<button class="btn btn-ghost btn-sm hidden lg:block" onClick={logOut}>
|
||||
<IconLogout size={16} />
|
||||
</button>
|
||||
<label
|
||||
for="nav-menu"
|
||||
class="btn btn-ghost btn-sm drawer-button pr-0 lg:hidden"
|
||||
>
|
||||
<IconMenuDeep size={25} color="white" />
|
||||
</label>
|
||||
</div>
|
||||
</Show>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
import { useSiteContext } from '@context/SiteContext'
|
||||
import useAuth from '@hooks/useAuth'
|
||||
import useLanguage from '@hooks/useLanguage'
|
||||
import { A } from '@solidjs/router'
|
||||
import {
|
||||
IconBuildingWarehouse,
|
||||
IconDashboard,
|
||||
IconLogout,
|
||||
IconTriangle
|
||||
} from '@tabler/icons-solidjs'
|
||||
import { For, Show, mergeProps } from 'solid-js'
|
||||
import { Dynamic } from 'solid-js/web'
|
||||
import './navbar.scss'
|
||||
|
||||
const language = useLanguage()
|
||||
|
||||
export const NAV_ITEM = (admin = false) => [
|
||||
{
|
||||
path: '/dashboard',
|
||||
show: admin,
|
||||
icon: IconDashboard,
|
||||
text: language?.ui.dashboard,
|
||||
},
|
||||
{
|
||||
path: '/warehouse',
|
||||
show: true,
|
||||
icon: IconBuildingWarehouse,
|
||||
text: language?.ui.location,
|
||||
},
|
||||
]
|
||||
|
||||
function NavbarItem(props) {
|
||||
const merged = mergeProps({ active: true }, props)
|
||||
return (
|
||||
<Show
|
||||
when={merged.active && merged.path}
|
||||
fallback={
|
||||
<>
|
||||
<Dynamic component={merged.icon} />
|
||||
{merged.text}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<A class="hover:text-fu-black" href={merged.path}>
|
||||
<Dynamic component={merged.icon} />
|
||||
{merged.text}
|
||||
</A>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function NavbarWithChildren(props) {
|
||||
return (
|
||||
<li class="mb-2">
|
||||
<h2 class="menu-title flex items-center gap-2">
|
||||
<NavbarItem {...props} active={false} />
|
||||
</h2>
|
||||
<ul class="pl-4">
|
||||
<For each={props.children}>
|
||||
{(child) => {
|
||||
if (!child.show) return
|
||||
if (child.children) {
|
||||
return <NavbarWithChildren {...child} />
|
||||
} else {
|
||||
return (
|
||||
<li class="mb-2">
|
||||
<NavbarItem {...child} />
|
||||
</li>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
</ul>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Navbar() {
|
||||
const { store, setAuth } = useSiteContext()
|
||||
const { clickLogOut } = useAuth(setAuth)
|
||||
|
||||
const logOut = async () => {
|
||||
try {
|
||||
await clickLogOut()
|
||||
} catch (error) {
|
||||
console.log({
|
||||
status: 'danger',
|
||||
title: 'Logout fail!',
|
||||
closable: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="drawer-side lg:h-[calc(100svh-64px)]">
|
||||
<label for="nav-menu" aria-label="close sidebar" class="drawer-overlay" />
|
||||
<div class="bg-base-200 w-80 min-h-full">
|
||||
<Show when={store.auth}>
|
||||
<div class="flex items-center justify-between px-3 pt-5 lg:hidden">
|
||||
<div class="avatar">
|
||||
<div class="w-9 mask mask-hexagon">
|
||||
<img
|
||||
src={`https://ui-avatars.com/api/?name=${store.userInfo?.name}`}
|
||||
alt="avatar"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span class="mx-3 line-clamp-1">
|
||||
<A href="/me">{store.userInfo?.name}</A>
|
||||
</span>
|
||||
<button class="btn btn-ghost btn-sm" onClick={logOut}>
|
||||
<IconLogout size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<div class="divider divider-success mb-0 lg:hidden">
|
||||
<IconTriangle size={30} />
|
||||
</div>
|
||||
</Show>
|
||||
<ul class="menu w-80 text-base-content pt-3 px-3 pb-6">
|
||||
<For each={NAV_ITEM(store?.userInfo?.isAdmin)}>
|
||||
{(item) => {
|
||||
if (!item.show) return
|
||||
if (item.children) {
|
||||
return <NavbarWithChildren {...item} />
|
||||
} else {
|
||||
return (
|
||||
<li class="mb-2">
|
||||
<NavbarItem {...item} />
|
||||
</li>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './Navbar'
|
||||
export { default } from './Navbar'
|
||||
@@ -1,62 +0,0 @@
|
||||
import {
|
||||
IconCircleCheck,
|
||||
IconFaceIdError,
|
||||
IconInfoCircle,
|
||||
IconX,
|
||||
} from '@tabler/icons-solidjs'
|
||||
import { Show } from 'solid-js'
|
||||
import { Dynamic } from 'solid-js/web'
|
||||
|
||||
const STATUS = Object.freeze(
|
||||
new Proxy(
|
||||
{
|
||||
success: {
|
||||
icon: IconCircleCheck,
|
||||
color: 'text-green-500',
|
||||
},
|
||||
error: {
|
||||
icon: IconFaceIdError,
|
||||
color: 'text-red-500',
|
||||
},
|
||||
info: {
|
||||
icon: IconInfoCircle,
|
||||
color: 'text-blue-500',
|
||||
},
|
||||
},
|
||||
{
|
||||
get: (target, prop) =>
|
||||
target[prop] ?? { icon: IconInfoCircle, color: 'text-blue-500' },
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
export default function Notify(props) {
|
||||
return (
|
||||
<div class="bg-white border border-slate-300 w-max h-20 shadow-lg rounded-md gap-4 p-4 flex flex-row items-center justify-center">
|
||||
<section class="w-6 h-full flex flex-col items-center justify-start">
|
||||
<Dynamic
|
||||
component={STATUS[props.status].icon}
|
||||
size={30}
|
||||
class={STATUS[props.status].color}
|
||||
/>
|
||||
</section>
|
||||
<section class="h-full flex flex-col items-start justify-end gap-1">
|
||||
<Show when={props.title}>
|
||||
<h1
|
||||
class={`text-base font-semibold text-zinc-800 antialiased ${STATUS[props.status].color}`}
|
||||
>
|
||||
{props.title}
|
||||
</h1>
|
||||
</Show>
|
||||
<p class="text-sm font-medium text-black antialiased">
|
||||
{props.description}
|
||||
</p>
|
||||
</section>
|
||||
<Show when={props.onClose}>
|
||||
<section class="w-5 h-full flex flex-col items-center justify-start">
|
||||
<IconX size={20} class="cursor-pointer" onclick={props.onClose} />
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Field } from 'solid-form-handler'
|
||||
import { Show, splitProps } from 'solid-js'
|
||||
import { Dynamic } from 'solid-js/web'
|
||||
|
||||
export default function Finput(props) {
|
||||
const [local, rest] = splitProps(props, ['label', 'icon'])
|
||||
|
||||
return (
|
||||
<Field
|
||||
{...props}
|
||||
mode="input"
|
||||
render={(field) => (
|
||||
<div class="form-control w-full [&:not(:last-child)]:pb-3">
|
||||
<Show when={local.label}>
|
||||
<div class="label">
|
||||
<span class="label-text">{local.label}</span>
|
||||
</div>
|
||||
</Show>
|
||||
<label
|
||||
class="input input-bordered flex items-center gap-2 w-full"
|
||||
classList={{ 'input-error': field.helpers.error }}
|
||||
>
|
||||
<Show when={local.icon}>
|
||||
<Dynamic component={local.icon} size={18} />
|
||||
</Show>
|
||||
<input {...rest} class="grow w-full" {...field.props} />
|
||||
</label>
|
||||
<Show when={field.helpers.error}>
|
||||
<div class="label">
|
||||
<span class="label-text-alt text-red-600">
|
||||
{field.helpers.errorMessage}
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './TextInput'
|
||||
@@ -1,56 +0,0 @@
|
||||
import { STORE_KEY } from '@utils/enum'
|
||||
import { Helpers } from '@utils/helper'
|
||||
import { createContext, onMount, useContext } from 'solid-js'
|
||||
import { createStore, produce } from 'solid-js/store'
|
||||
|
||||
export const SiteContext = createContext()
|
||||
|
||||
export function SiteContextProvider(props) {
|
||||
const [store, setStore] = createStore({
|
||||
auth: false,
|
||||
userInfo: null,
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
const storeData = Helpers.decrypt(localStorage.getItem(STORE_KEY))
|
||||
if (!storeData) return
|
||||
setStore(storeData)
|
||||
})
|
||||
|
||||
const setLocalStore = () => {
|
||||
if (store.auth) {
|
||||
localStorage.setItem(STORE_KEY, Helpers.encrypt(store))
|
||||
} else {
|
||||
localStorage.removeItem(STORE_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
const setAuth = ({ auth, user }) => {
|
||||
setStore(
|
||||
produce((s) => {
|
||||
s.auth = auth
|
||||
s.userInfo = user
|
||||
}),
|
||||
)
|
||||
setLocalStore()
|
||||
}
|
||||
|
||||
const setUser = (user) => {
|
||||
setStore(
|
||||
produce((s) => {
|
||||
s.userInfo = user
|
||||
}),
|
||||
)
|
||||
setLocalStore()
|
||||
}
|
||||
|
||||
return (
|
||||
<SiteContext.Provider value={{ store, setAuth, setUser }}>
|
||||
{props.children}
|
||||
</SiteContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useSiteContext() {
|
||||
return useContext(SiteContext)
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { getLogout, postLogin } from '@api/auth'
|
||||
import { useNavigate } from '@solidjs/router'
|
||||
import { LOGIN_KEY } from '@utils/enum'
|
||||
import { Helpers } from '@utils/helper'
|
||||
|
||||
export default function useAuth(setAuth) {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const clickLogIn = async (username, password, cbFormReset) => {
|
||||
const resp = await postLogin({ username, password })
|
||||
|
||||
if (resp.status === 200) {
|
||||
const token = resp.data || {}
|
||||
if (token) {
|
||||
const { name, ...rest } = token
|
||||
setAuth({ auth: true, user: { name } })
|
||||
localStorage.setItem(LOGIN_KEY, Helpers.encrypt(JSON.stringify(rest)))
|
||||
}
|
||||
|
||||
cbFormReset()
|
||||
navigate('/', { replace: true })
|
||||
}
|
||||
}
|
||||
|
||||
const clickLogOut = async () => {
|
||||
await getLogout()
|
||||
Helpers.clearStorage()
|
||||
setAuth({ auth: false, user: null })
|
||||
navigate('/login', { replace: true })
|
||||
}
|
||||
|
||||
return {
|
||||
clickLogOut,
|
||||
clickLogIn,
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
export default function useLanguage(selectLanguage = 'vi') {
|
||||
const data = import.meta.glob('@lang/*.json', {
|
||||
import: 'default',
|
||||
eager: true,
|
||||
})
|
||||
|
||||
const imp = {}
|
||||
|
||||
for (const path in data) {
|
||||
const keypath = path.match(/\/[a-zA-Z]+\./)[0].replace(/\/(\w+)\./, '$1')
|
||||
imp[keypath] = data[path]
|
||||
}
|
||||
|
||||
return imp[selectLanguage]
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import Notify from '@components/Notify'
|
||||
import toast from 'solid-toast'
|
||||
|
||||
export default function useToast() {
|
||||
const notify = {}
|
||||
|
||||
notify.show = ({ status, title, description, closable = false }) => {
|
||||
return toast.custom((t) => (
|
||||
<Notify
|
||||
status={status}
|
||||
title={title}
|
||||
description={description}
|
||||
onClose={closable ? () => toast.dismiss(t.id) : null}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
notify.success = ({ title, description, closable = false }) => {
|
||||
return notify.show({ status: 'success', title, description, closable })
|
||||
}
|
||||
|
||||
notify.error = ({ title, description, closable = false }) => {
|
||||
return notify.show({ status: 'error', title, description, closable })
|
||||
}
|
||||
|
||||
notify.info = ({ title, description, closable = false }) => {
|
||||
return notify.show({ status: 'info', title, description, closable })
|
||||
}
|
||||
|
||||
return notify
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import Layout from '@pages/Layout'
|
||||
import { Route, Router } from '@solidjs/router'
|
||||
import { For, lazy } from 'solid-js'
|
||||
import { render } from 'solid-js/web'
|
||||
import App from './App'
|
||||
import './index.scss'
|
||||
import { ROUTES } from './routes'
|
||||
|
||||
const root = document.getElementById('root')
|
||||
|
||||
render(
|
||||
() => (
|
||||
<Router root={App}>
|
||||
<Route path="/login" component={lazy(() => import('@pages/Login'))} />
|
||||
<Route path="/" component={Layout}>
|
||||
<For each={ROUTES}>
|
||||
{(route) =>
|
||||
route.show && (
|
||||
<Route
|
||||
path={route.path}
|
||||
component={route.components}
|
||||
matchFilters={route.filter}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</For>
|
||||
</Route>
|
||||
<Route path="*" component={lazy(() => import('@pages/NotFound'))} />
|
||||
</Router>
|
||||
),
|
||||
root,
|
||||
)
|
||||
@@ -1,27 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import './assets/css/fu-theme.scss';
|
||||
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(0, 0, 0, 1);
|
||||
background-color: #ffffff;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"login": "Login",
|
||||
"logout": "Logout"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"ui": {
|
||||
"username": "Tên người dùng",
|
||||
"password": "Mật khẩu",
|
||||
"login": "Đăng Nhập",
|
||||
"logout": "Đăng xuất",
|
||||
"dashboard": "Bảng điều khiển",
|
||||
"profile": "Hồ sơ",
|
||||
"changeInfo": "Thông tin tài khoản",
|
||||
"save": "Lưu",
|
||||
"clear": "Xóa",
|
||||
"location": "Nhà kho",
|
||||
"displayName": "Display Name",
|
||||
"newPassword": "New Password",
|
||||
"confirmNewPassword": "Confirm New Password"
|
||||
},
|
||||
"message": {
|
||||
"CREATED_USER": "Username already registered!",
|
||||
"LOGIN_WRONG": "Your username or password input is wrong!",
|
||||
"USER_LOCK": "Your Account was locked"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function Dashboard() {
|
||||
return <>Dashboard</>
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { NAV_ITEM } from '@components/Navbar'
|
||||
import { useSiteContext } from '@context/SiteContext'
|
||||
import { useNavigate } from '@solidjs/router'
|
||||
import { onMount } from 'solid-js'
|
||||
|
||||
function getFirstItem(array) {
|
||||
const first = array.filter((item) => item.show)[0]
|
||||
if (first.children) {
|
||||
return getFirstItem(first.children)
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const { store } = useSiteContext()
|
||||
const navigate = useNavigate()
|
||||
|
||||
onMount(() => {
|
||||
const first = getFirstItem(NAV_ITEM(store?.userInfo?.isAdmin))
|
||||
navigate(first ? first.path : '/me', { replace: true })
|
||||
})
|
||||
|
||||
return <></>
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import Header from '@components/Header'
|
||||
import Navbar from '@components/Navbar'
|
||||
import { useSiteContext } from '@context/SiteContext'
|
||||
import { useNavigate } from '@solidjs/router'
|
||||
import { onMount } from 'solid-js'
|
||||
|
||||
export default function Layout(props) {
|
||||
const { store } = useSiteContext()
|
||||
const navigate = useNavigate()
|
||||
|
||||
onMount(() => {
|
||||
if (!store.auth) {
|
||||
navigate('/login', { replace: true })
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="layer-top">
|
||||
<Header />
|
||||
<div id="main-page">
|
||||
<div class="drawer lg:drawer-open">
|
||||
<input id="nav-menu" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content flex flex-col">
|
||||
<main class="main-content p-3">{props.children}</main>
|
||||
</div>
|
||||
<Navbar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import { useSiteContext } from '@context/SiteContext'
|
||||
import useLanguage from '@hooks/useLanguage'
|
||||
import { useNavigate } from '@solidjs/router'
|
||||
import { IconKey, IconUser } from '@tabler/icons-solidjs'
|
||||
import { useFormHandler } from 'solid-form-handler'
|
||||
import { yupSchema } from 'solid-form-handler/yup'
|
||||
import { onMount } from 'solid-js'
|
||||
import * as yup from 'yup'
|
||||
import './login.scss'
|
||||
|
||||
import Logo from '@assets/images/logo.svg'
|
||||
import TextInput from '@components/common/TextInput'
|
||||
import useAuth from '@hooks/useAuth'
|
||||
import useToast from '@hooks/useToast'
|
||||
|
||||
const loginSchema = yup.object({
|
||||
username: yup.string().required('Username is required'),
|
||||
password: yup.string().required('Password is required'),
|
||||
})
|
||||
|
||||
const language = useLanguage()
|
||||
|
||||
export default function Login() {
|
||||
const { store, setAuth } = useSiteContext()
|
||||
const navigate = useNavigate()
|
||||
const { clickLogIn } = useAuth(setAuth)
|
||||
const notify = useToast()
|
||||
const formHandler = useFormHandler(yupSchema(loginSchema))
|
||||
const { formData } = formHandler
|
||||
|
||||
onMount(() => {
|
||||
if (store.auth) {
|
||||
navigate('/', { replace: true })
|
||||
}
|
||||
})
|
||||
|
||||
const submit = async (event) => {
|
||||
event.preventDefault()
|
||||
await formHandler.validateForm()
|
||||
try {
|
||||
const { username, password } = formData()
|
||||
await clickLogIn(username, password, formHandler.resetForm)
|
||||
notify.success({
|
||||
title: 'Login success!',
|
||||
description: 'Welcome back!',
|
||||
closable: true,
|
||||
})
|
||||
} catch (error) {
|
||||
notify.error({
|
||||
title: 'Login fail!',
|
||||
description: error?.data
|
||||
? language.message[error.data]
|
||||
: 'Your username or password input is wrong!',
|
||||
closable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="login-page">
|
||||
<div class="card glass card-compact login-wrap shadow-xl">
|
||||
<div class="h-44">
|
||||
<picture class="logo">
|
||||
<source srcSet={Logo} type="image/png" media="(min-width: 600px)" />
|
||||
<img src={Logo} alt="logo" />
|
||||
</picture>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{language.ui.login}</h1>
|
||||
<form autoComplete="off" onSubmit={submit}>
|
||||
<TextInput
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
icon={IconUser}
|
||||
formHandler={formHandler}
|
||||
/>
|
||||
<TextInput
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
icon={IconKey}
|
||||
formHandler={formHandler}
|
||||
/>
|
||||
<div class="card-actions justify-end mt-5">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{language.ui.login}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './Login'
|
||||
@@ -1,69 +0,0 @@
|
||||
.login-page {
|
||||
width: 100%;
|
||||
height: 100svh;
|
||||
display: flex;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
background: #fff url('/images/bg-login.jpg') no-repeat fixed center;
|
||||
background-size: cover;
|
||||
place-items: center;
|
||||
|
||||
.login-wrap {
|
||||
width: 40%;
|
||||
max-width: 500px;
|
||||
min-width: 320px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
border-radius: 15px;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: -120px;
|
||||
left: -285px;
|
||||
background: #10b981;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
border-radius: 15px;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: -40px;
|
||||
left: -130px;
|
||||
background: #ff6600;
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
display: block;
|
||||
width: 40%;
|
||||
max-width: 150px;
|
||||
min-width: 100px;
|
||||
margin: 0 auto;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 5px;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { A } from '@solidjs/router'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div class="flex items-center justify-center min-h-screen bg-fu-green bg-fixed bg-cover bg-bottom error-bg">
|
||||
<div class="flex flex-col items-center text-white">
|
||||
<div class="relative text-center">
|
||||
<h1 class="relative text-9xl tracking-tighter-less text-shadow font-sans font-bold">
|
||||
<span>4</span>
|
||||
<span>0</span>
|
||||
<span>4</span>
|
||||
</h1>
|
||||
<span class="absolute top-0 -ml-12 font-semibold">Oops!</span>
|
||||
</div>
|
||||
<h5 class="font-semibold">Page not found</h5>
|
||||
<p class="mt-2 mb-6">
|
||||
we are sorry, but the page you requested was not found
|
||||
</p>
|
||||
<A href="/" class="btn btn-secondary btn-sm hover:text-white">
|
||||
Got to Home
|
||||
</A>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './NotFound'
|
||||
@@ -1,115 +0,0 @@
|
||||
import { putUpdateProfile } from '@api/user'
|
||||
import TextInput from '@components/common/TextInput'
|
||||
import { useSiteContext } from '@context/SiteContext'
|
||||
import useLanguage from '@hooks/useLanguage'
|
||||
import useToast from '@hooks/useToast'
|
||||
import { IconKey, IconUser, IconUserCircle } from '@tabler/icons-solidjs'
|
||||
import { Helpers } from '@utils/helper'
|
||||
import { useFormHandler } from 'solid-form-handler'
|
||||
import { yupSchema } from 'solid-form-handler/yup'
|
||||
import { createEffect } from 'solid-js'
|
||||
import * as yup from 'yup'
|
||||
|
||||
const profileSchema = yup.object({
|
||||
name: yup.string().required('Name is required'),
|
||||
password: yup.string().nullable().optional(),
|
||||
'confirm-password': yup.string().when('password', {
|
||||
is: (val) => !!(val && val.length > 0),
|
||||
then: (schema) =>
|
||||
schema.oneOf([yup.ref('password'), null], 'Passwords must match'),
|
||||
}),
|
||||
})
|
||||
|
||||
const language = useLanguage()
|
||||
const notify = useToast()
|
||||
|
||||
export default function Profile() {
|
||||
const {
|
||||
store: { userInfo },
|
||||
setUser,
|
||||
} = useSiteContext()
|
||||
const formHandler = useFormHandler(yupSchema(profileSchema))
|
||||
const { formData } = formHandler
|
||||
|
||||
createEffect(() => {
|
||||
formHandler.fillForm({
|
||||
name: userInfo?.name,
|
||||
})
|
||||
})
|
||||
|
||||
const submit = async (event) => {
|
||||
event.preventDefault()
|
||||
await formHandler.validateForm()
|
||||
try {
|
||||
const { name, password } = formData()
|
||||
const clearObj = Helpers.clearObject({
|
||||
name: name || null,
|
||||
password: password || null,
|
||||
})
|
||||
const resp = await putUpdateProfile(clearObj)
|
||||
|
||||
if (resp.status === 200) {
|
||||
setUser(resp.data)
|
||||
formHandler.setFieldValue('password', '')
|
||||
formHandler.setFieldValue('confirm-password', '')
|
||||
notify.success({
|
||||
title: 'Update profile success!',
|
||||
description: 'Your profile has been updated!',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
notify.error({
|
||||
title: 'Update profile fail!',
|
||||
description: error?.data
|
||||
? language.message[error.data]
|
||||
: 'Your username or password input is wrong!',
|
||||
closable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="profile">
|
||||
<div class="divider divider-accent text-xl">
|
||||
<span class="text-secondary">
|
||||
<IconUserCircle size={30} />
|
||||
</span>
|
||||
{language.ui.profile}
|
||||
</div>
|
||||
<div class="card w-full bg-base-100 shadow-lg border border-gray-200">
|
||||
<div class="card-body">
|
||||
<form autoComplete="off" onSubmit={submit}>
|
||||
<p class="card-title">{language.ui.changeInfo}</p>
|
||||
<div class="form-content py-5">
|
||||
<TextInput
|
||||
icon={IconUser}
|
||||
name="name"
|
||||
placeholder={language.ui.displayName}
|
||||
formHandler={formHandler}
|
||||
/>
|
||||
<TextInput
|
||||
icon={IconKey}
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder={language.ui.newPassword}
|
||||
formHandler={formHandler}
|
||||
/>
|
||||
<TextInput
|
||||
icon={IconKey}
|
||||
name="confirm-password"
|
||||
type="password"
|
||||
placeholder={language.ui.confirmNewPassword}
|
||||
formHandler={formHandler}
|
||||
/>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{language.ui.save}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './Profile'
|
||||
@@ -1,54 +0,0 @@
|
||||
import { IconPackage } from '@tabler/icons-solidjs'
|
||||
import { For } from 'solid-js'
|
||||
import { Dynamic } from 'solid-js/web'
|
||||
import './warehouse.scss'
|
||||
|
||||
function PackageItem(props) {
|
||||
const { item } = { ...props }
|
||||
const isEmpty = item % 2 === 0
|
||||
return (
|
||||
<a href="#" class={`group/item w-10 h-10 m-[5px] hover:text-white bg-fu-warning${isEmpty ? '/30' : ''} hover:bg-fu-green rounded-[10px] block`}>
|
||||
<div class="bx-dec invisible group-hover/item:visible">
|
||||
<div class="section-dec flex items-center absolute bg-fu-green/90 rounded-[10px] p-[10px] shadow-[0_5px_15px_4px_rgba(0, 135, 90, 0.25)]] mt-[20px] ml-[20px]">
|
||||
<div class="box-img">
|
||||
<Dynamic class="mr-[10px] w-20 h-20 text-fu-warning" component={IconPackage} />
|
||||
</div>
|
||||
<div class="dec max-w-[200px]">
|
||||
<h4 class="text-white mb-[5px] text-base font-bold">{isEmpty ? 'Rỗng' : 'Dụng cụ bếp'}</h4>
|
||||
<p class="line-clamp-3">
|
||||
{isEmpty
|
||||
? ''
|
||||
: `Nồi cơm điện, Bếp nấu ăn, Lò vi sóng, Máy xay sinh tố, Máy ép trái cây, Lò nướng, Nồi áp suất, Chảo chống dính, Xửng hấp, Bộ nồi chuyên dụng, Dao bếp, Thớt, Hộp đựng thực phẩm, Hũ
|
||||
đựng gia vị , Dụng cụ đo lường, Bộ chén bát, Đũa, Muỗng, Thau, rổ, Khăn lau bếp, Thùng đựng gạo, Bộ ly cốc, Dung dịch tẩy rửa, Giá treo, Vòi rửa thông minh, Máy hút mùi, Máy lọc nước`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
export default function WareHouse() {
|
||||
return (
|
||||
<div class="warehouse">
|
||||
<div class="card w-full bg-base-100 shadow-lg border border-gray-200">
|
||||
<div class="card-body">
|
||||
<div class="box-header pb-5 mb-5 flex items-center justify-between border-b border-gray-200">
|
||||
<h4 class="card-title">Section Overview</h4>
|
||||
<div class="box-controls pull-right">
|
||||
<input class="form-control no-border bg-base-200 px-2 py-1" id="e" type="date" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="area-in-warehouse grid grid-cols-4">
|
||||
<div class="area-block mb-10">
|
||||
<div class="area-title text-center text-base font-bold mb-3">Khu bếp</div>
|
||||
<div class="area-content flex justify-space-around flex-wrap">
|
||||
<For each={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]}>{(item) => <PackageItem item={item} />}</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './WareHouse'
|
||||
@@ -1,3 +0,0 @@
|
||||
.warehouse {
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './routes'
|
||||
@@ -1,28 +0,0 @@
|
||||
import { lazy } from 'solid-js'
|
||||
|
||||
export const ROUTES = [
|
||||
{
|
||||
path: '/',
|
||||
components: lazy(() => import('@pages/Home')),
|
||||
filter: {},
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
path: '/me',
|
||||
components: lazy(() => import('@pages/Profile')),
|
||||
filter: {},
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
components: lazy(() => import('@pages/Dashboard')),
|
||||
filter: {},
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
path: '/warehouse',
|
||||
components: lazy(() => import('@pages/WareHouse')),
|
||||
filter: {},
|
||||
show: true,
|
||||
},
|
||||
]
|
||||
@@ -1,14 +0,0 @@
|
||||
import { STORE_KEY } from './enum'
|
||||
import { Helpers } from './helper'
|
||||
|
||||
export default class UserHelper {
|
||||
#user
|
||||
|
||||
constructor() {
|
||||
this.#user = Helpers.decrypt(localStorage.getItem(STORE_KEY))
|
||||
}
|
||||
|
||||
get isAdmin() {
|
||||
return this.#user?.userInfo?.isAdmin
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// const PRODUCTION = import.meta.env.NODE_ENV === 'production'
|
||||
|
||||
export const SECRET_KEY = 'bGV0IGRvIGl0IGZvciBlbmNyeXRo'
|
||||
export const STORE_KEY = 'dXNlciBsb2dpbiBpbmZv'
|
||||
export const LOGIN_KEY = '7fo24CMyIc'
|
||||
@@ -1,59 +0,0 @@
|
||||
import { AES, enc } from 'crypto-js'
|
||||
import { LOGIN_KEY, SECRET_KEY, STORE_KEY } from './enum'
|
||||
|
||||
export class Helpers {
|
||||
static setCookie = (cname, cvalue, exdays) => {
|
||||
const d = new Date()
|
||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000)
|
||||
let expires = `expires=${d.toUTCString()}`
|
||||
document.cookie = `${cname}=${cvalue};${expires};path=/`
|
||||
}
|
||||
|
||||
static getCookie = (cname) => {
|
||||
let name = cname + '='
|
||||
let ca = document.cookie.split(';')
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i]
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1)
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length)
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
static deleteCookie = (cname) => {
|
||||
document.cookie = `${cname}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`
|
||||
}
|
||||
|
||||
static clearStorage = () => {
|
||||
localStorage.removeItem(LOGIN_KEY)
|
||||
localStorage.removeItem(STORE_KEY)
|
||||
}
|
||||
|
||||
static checkTokenExpired = (exp) => {
|
||||
const currentTime = Math.floor(new Date().getTime() / 1000)
|
||||
return exp < currentTime
|
||||
}
|
||||
|
||||
static encrypt = (obj) => {
|
||||
return AES.encrypt(JSON.stringify(obj), SECRET_KEY).toString()
|
||||
}
|
||||
|
||||
static decrypt = (hash, defaultValue = {}) => {
|
||||
return hash
|
||||
? JSON.parse(AES.decrypt(hash, SECRET_KEY).toString(enc.Utf8))
|
||||
: defaultValue
|
||||
}
|
||||
|
||||
static clearObject = (object) => {
|
||||
for (var propName in object) {
|
||||
if (object[propName] === null || object[propName] === undefined) {
|
||||
delete object[propName]
|
||||
}
|
||||
}
|
||||
return object
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import daisyui from 'daisyui'
|
||||
import themes from 'daisyui/src/theming/themes'
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./src/**/*.{js,jsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
fu: {
|
||||
white: '#fff',
|
||||
black: '#212121',
|
||||
primary: '#03c9d7',
|
||||
green: '#05b187',
|
||||
orange: '#fb9678',
|
||||
warning: '#fbc66c',
|
||||
yellow: '#fec90f',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [daisyui],
|
||||
daisyui: {
|
||||
themes: [
|
||||
{
|
||||
light: {
|
||||
...themes['light'],
|
||||
// primary: '#03c9d7',
|
||||
// 'primary-content': '#ffffff',
|
||||
secondary: '#fb9678',
|
||||
'secondary-content': '#ffffff',
|
||||
neutral: '#03c9d7',
|
||||
'neutral-content': '#ffffff',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import path, { dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
// import mkcert from 'vite-plugin-mkcert'
|
||||
import solid from 'vite-plugin-solid'
|
||||
|
||||
const _dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
// production
|
||||
export default defineConfig(({ mode }) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
|
||||
if (env.NODE_ENV === 'production') {
|
||||
return {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(_dirname, './src'),
|
||||
'@lang': path.resolve(_dirname, './src/lang'),
|
||||
'@api': path.resolve(_dirname, './src/api'),
|
||||
'@hooks': path.resolve(_dirname, './src/hooks'),
|
||||
'@pages': path.resolve(_dirname, './src/pages'),
|
||||
'@components': path.resolve(_dirname, './src/components'),
|
||||
'@routes': path.resolve(_dirname, './src/routes'),
|
||||
'@utils': path.resolve(_dirname, './src/utils'),
|
||||
'@assets': path.resolve(_dirname, './src/assets'),
|
||||
'@context': path.resolve(_dirname, './src/context'),
|
||||
},
|
||||
},
|
||||
plugins: [solid()],
|
||||
server: {
|
||||
https: false,
|
||||
host: true,
|
||||
port: 5001,
|
||||
strictPort: true,
|
||||
watch: {
|
||||
usePolling: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(_dirname, './src'),
|
||||
'@lang': path.resolve(_dirname, './src/lang'),
|
||||
'@api': path.resolve(_dirname, './src/api'),
|
||||
'@hooks': path.resolve(_dirname, './src/hooks'),
|
||||
'@pages': path.resolve(_dirname, './src/pages'),
|
||||
'@components': path.resolve(_dirname, './src/components'),
|
||||
'@routes': path.resolve(_dirname, './src/routes'),
|
||||
'@utils': path.resolve(_dirname, './src/utils'),
|
||||
'@assets': path.resolve(_dirname, './src/assets'),
|
||||
'@context': path.resolve(_dirname, './src/context'),
|
||||
},
|
||||
},
|
||||
// plugins: [solid(), mkcert()],
|
||||
plugins: [solid()],
|
||||
server: {
|
||||
https: false,
|
||||
host: true,
|
||||
port: 5001,
|
||||
strictPort: true,
|
||||
watch: {
|
||||
usePolling: true,
|
||||
},
|
||||
proxy: {
|
||||
'/api': 'http://localhost:9000',
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -3,7 +3,6 @@ name = "backend"
|
||||
version = "0.1.0"
|
||||
description = "project for manage item with exp date"
|
||||
authors = ["Sam Liu <luu.dat.tham@gmail.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
|
||||
Reference in New Issue
Block a user