13 Commits
Author SHA1 Message Date
Alena 6fb11a70f2 css dasboard 1.0 2026-11-01 21:13:33 +03:00
MH.Dmitrii acd669a942 css adjustments 2026-11-01 18:57:38 +03:00
MH.Dmitrii e99d21c6d3 standart env 2026-10-06 16:44:06 +03:00
MH.Dmitrii b80fe40852 dev, prod, local environment fixes 2026-10-06 11:21:09 +03:00
MH.Dmitrii 4a7ea33185 makefile add frontend 2026-10-05 09:41:59 +03:00
MH.Dmitrii 7ddc7157b5 permission context 2026-10-05 09:41:31 +03:00
MH.Dmitrii c2fd16e2d8 dashboard 2026-10-04 18:50:34 +03:00
MH.Dmitrii 64234f8639 fix odd if statement 2026-10-02 17:25:12 +03:00
MH.Dmitrii e99aafa36a rememberme, dashboard, logout features 2026-10-02 17:17:54 +03:00
MH.Dmitrii af151f254b fix layout for dashboard, add dashboard, save rememberme state in localstorage 2026-10-01 10:45:51 +03:00
MH.Dmitrii 1fbec06c23 revert the front 2026-09-30 16:18:36 +03:00
MH.Dmitrii bcfd4acfad save tokens localstorage, fix refresh sql query, added permissions 2026-09-30 14:33:20 +03:00
MH.Dmitrii 6a6a95e863 tests for temp_password 2026-09-28 09:56:59 +03:00
65 changed files with 155583 additions and 128 deletions
+6 -1
View File
@@ -15,4 +15,9 @@ __pycache__/
# OS мусор
.DS_Store
Thumbs.db
Thumbs.db
# frontend
frontend/node_modules
frontend/dist
frontend/configs/.env
+7 -1
View File
@@ -25,5 +25,11 @@ SMTP_SERVER="change_me"
EMAIL_LOGIN="change_me"
EMAIL_PASSWORD="change_me"
FRONT_URL="change_me"
FRONT_HOST="change_me"
FRONT_PORT=change_me #int
VITE_BACKEND_HOST="change_me"
VITE_BACKEND_PORT="change_me"
PROD_MODE=bool
+60 -2
View File
@@ -16,6 +16,8 @@ services:
- DB_HOST=psql #rewrite DB_HOST var to communicate inside the docker network
- RABBITMQ_HOST=rabbitmq
- REDIS_HOST=redis
- FRONT_HOST=${FRONT_HOST}
- FRONT_PORT=${FRONT_PORT}
volumes:
- type: bind
source: ../src
@@ -48,7 +50,7 @@ services:
rabbitmq:
condition: service_healthy
ports:
- "80:8000"
- "${VITE_BACKEND_PORT}:8000"
entrypoint: ["./entrypoint.sh", "--dev"]
backend-prod:
@@ -65,6 +67,8 @@ services:
- DB_HOST=psql
- RABBITMQ_HOST=rabbitmq
- REDIS_HOST=redis
- FRONT_HOST=${FRONT_HOST}
- FRONT_PORT=${FRONT_PORT}
volumes:
- type: bind
source: ../configs
@@ -89,7 +93,7 @@ services:
rabbitmq:
condition: service_healthy
ports:
- "80:8000"
- "${VITE_BACKEND_PORT}:8000"
daemons:
profiles: ["prod", "dev"]
@@ -185,6 +189,60 @@ services:
networks:
- backend
nginx-dev:
image: "${DOCKER_REGISTRY:-local}/excel-frontend-dev:${IMAGE_TAG:-local}"
container_name: frontend-dev
profiles: ["dev"]
build:
dockerfile: ./docker/dockerfile
context: ../
target: frontend_dev
init: true
ipc: private
volumes:
- type: bind
source: ../frontend/src
target: /home/excel-project/src
- type: bind
source: ../frontend/public
target: /home/excel-project/public
- type: bind
source: ../frontend/index.html
target: /home/excel-project/index.html
- type: bind
source: ../frontend/vite.config.ts
target: /home/excel-project/vite.config.ts
- type: bind
source: ../configs
target: /home/configs
ports:
- "${FRONT_PORT}:5173"
networks:
- backend
restart: always
entrypoint: ["yarn", "dev", "--host", "0.0.0.0"]
nginx-prod:
image: "${DOCKER_REGISTRY:-local}/excel-frontend-prod:${IMAGE_TAG:-local}"
container_name: frontend-prod
profiles: ["prod"]
build:
dockerfile: ./docker/dockerfile
context: ../
target: frontend_prod
environment:
- BACKEND_HOST=backend-prod
- BACKEND_PORT=${VITE_BACKEND_PORT}
init: true
ipc: private
ports:
- "${FRONT_PORT}:80"
networks:
- backend
restart: always
networks:
backend:
name: "${BACKEND_NETWORK:-backend_network}"
+54
View File
@@ -37,6 +37,7 @@ RUN pip install --no-cache-dir poetry \
&& poetry install --no-root --no-interaction --only main,web \
&& pip uninstall -y poetry poetry-core poetry-plugin-export
# --- Stage 2: Python Backend prod ---
FROM python:3.14-slim AS prod
@@ -64,6 +65,7 @@ USER appuser
ENTRYPOINT ["./entrypoint.sh", "--prod"]
# --- Stage 1: Python daemon builder ---
FROM python:3.14-slim AS worker-builder
@@ -85,6 +87,7 @@ RUN pip install --no-cache-dir poetry \
&& poetry install --no-root --no-interaction --only main,daemon \
&& pip uninstall -y poetry poetry-core poetry-plugin-export
# --- Stage 2: Python daemons ---
FROM python:3.14-slim AS daemon
@@ -109,3 +112,54 @@ RUN groupadd --gid 1000 appuser \
USER appuser
ENTRYPOINT ["python","-u", "daemon_run.py", "--all"]
# --- Stage 1: Node Frontend builder ---
FROM node:24-alpine AS frontend-builder
LABEL org.opencontainers.image.title="The-DisExcel-project-prod"
LABEL org.opencontainers.image.source="https://git.homyk.space/MH.Dmitrii/The_DisExcel_project"
WORKDIR /home/excel-project
COPY frontend/package.json frontend/yarn.lock ./
RUN yarn install --frozen-lockfile
COPY frontend/ ./
RUN yarn build
# --- Stage 2: Node Frontend prod ---
FROM nginx:alpine AS frontend_prod
LABEL org.opencontainers.image.title="The-DisExcel-project-prod"
LABEL org.opencontainers.image.source="https://git.homyk.space/MH.Dmitrii/The_DisExcel_project"
COPY --from=frontend-builder /home/excel-project/dist /usr/share/nginx/html
COPY docker/nginx/nginx.conf /etc/nginx/templates/default.conf.template
# --- Stage 1: Node Frontend dev ---
FROM node:24-alpine AS frontend_dev
LABEL org.opencontainers.image.title="The-DisExcel-project-prod"
LABEL org.opencontainers.image.source="https://git.homyk.space/MH.Dmitrii/The_DisExcel_project"
WORKDIR /home/excel-project
COPY frontend/package.json frontend/yarn.lock ./
RUN yarn install --frozen-lockfile
+15
View File
@@ -0,0 +1,15 @@
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri /index.html;
}
location /protected/ {
proxy_pass http://${BACKEND_HOST}:${BACKEND_PORT};
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
-1
View File
@@ -1 +0,0 @@
VITE_BACKEND_URL="change_me"
-4
View File
@@ -1,7 +1,3 @@
#root {
height: 100%;
box-shadow: 1px 1px 20px 1px rgba(68, 68, 68, 0.5);
border-radius: 40px;
border: 1px solid #444;
overflow: hidden;
}
+6
View File
@@ -2,12 +2,18 @@ import './App.css'
import { Route, Routes } from "react-router-dom";
import { LoginPage } from "./pages/LoginPage";
import { ForgotPasswordPage } from "./pages/ForgotPasswordPage";
import { DashboardPage } from './pages/Dashboard';
import { PermissionProvider } from './contexts/PermissionProvider';
function App() {
return (
<Routes>
<Route path="/" element={<LoginPage />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
<Route path="/dashboard" element={
<PermissionProvider>
<DashboardPage />
</PermissionProvider>} />
</Routes>
);
}
+3 -1
View File
@@ -7,7 +7,9 @@ export async function login(email: string, password: string) {
body,
credentials: "include",
});
if (!res.ok) throw new Error("Неверный email или пароль");
if (!res.ok){
throw new Error("Неверный email или пароль");
};
const data = await res.json();
setAccessToken(data.access_token);
return data;
+13
View File
@@ -0,0 +1,13 @@
import { API_URL, setAccessToken } from "../token";
export async function refreshToken(): Promise<void> {
const response = await fetch(`${API_URL}/protected/refresh`, {
method: "POST",
credentials: "include",
});
if (!response.ok) {
throw new Error("Session update wasn't successful");
};
const data = await response.json();
setAccessToken(data.access_token);
}
+3 -1
View File
@@ -5,7 +5,9 @@ export async function requestPasswordReset(email: string): Promise<void> {
credentials: "include",
});
if (!res.ok) {
const body = await res.json().catch(() => null);
const body = await res.json().catch(function(){
return null
});
throw new Error(typeof body?.detail === "string" ? body.detail : "Не удалось отправить запрос");
}
}
+13
View File
@@ -0,0 +1,13 @@
import type { UserOut } from "../../types/UserTypes";
import { authFetch } from "../token";
export async function fetchUser(): Promise<UserOut> {
const response = await authFetch("/protected/me");
if (!response.ok) {
throw new Error("Not authenticated");
}
const data = await response.json()
console.log(data)
return data;
}
+44 -4
View File
@@ -1,17 +1,57 @@
export const API_URL = import.meta.env.VITE_BACKEND_URL;
import { refreshToken } from "./RefreshAPI/Refresh";
const API_HOST = import.meta.env.VITE_BACKEND_HOST ?? "";
const API_PORT = import.meta.env.VITE_BACKEND_PORT ?? "";
export const API_URL = API_HOST ? `${API_HOST}:${API_PORT}` : "";
let accessToken: string | null = null;
let refreshTimer: ReturnType<typeof setInterval> | null =null;
export function setAccessToken(token: string | null) {
export function setAccessToken(token: string | null):void{
accessToken = token;
}
export function getAccessToken() {
export function getAccessToken():string|null {
return accessToken;
}
export async function authFetch(path: string, init: RequestInit = {}) {
const headers = new Headers(init.headers);
if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
if (accessToken) {
headers.set("Authorization", `Bearer ${accessToken}`);
};
return fetch(`${API_URL}${path}`, { ...init, headers, credentials: "include" });
}
export function startRefreshTimer(intervalms:number) {
stopRefreshTimer();
refreshTimer = setInterval(function () {
refreshToken().catch(function() {
});
}, intervalms);
}
export function stopRefreshTimer() {
if (refreshTimer) {
clearInterval(refreshTimer);
}
refreshTimer = null;
}
export async function logout() {
try {
await authFetch(`/protected/logout`);
} catch (err) {
throw new Error("Something went wrong", { cause: err });
}
setAccessToken(null);
stopRefreshTimer();
localStorage.removeItem("rememberMe");
sessionStorage.removeItem("rememberMe");
}
@@ -0,0 +1,8 @@
import { createContext } from "react";
export type PermissionsState = {
permissions: Set<string>;
loading: boolean;
};
export const PermissionContext = createContext<PermissionsState | null>(null);
@@ -0,0 +1,31 @@
import { useEffect, useState, type ReactNode } from "react";
import { useNavigate } from "react-router-dom";
import { loadPermissions } from "../utils/permissions";
import { PermissionContext } from "./PermissionContext";
export function PermissionProvider({ children }: { children: ReactNode }) {
const navigate = useNavigate();
const [loading, setLoading] = useState(true);
const [permissions, setPermissions] = useState<Set<string>>(new Set());
useEffect(function () {
async function load() {
try {
const response = await loadPermissions();
setPermissions(response);
setLoading(false);
} catch {
navigate("/");
}
}
load();
}, [navigate]);
return (
<PermissionContext value={{ permissions, loading }}>
{children}
</PermissionContext>
);
}
@@ -1,7 +1,8 @@
import { useState, type SubmitEvent, type ReactElement } from "react";
import { Link } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import styles from "./AuthComponent.module.css";
import { login } from "../../../api/LoginAPI/Auth";
import { persistRememberMe } from "../../../utils/rememberMe";
export function Auth():ReactElement {
const [email, setEmail] = useState("");
@@ -9,29 +10,32 @@ export function Auth():ReactElement {
const [rememberMe, setRememberMe] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [errors, setErrors] = useState<{ email?: string; password?: string }>({});
const navigate = useNavigate();
const validateEmail = (value: string) => {
function validateEmail(value: string) {
if (!value) return "Введите email";
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
return undefined;
};
const validatePassword = (value: string) =>
value ? undefined : "Введите пароль";
}
function validatePassword(value: string) {
return value ? undefined : "Введите пароль";
}
const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
e.preventDefault();
const next = { email: validateEmail(email), password: validatePassword(password) };
setErrors(next);
if (next.email || next.password) return;
async function handleSubmit(e: SubmitEvent<HTMLFormElement>) {
e.preventDefault();
const next = { email: validateEmail(email), password: validatePassword(password) };
setErrors(next);
if (next.email || next.password) return;
try {
await login(email, password);
window.location.href = "http://localhost:8000/docs";
} catch (err) {
console.error(err);
setErrors({ password: "Неверный email или пароль" });
try {
await login(email, password);
persistRememberMe(rememberMe);
navigate("/dashboard");
} catch (err) {
console.error(err);
setErrors({ password: "Неверный email или пароль" });
}
}
};
return (
<>
@@ -8,12 +8,12 @@ export function Reset():ReactElement {
const [email, setEmail] = useState("");
const [errors, setErrors] = useState<{ email?: string;}>({});
const validateEmail = (value: string) => {
function validateEmail(value: string) {
if (!value) return "Введите email";
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
return undefined;
};
const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
async function handleSubmit(e: SubmitEvent<HTMLFormElement>) {
e.preventDefault();
const next = {
email: validateEmail(email),
@@ -0,0 +1,6 @@
.grid {
display: grid;
justify-content: center;
align-items: center;
width: 100%;
}
@@ -0,0 +1,13 @@
import type { ReactElement } from "react";
import styles from "./Grid.module.css"
export function Grid():ReactElement {
return (
<>
<div className={styles.grid}>
<p>Content is here</p>
</div>
</>
);
}
@@ -0,0 +1,55 @@
.profile {
display: grid;
justify-content: end;
align-items: center;
width: 100%;
height: 100%;
position: relative;
display: flex;
}
.profile_avatar {
display: flex;
align-items: center;
justify-content: center;
background-color: rgb(0, 66, 33);
border-radius: 50%;
width: 40px;
height: 40px;
margin-right: 15px;
}
.menu{
position: absolute;
top:100%;
right: 0;
background: white;
border: 1px solid #ddd;
padding: 8px;
list-style: none;
}
.menu button {
width: 100%;
text-align: left;
}
.profile_button {
display: flex;
align-items: center;
justify-content: center;
background-color: transparent;
}
.text_profile {
}
.profile_name {
text-align: start;
margin-bottom: 5px;
font-weight: 500;
}
.profile_post {
color: #444;
font-weight: 300;
}
.arrow_down {
width: 12px;
height: 12px;
margin: 20px;
}
@@ -0,0 +1,45 @@
import { useState, type ReactElement } from "react";
import styles from "./Header.module.css"
import { logout } from "../../../api/token";
import { useNavigate } from "react-router-dom";
export function Header():ReactElement {
const [open, setOpen] = useState(false);
const navigate = useNavigate();
function toggleMenu() {
setOpen(!open);
}
async function handleLogout() {
await logout();
navigate("/");
}
return (
<>
<div className={styles.profile}>
<button onClick={toggleMenu} className={styles.profile_button}>
<div className={styles.profile_avatar}>
ИИ
</div>
<div className={styles.text_profile}>
<div className={styles.profile_name}>
Иван Иванов
</div>
<div className={styles.profile_post}>
Администратор
</div>
</div>
<div className={styles.arrow_down}>
<svg fill="#444" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 30.727 30.727" xml:space="preserve" stroke="#444"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <path d="M29.994,10.183L15.363,24.812L0.733,10.184c-0.977-0.978-0.977-2.561,0-3.536c0.977-0.977,2.559-0.976,3.536,0 l11.095,11.093L26.461,6.647c0.977-0.976,2.559-0.976,3.535,0C30.971,7.624,30.971,9.206,29.994,10.183z"></path> </g> </g></svg>
</div>
</button>
{open && (<ul className={styles.menu}>
<li>Settings</li>
<li><button onClick={handleLogout}>logout</button></li>
</ul>
)}
</div>
</>
);
}
@@ -0,0 +1,5 @@
.right_sidebar {
display: grid;
align-content: center;
justify-content: center;
}
@@ -0,0 +1,13 @@
import type { ReactElement } from "react";
import styles from "./Right_sidebar.module.css"
export function Right_sidebar():ReactElement {
return (
<>
<div className={styles.right_sidebar}>
<p>right_sidebar</p>
</div>
</>
);
}
@@ -0,0 +1,36 @@
.wrapper{
display: grid;
grid-template-areas:
"logo logo"
"elements elements";
}
.sidebar_elements{
display: grid;
grid-area: elements;
align-content: start;
gap: 15px;
}
.logo {
grid-area: logo;
display: grid;
grid-template-columns: 30% 1fr;
padding: 20px 20px 20px 5px;
}
.l_img {
width: 100%;
height: 100%;
}
.logo_name {
display: flex;
flex-direction: column;
margin-left: 10px;
justify-content: space-between;
padding: 6% 0;
}
.logo_title {
font-weight: 900;
}
.logo_heading {
color: #444;
font-weight: 500;
}
@@ -0,0 +1,32 @@
import type { ReactElement } from "react";
import styles from "./Sidebar.module.css"
import l_img from "../../../assets/LOGO.png";
export function Sidebar():ReactElement {
return (
<>
<div className={styles.wrapper}>
{/* ссылку на главную */}
<div className={styles.logo}>
<img className={styles.l_img} src={l_img} alt="Logo" />
<p className={styles.logo_name}>
<div className={styles.logo_title}>
The DisExcel
</div>
<div className={styles.logo_heading}>
Project
</div>
</p>
</div>
{/* до сюда */}
<div className={styles.sidebar_elements}>
<button>Dashboard</button>
<button>Excel</button>
<button>Papers</button>
</div>
</div>
</>
);
}
+8
View File
@@ -0,0 +1,8 @@
import { useContext } from "react";
import { PermissionContext } from "../contexts/PermissionContext";
export function usePermissions() {
const ctx = useContext(PermissionContext);
if (!ctx) throw new Error("usePermissions must be used inside PermissionProvider");
return ctx;
}
+1 -3
View File
@@ -6,12 +6,10 @@
a:focus,a:active{outline: none;}
nav,footer,header,aside{display: block;}
html,body{
height:100%;width:100%;
padding: 30px;
font-family: 'Manrope', sans-serif;
user-select: none;
font-size:100%;line-height:1;-ms-text-size-adjust:100%;-moz-text-size-adjust:100%;-webkit-text-size-adjust:100%;}
input,button,textarea{font-family:inherit;}
input, button, textarea { font: inherit; }
input::-ms-clear{display: none;}
button{cursor: pointer;}
button::-moz-focus-inner{padding:0;border:0;}
@@ -1,10 +1,12 @@
.wrapper {
position: absolute;
inset: 30px;
display: flex;
flex-direction: row;
min-height: auto;
width: 100%;
overflow: hidden;
height: 100%;
box-shadow: 1px 1px 20px 1px rgba(68, 68, 68, 0.5);
border-radius: 40px;
border: 1px solid #444;
}
.left_side {
width: 60%;
@@ -1,28 +1,28 @@
import type { ReactNode } from "react";
import "./AuthLayout.css";
import logo from "../assets/LOGO.png";
import img_1 from "../assets/paper.png";
import img_2 from "../assets/PC.png";
import img_3 from "../assets/graphics.png";
import styles from "./AuthLayout.module.css";
import logo from "../../assets/LOGO.png";
import img_1 from "../../assets/paper.png";
import img_2 from "../../assets/PC.png";
import img_3 from "../../assets/graphics.png";
export function AuthLayout({ children }: { children: ReactNode }) {
return (
<div className="wrapper">
<div className="left_side">
<header className="header">
<div className="container">
<div className="header_top">
<img className="img_logo" src={logo} alt="LOGO" />
<div className="header_text">
<div className="header_title">
<div className={styles.wrapper}>
<div className={styles.left_side}>
<header className={styles.header}>
<div className={styles.container}>
<div className={styles.header_top}>
<img className={styles.img_logo} src={logo} alt="LOGO" />
<div className={styles.header_text}>
<div className={styles.header_title}>
The DisExcel
</div>
<div className="header_heading">
<div className={styles.header_heading}>
Project
</div>
</div>
</div>
<div className="header_bottom">
<div className={styles.header_bottom}>
<p>
Избавляем от ручной работы. <br />
Переводим процессы из бумаги в Excel, <br />
@@ -31,69 +31,69 @@ export function AuthLayout({ children }: { children: ReactNode }) {
</div>
</div>
</header>
<main className="main">
<div className="contain">
<div className="main_items">
<main className={styles.main}>
<div className={styles.contain}>
<div className={styles.main_items}>
{/* 1 */}
<div className="main_item">
<img className="main_item_img" src={img_1} alt="img" />
<div className="main_item_title">Бумага</div>
<div className="main_item_p">
<div className={styles.main_item}>
<img className={styles.main_item_img} src={img_1} alt="img" />
<div className={styles.main_item_title}>Бумага</div>
<div className={styles.main_item_p}>
Ручная работа, ошибки, потери
</div>
</div>
<div className="main_i"></div>
<div className={styles.main_i}></div>
{/* 2 */}
<div className="main_item">
<img className="main_item_img" src={img_2} alt="img" />
<div className="main_item_title">Excel</div>
<div className="main_item_p">
<div className={styles.main_item}>
<img className={styles.main_item_img} src={img_2} alt="img" />
<div className={styles.main_item_title}>Excel</div>
<div className={styles.main_item_p}>
Структура, формулы, автоматизация
</div>
</div>
<div className="main_i"></div>
<div className={styles.main_i}></div>
{/* 3 */}
<div className="main_item">
<img className="main_item_img" src={img_3} alt="img" />
<div className="main_item_title">1С / Отчеты</div>
<div className="main_item_p">
<div className={styles.main_item}>
<img className={styles.main_item_img} src={img_3} alt="img" />
<div className={styles.main_item_title}>1С / Отчеты</div>
<div className={styles.main_item_p}>
Готовые процессы, аналитика, контроль
</div>
</div>
</div>
</div>
</main>
<section className="section">
<div className="section_items">
<section className={styles.section}>
<div className={styles.section_items}>
{/* 1 */}
<div className="section_item">
<div className={styles.section_item}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<circle cx="24" cy="24" r="16" stroke="#087A55" strokeWidth="3"/>
<path d="M24 14v11l7 4" stroke="#087A55" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
Экономим <br /> время
</div>
<div className="section_line"></div>
<div className={styles.section_line}></div>
{/* 2 */}
<div className="section_item">
<div className={styles.section_item}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<path d="M24 6 38 11v11c0 9-5.8 15.2-14 20-8.2-4.8-14-11-14-20V11z" stroke="#087A55" strokeWidth="3" strokeLinejoin="round"/>
<path d="m17 24 5 5 10-11" stroke="#087A55" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
Снижаем <br /> ошибки
</div>
<div className="section_line"></div>
<div className={styles.section_line}></div>
{/* 3 */}
<div className="section_item">
<div className={styles.section_item}>
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path stroke="#087A55" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.5.5 0 0 0 .12-.63l-1.92-3.32a.5.5 0 0 0-.6-.22l-2.39.96a7.03 7.03 0 0 0-1.62-.94l-.36-2.54a.5.5 0 0 0-.5-.43h-3.84a.5.5 0 0 0-.5.43l-.36 2.54c-.59.24-1.13.56-1.62.94l-2.39-.96a.5.5 0 0 0-.6.22L2.66 8.85a.5.5 0 0 0 .12.63l2.03 1.58c-.05.3-.08.63-.08.94s.02.64.07.94l-2.03 1.58a.5.5 0 0 0-.12.63l1.92 3.32a.5.5 0 0 0 .6.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54a.5.5 0 0 0 .5.43h3.84a.5.5 0 0 0 .5-.43l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96a.5.5 0 0 0 .6-.22l1.92-3.32a.5.5 0 0 0-.12-.63l-2.03-1.58Z"/>
<circle cx="12" cy="12" r="3.5" stroke="#087A55" strokeWidth="1.6"/>
</svg>
Автоматизируем <br /> процессы
</div>
<div className="section_line"></div>
<div className={styles.section_line}></div>
{/* 4 */}
<div className="section_item">
<div className={styles.section_item}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<path d="M24 6 38 11v11c0 9-5.8 15.2-14 20-8.2-4.8-14-11-14-20V11z" stroke="#087A55" strokeWidth="3" strokeLinejoin="round"/>
<path d="m17 24 5 5 10-11" stroke="#087A55" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
@@ -0,0 +1,51 @@
* {
outline: 1px solid red;
}
.wrapper {
display: grid;
grid-template-columns: 20% 1fr 20%;
grid-template-rows: 8.5% 1fr;
min-height: 100vh;
background-color:#F7FAF9;
grid-template-areas:
"left_sidebar header header"
"left_sidebar content right_sidebar";
}
.left_sidebar{
display: grid;
grid-area: left_sidebar;
align-content: start;
gap:10px;
background-color:#F1FAF7;
border-right: 1px solid #E1E9E6 ;
}
.header {
display: grid;
grid-area: header;
background: #FFFFFF;
border-bottom: 1px solid #E1E9E6;
}
.content {
display: grid;
grid-area: content;
}
/* .wrapper_1 {
display: grid;
grid-template-rows: 5vh 1fr;
height: 100%;
}
.wrapper_2 {
display: grid;
grid-template-columns: 70% 1fr;
height: 100%;
} */
.right_sidebar{
display: grid;
grid-area: right_sidebar;
background: #FFFFFF;
border-left: 1px solid #E1E9E6;
}
+32
View File
@@ -0,0 +1,32 @@
import type { ReactNode } from "react";
import styles from "./Dashboard.module.css"
import { usePermissions } from "../../hooks/usePermissions";
export function MainLayout({sidebar, header, content, right_sidebar }: { sidebar: ReactNode;header: ReactNode; content: ReactNode; right_sidebar: ReactNode }) {
//const { permissions, loading } = usePermissions();
const { loading } = usePermissions();
if (loading) {
return null;
}
return (
<div className={styles.wrapper}>
<div className={styles.left_sidebar}>
{sidebar}
</div>
<div className={styles.header}>
{header}
{/* {permissions.has("admin") && <div><p>test</p></div>} */}
</div>
<div className={styles.content}>
{content}
</div>
<div className={styles.right_sidebar}>
{right_sidebar}
</div>
</div>
);
}
+10
View File
@@ -3,7 +3,17 @@ import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import './index.css'
import App from './App.tsx'
import { refreshToken } from './api/RefreshAPI/Refresh.ts'
import { getRememberMe } from './utils/rememberMe.ts'
import { startRefreshTimer } from './api/token.ts'
if (getRememberMe()) {
await refreshToken().catch(() => {});
console.log("Timer started");
startRefreshTimer(10*60*1000);
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<BrowserRouter>
+16
View File
@@ -0,0 +1,16 @@
import { MainLayout } from "../layouts/Main/Dashboard";
import { Sidebar} from "../features/dashboard/components/Sidebar";
import { Header } from "../features/dashboard/components/Header";
import { Grid } from "../features/dashboard/components/Grid";
import { Right_sidebar } from "../features/dashboard/components/Right_sidebar";
export function DashboardPage() {
return (
<MainLayout
header={<Header />}
content={<Grid />}
sidebar={<Sidebar />}
right_sidebar={<Right_sidebar />}
>
</MainLayout>
);
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { Reset } from "../features/auth/components/ResetComponent";
import { AuthLayout } from "../layouts/AuthLayout";
import { AuthLayout } from "../layouts/Auth/AuthLayout";
export function ForgotPasswordPage() {
return (
+30 -1
View File
@@ -1,7 +1,36 @@
import { useNavigate } from "react-router-dom";
import { Auth } from "../features/auth/components/AuthComponent";
import { AuthLayout } from "../layouts/AuthLayout";
import { AuthLayout } from "../layouts/Auth/AuthLayout";
import { useEffect, useState } from "react";
import { fetchUser } from "../api/UserAPI/User";
import { getAccessToken } from "../api/token";
export function LoginPage(){
const navigate = useNavigate();
const [checking, setChecking] = useState(function () {
return !!getAccessToken();
});
useEffect(function () {
if (!getAccessToken()) return;
async function check() {
try {
await fetchUser();
navigate("/dashboard");
} catch {
setChecking(false);
}
}
check();
}, [navigate]);
if (checking) {
return null;
}
return(
<AuthLayout>
<Auth />
+16
View File
@@ -0,0 +1,16 @@
import { fetchUser } from "../api/UserAPI/User";
export async function loadPermissions(): Promise<Set<string>> {
const user = await fetchUser();
const permissions = new Set<string>();
for (const p of user.direct_permissions) {
permissions.add(p.permission);
}
for (const g of user.group) {
for (const p of g.permissions) {
permissions.add(p.permission);
}
}
return permissions;
}
+21
View File
@@ -0,0 +1,21 @@
export function persistRememberMe(value:boolean):void {
if (value) {
localStorage.setItem("rememberMe", "True");
sessionStorage.removeItem("rememberMe");
} else{
sessionStorage.setItem("rememberMe", "True");
localStorage.removeItem("rememberMe");
}
};
export function getRememberMe():boolean {
if (localStorage.getItem("rememberMe") || sessionStorage.getItem("rememberMe")) {
return true;
}
else {
return false;
}
}
+1 -1
View File
@@ -4,5 +4,5 @@ import { defineConfig } from 'vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
envDir:"./configs",
envDir:"../configs",
})
+1 -1
View File
@@ -39,7 +39,7 @@ app.include_router(router=protected_router)
app.include_router(router=protected_user_action_routes)
app.add_middleware(
CORSMiddleware,
allow_origins=[env_settings.FRONT_URL],
allow_origins=[f"http://{env_settings.FRONT_HOST}:{env_settings.FRONT_PORT}"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
+10 -2
View File
@@ -1,4 +1,4 @@
VENV:=source .venv/bin/activate;
VENV:=source .venv/bin/activate &&
ALLURE:=.venv/allure-2.44.0/bin/allure #linux&macos
#ALLURE=.venv\allure-2.44.0\bin\allure #Windows
@@ -17,7 +17,7 @@ help:
##
.PHONY: run
run: ## Run dev local application
docker compose -f docker/compose-dev.yaml --env-file configs/.env --profile local up -d && ${VENV} uvicorn main:app --host 0.0.0.0 --port 8000 --reload
docker compose -f docker/compose-dev.yaml --env-file configs/.env --profile local up -d && $(MAKE) -j2 backend frontend
.PHONY: down
down: ## Down dev local db
@@ -39,6 +39,14 @@ down-dev: ## Down dev application
down-prod: ## Down prod application
docker compose -f docker/compose-dev.yaml --env-file configs/.env --profile prod down
.PHONY: backend
backend: ## Run backend only
${VENV} uvicorn main:app --host 0.0.0.0 --port 8000 --reload
.PHONY: frontend
frontend: ## Run frontend only
yarn --cwd ./frontend dev
##
## Migration section
##
Generated Vendored
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../yarn/bin/yarn.js" "$@"
ret=$?
else
node "$basedir/../yarn/bin/yarn.js" "$@"
ret=$?
fi
exit $ret
Generated Vendored
+7
View File
@@ -0,0 +1,7 @@
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\yarn\bin\yarn.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\yarn\bin\yarn.js" %*
)
Generated Vendored
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../yarn/bin/yarn.js" "$@"
ret=$?
else
node "$basedir/../yarn/bin/yarn.js" "$@"
ret=$?
fi
exit $ret
+7
View File
@@ -0,0 +1,7 @@
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\yarn\bin\yarn.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\yarn\bin\yarn.js" %*
)
+16
View File
@@ -0,0 +1,16 @@
{
"systemParams": "win32-x64-137",
"modulesFolders": [
"node_modules"
],
"flags": [],
"linkedModules": [],
"topLevelPatterns": [
"yarn@^1.22.22"
],
"lockfileEntries": {
"yarn@^1.22.22": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz"
},
"files": [],
"artifacts": {}
}
Generated Vendored
+26
View File
@@ -0,0 +1,26 @@
BSD 2-Clause License
For Yarn software
Copyright (c) 2016-present, Yarn Contributors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+60
View File
@@ -0,0 +1,60 @@
<p align="center">
<a href="https://yarnpkg.com/">
<img alt="Yarn" src="https://github.com/yarnpkg/assets/blob/master/yarn-kitten-full.png?raw=true" width="546">
</a>
</p>
<p align="center">
Fast, reliable, and secure dependency management.
</p>
<p align="center">
<a href="https://circleci.com/gh/yarnpkg/yarn"><img alt="Circle Status" src="https://circleci.com/gh/yarnpkg/yarn.svg?style=shield&circle-token=5f0a78473b0f440afb218bf2b82323cc6b3cb43f"></a>
<a href="https://ci.appveyor.com/project/kittens/yarn/branch/master"><img alt="Appveyor Status" src="https://ci.appveyor.com/api/projects/status/0xdv8chwe2kmk463?svg=true"></a>
<a href="https://dev.azure.com/yarnpkg/yarn/_build"><img alt="Azure Pipelines status" src="https://dev.azure.com/yarnpkg/yarn/_apis/build/status/Yarn%20Acceptance%20Tests"></a>
<a href="https://discord.gg/yarnpkg"><img alt="Discord Chat" src="https://img.shields.io/discord/226791405589233664.svg"></a>
<a href="http://commitizen.github.io/cz-cli/"><img alt="Commitizen friendly" src="https://img.shields.io/badge/commitizen-friendly-brightgreen.svg"></a>
</p>
---
**Fast:** Yarn caches every package it has downloaded, so it never needs to download the same package again. It also does almost everything concurrently to maximize resource utilization. This means even faster installs.
**Reliable:** Using a detailed but concise lockfile format and a deterministic algorithm for install operations, Yarn is able to guarantee that any installation that works on one system will work exactly the same on another system.
**Secure:** Yarn uses checksums to verify the integrity of every installed package before its code is executed.
## Features
* **Offline Mode.** If you've installed a package before, then you can install it again without an internet connection.
* **Deterministic.** The same dependencies will be installed in the same exact way on any machine, regardless of installation order.
* **Network Performance.** Yarn efficiently queues requests and avoids request waterfalls in order to maximize network utilization.
* **Network Resilience.** A single request that fails will not cause the entire installation to fail. Requests are automatically retried upon failure.
* **Flat Mode.** Yarn resolves mismatched versions of dependencies to a single version to avoid creating duplicates.
* **More emojis.** 🐈
## Installing Yarn
Read the [Installation Guide](https://yarnpkg.com/en/docs/install) on our website for detailed instructions on how to install Yarn.
## Using Yarn
Read the [Usage Guide](https://yarnpkg.com/en/docs/usage) on our website for detailed instructions on how to use Yarn.
## Contributing to Yarn
Contributions are always welcome, no matter how large or small. Substantial feature requests should be proposed as an [RFC](https://github.com/yarnpkg/rfcs). Before contributing, please read the [code of conduct](CODE_OF_CONDUCT.md).
See [Contributing](https://yarnpkg.com/org/contributing/).
## Prior art
Yarn wouldn't exist if it wasn't for excellent prior art. Yarn has been inspired by the following projects:
- [Bundler](https://github.com/bundler/bundler)
- [Cargo](https://github.com/rust-lang/cargo)
- [npm](https://github.com/npm/cli)
## Credits
Thanks to [Sam Holmes](https://github.com/samholmes) for donating the npm package name!
Generated Vendored
+35
View File
@@ -0,0 +1,35 @@
#!/bin/sh
argv0=$(echo "$0" | sed -e 's,\\,/,g')
basedir=$(dirname "$(readlink "$0" || echo "$argv0")")
case "$(uname -s)" in
Darwin) basedir="$( cd "$( dirname "$argv0" )" && pwd )";;
Linux) basedir=$(dirname "$(readlink -f "$0" || echo "$argv0")");;
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
command_exists() {
command -v "$1" >/dev/null 2>&1;
}
if command_exists node; then
if [ "$YARN_FORCE_WINPTY" = 1 ] || command_exists winpty && test -t 1; then
winpty node "$basedir/yarn.js" "$@"
else
exec node "$basedir/yarn.js" "$@"
fi
ret=$?
# Debian and Ubuntu use "nodejs" as the name of the binary, not "node", so we
# search for that too. See:
# https://lists.debian.org/debian-devel-announce/2012/07/msg00002.html
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=614907
elif command_exists nodejs; then
exec nodejs "$basedir/yarn.js" "$@"
ret=$?
else
>&2 echo 'Yarn requires Node.js 4.0 or higher to be installed.'
ret=1
fi
exit $ret
+2
View File
@@ -0,0 +1,2 @@
@echo off
node "%~dp0\yarn.js" %*
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env node
/* eslint-disable no-var */
/* eslint-disable flowtype/require-valid-file-annotation */
'use strict';
var ver = process.versions.node;
var majorVer = parseInt(ver.split('.')[0], 10);
if (majorVer < 4) {
console.error('Node version ' + ver + ' is not supported, please use Node.js 4.0 or higher.');
process.exit(1); // eslint-disable-line no-process-exit
} else {
try {
require(__dirname + '/../lib/v8-compile-cache.js');
} catch (err) {
// We don't have/need this on legacy builds and dev builds
}
// Just requiring this package will trigger a yarn run since the
// `require.main === module` check inside `cli/index.js` will always
// be truthy when built with webpack :(
// `lib/cli` may be `lib/cli/index.js` or `lib/cli.js` depending on the build.
var cli = require(__dirname + '/../lib/cli');
if (!cli.autoRun) {
cli.default().catch(function(error) {
console.error(error.stack || error.message || error);
process.exitCode = 1;
});
}
}
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('./yarn.js');
+2
View File
@@ -0,0 +1,2 @@
@echo off
"%~dp0\yarn.cmd" %*
+154071
View File
File diff suppressed because one or more lines are too long
+351
View File
@@ -0,0 +1,351 @@
'use strict';
const Module = require('module');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const os = require('os');
const hasOwnProperty = Object.prototype.hasOwnProperty;
//------------------------------------------------------------------------------
// FileSystemBlobStore
//------------------------------------------------------------------------------
class FileSystemBlobStore {
constructor(directory, prefix) {
const name = prefix ? slashEscape(prefix + '.') : '';
this._blobFilename = path.join(directory, name + 'BLOB');
this._mapFilename = path.join(directory, name + 'MAP');
this._lockFilename = path.join(directory, name + 'LOCK');
this._directory = directory;
this._load();
}
has(key, invalidationKey) {
if (hasOwnProperty.call(this._memoryBlobs, key)) {
return this._invalidationKeys[key] === invalidationKey;
} else if (hasOwnProperty.call(this._storedMap, key)) {
return this._storedMap[key][0] === invalidationKey;
}
return false;
}
get(key, invalidationKey) {
if (hasOwnProperty.call(this._memoryBlobs, key)) {
if (this._invalidationKeys[key] === invalidationKey) {
return this._memoryBlobs[key];
}
} else if (hasOwnProperty.call(this._storedMap, key)) {
const mapping = this._storedMap[key];
if (mapping[0] === invalidationKey) {
return this._storedBlob.slice(mapping[1], mapping[2]);
}
}
}
set(key, invalidationKey, buffer) {
this._invalidationKeys[key] = invalidationKey;
this._memoryBlobs[key] = buffer;
this._dirty = true;
}
delete(key) {
if (hasOwnProperty.call(this._memoryBlobs, key)) {
this._dirty = true;
delete this._memoryBlobs[key];
}
if (hasOwnProperty.call(this._invalidationKeys, key)) {
this._dirty = true;
delete this._invalidationKeys[key];
}
if (hasOwnProperty.call(this._storedMap, key)) {
this._dirty = true;
delete this._storedMap[key];
}
}
isDirty() {
return this._dirty;
}
save() {
const dump = this._getDump();
const blobToStore = Buffer.concat(dump[0]);
const mapToStore = JSON.stringify(dump[1]);
try {
mkdirpSync(this._directory);
fs.writeFileSync(this._lockFilename, 'LOCK', {flag: 'wx'});
} catch (error) {
// Swallow the exception if we fail to acquire the lock.
return false;
}
try {
fs.writeFileSync(this._blobFilename, blobToStore);
fs.writeFileSync(this._mapFilename, mapToStore);
} catch (error) {
throw error;
} finally {
fs.unlinkSync(this._lockFilename);
}
return true;
}
_load() {
try {
this._storedBlob = fs.readFileSync(this._blobFilename);
this._storedMap = JSON.parse(fs.readFileSync(this._mapFilename));
} catch (e) {
this._storedBlob = Buffer.alloc(0);
this._storedMap = {};
}
this._dirty = false;
this._memoryBlobs = {};
this._invalidationKeys = {};
}
_getDump() {
const buffers = [];
const newMap = {};
let offset = 0;
function push(key, invalidationKey, buffer) {
buffers.push(buffer);
newMap[key] = [invalidationKey, offset, offset + buffer.length];
offset += buffer.length;
}
for (const key of Object.keys(this._memoryBlobs)) {
const buffer = this._memoryBlobs[key];
const invalidationKey = this._invalidationKeys[key];
push(key, invalidationKey, buffer);
}
for (const key of Object.keys(this._storedMap)) {
if (hasOwnProperty.call(newMap, key)) continue;
const mapping = this._storedMap[key];
const buffer = this._storedBlob.slice(mapping[1], mapping[2]);
push(key, mapping[0], buffer);
}
return [buffers, newMap];
}
}
//------------------------------------------------------------------------------
// NativeCompileCache
//------------------------------------------------------------------------------
class NativeCompileCache {
constructor() {
this._cacheStore = null;
this._previousModuleCompile = null;
}
setCacheStore(cacheStore) {
this._cacheStore = cacheStore;
}
install() {
const self = this;
this._previousModuleCompile = Module.prototype._compile;
Module.prototype._compile = function(content, filename) {
const mod = this;
function require(id) {
return mod.require(id);
}
require.resolve = function(request) {
return Module._resolveFilename(request, mod);
};
require.main = process.mainModule;
// Enable support to add extra extension types
require.extensions = Module._extensions;
require.cache = Module._cache;
const dirname = path.dirname(filename);
const compiledWrapper = self._moduleCompile(filename, content);
// We skip the debugger setup because by the time we run, node has already
// done that itself.
const args = [mod.exports, require, mod, filename, dirname, process, global];
return compiledWrapper.apply(mod.exports, args);
};
}
uninstall() {
Module.prototype._compile = this._previousModuleCompile;
}
_moduleCompile(filename, content) {
// https://github.com/nodejs/node/blob/v7.5.0/lib/module.js#L511
// Remove shebang
var contLen = content.length;
if (contLen >= 2) {
if (content.charCodeAt(0) === 35/*#*/ &&
content.charCodeAt(1) === 33/*!*/) {
if (contLen === 2) {
// Exact match
content = '';
} else {
// Find end of shebang line and slice it off
var i = 2;
for (; i < contLen; ++i) {
var code = content.charCodeAt(i);
if (code === 10/*\n*/ || code === 13/*\r*/) break;
}
if (i === contLen) {
content = '';
} else {
// Note that this actually includes the newline character(s) in the
// new output. This duplicates the behavior of the regular
// expression that was previously used to replace the shebang line
content = content.slice(i);
}
}
}
}
// create wrapper function
var wrapper = Module.wrap(content);
var invalidationKey = crypto
.createHash('sha1')
.update(content, 'utf8')
.digest('hex');
var buffer = this._cacheStore.get(filename, invalidationKey);
var script = new vm.Script(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true,
cachedData: buffer,
produceCachedData: true,
});
if (script.cachedDataProduced) {
this._cacheStore.set(filename, invalidationKey, script.cachedData);
} else if (script.cachedDataRejected) {
this._cacheStore.delete(filename);
}
var compiledWrapper = script.runInThisContext({
filename: filename,
lineOffset: 0,
columnOffset: 0,
displayErrors: true,
});
return compiledWrapper;
}
}
//------------------------------------------------------------------------------
// utilities
//
// https://github.com/substack/node-mkdirp/blob/f2003bb/index.js#L55-L98
// https://github.com/zertosh/slash-escape/blob/e7ebb99/slash-escape.js
//------------------------------------------------------------------------------
function mkdirpSync(p_) {
_mkdirpSync(path.resolve(p_), parseInt('0777', 8) & ~process.umask());
}
function _mkdirpSync(p, mode) {
try {
fs.mkdirSync(p, mode);
} catch (err0) {
if (err0.code === 'ENOENT') {
_mkdirpSync(path.dirname(p));
_mkdirpSync(p);
} else {
try {
const stat = fs.statSync(p);
if (!stat.isDirectory()) { throw err0; }
} catch (err1) {
throw err0;
}
}
}
}
function slashEscape(str) {
const ESCAPE_LOOKUP = {
'\\': 'zB',
':': 'zC',
'/': 'zS',
'\x00': 'z0',
'z': 'zZ',
};
return str.replace(/[\\:\/\x00z]/g, match => (ESCAPE_LOOKUP[match]));
}
function supportsCachedData() {
const script = new vm.Script('""', {produceCachedData: true});
// chakracore, as of v1.7.1.0, returns `false`.
return script.cachedDataProduced === true;
}
function getCacheDir() {
// Avoid cache ownership issues on POSIX systems.
const dirname = typeof process.getuid === 'function'
? 'v8-compile-cache-' + process.getuid()
: 'v8-compile-cache';
const version = typeof process.versions.v8 === 'string'
? process.versions.v8
: typeof process.versions.chakracore === 'string'
? 'chakracore-' + process.versions.chakracore
: 'node-' + process.version;
const cacheDir = path.join(os.tmpdir(), dirname, version);
return cacheDir;
}
function getParentName() {
// `module.parent.filename` is undefined or null when:
// * node -e 'require("v8-compile-cache")'
// * node -r 'v8-compile-cache'
// * Or, requiring from the REPL.
const parentName = module.parent && typeof module.parent.filename === 'string'
? module.parent.filename
: process.cwd();
return parentName;
}
//------------------------------------------------------------------------------
// main
//------------------------------------------------------------------------------
if (!process.env.DISABLE_V8_COMPILE_CACHE && supportsCachedData()) {
const cacheDir = getCacheDir();
const prefix = getParentName();
const blobStore = new FileSystemBlobStore(cacheDir, prefix);
const nativeCompileCache = new NativeCompileCache();
nativeCompileCache.setCacheStore(blobStore);
nativeCompileCache.install();
process.once('exit', code => {
if (blobStore.isDirty()) {
blobStore.save();
}
nativeCompileCache.uninstall();
});
}
module.exports.__TEST__ = {
FileSystemBlobStore,
NativeCompileCache,
mkdirpSync,
slashEscape,
supportsCachedData,
getCacheDir,
getParentName,
};
+28
View File
@@ -0,0 +1,28 @@
{
"name": "yarn",
"installationMethod": "tar",
"version": "1.22.22",
"packageManager": "yarn@1.22.17",
"license": "BSD-2-Clause",
"preferGlobal": true,
"description": "📦🐈 Fast, reliable, and secure dependency management.",
"resolutions": {
"sshpk": "^1.14.2"
},
"engines": {
"node": ">=4.0.0"
},
"repository": "yarnpkg/yarn",
"bin": {
"yarn": "./bin/yarn.js",
"yarnpkg": "./bin/yarn.js"
},
"scripts": {
"preinstall": ":; (node ./preinstall.js > /dev/null 2>&1 || true)"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}
+60
View File
@@ -0,0 +1,60 @@
// This file is a bit weird, so let me explain with some context: we're working
// to implement a tool called "Corepack" in Node. This tool will allow us to
// provide a Yarn shim to everyone using Node, meaning that they won't need to
// run `npm install -g yarn`.
//
// Still, we don't want to break the experience of people that already use `npm
// install -g yarn`! And one annoying thing with npm is that they install their
// binaries directly inside the Node bin/ folder. And Because of this, they
// refuse to overwrite binaries when they detect they don't belong to npm. Which
// means that, since the "yarn" Corepack symlink belongs to Corepack and not npm,
// running `npm install -g yarn` would crash by refusing to override the binary :/
//
// And thus we have this preinstall script, which checks whether Yarn is being
// installed as a global binary, and remove the existing symlink if it detects
// it belongs to Corepack. Since preinstall scripts run, in npm, before the global
// symlink is created, we bypass this way the ownership check.
//
// More info:
// https://github.com/arcanis/pmm/issues/6
if (process.env.npm_config_global) {
var cp = require('child_process');
var fs = require('fs');
var path = require('path');
try {
var targetPath = cp.execFileSync(process.execPath, [process.env.npm_execpath, 'bin', '-g'], {
encoding: 'utf8',
stdio: ['ignore', undefined, 'ignore'],
}).replace(/\n/g, '');
var manifest = require('./package.json');
var binNames = typeof manifest.bin === 'string'
? [manifest.name.replace(/^@[^\/]+\//, '')]
: typeof manifest.bin === 'object' && manifest.bin !== null
? Object.keys(manifest.bin)
: [];
binNames.forEach(function (binName) {
var binPath = path.join(targetPath, binName);
var binTarget;
try {
binTarget = fs.readlinkSync(binPath);
} catch (err) {
return;
}
if (binTarget.startsWith('../lib/node_modules/corepack/')) {
try {
fs.unlinkSync(binPath);
} catch (err) {
return;
}
}
});
} catch (err) {
// ignore errors
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"name": "The_DisExcel_project",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"yarn": "^1.22.22"
}
},
"node_modules/yarn": {
"version": "1.22.22",
"resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz",
"integrity": "sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==",
"hasInstallScript": true,
"license": "BSD-2-Clause",
"bin": {
"yarn": "bin/yarn.js",
"yarnpkg": "bin/yarn.js"
},
"engines": {
"node": ">=4.0.0"
}
}
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"dependencies": {
"yarn": "^1.22.22"
}
}
Generated
+31 -31
View File
@@ -713,7 +713,7 @@ version = "0.9.63"
description = "AI coding assistant skill (Claude Code, CodeBuddy, Codex, OpenCode, Kilo Code, Cursor, Gemini CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, Pi, Devin CLI, Google Antigravity) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph"
optional = false
python-versions = ">=3.10"
groups = ["main"]
groups = ["claude"]
files = [
{file = "graphifyy-0.9.63-py3-none-any.whl", hash = "sha256:e56aff02d01021c3337b192af6e4d1d2d28b70a35bb4912f777264c3858712d3"},
{file = "graphifyy-0.9.63.tar.gz", hash = "sha256:0d2073903f6cc79a67eae27e1cb9284323da6b29555ecef5c2e35b7a4a1d2a5b"},
@@ -1429,7 +1429,7 @@ version = "3.6"
description = "Python package for creating and manipulating graphs and networks"
optional = false
python-versions = ">=3.11"
groups = ["main"]
groups = ["claude"]
files = [
{file = "networkx-3.6-py3-none-any.whl", hash = "sha256:cdb395b105806062473d3be36458d8f1459a4e4b98e236a66c3a48996e07684f"},
{file = "networkx-3.6.tar.gz", hash = "sha256:285276002ad1f7f7da0f7b42f004bcba70d381e936559166363707fdad3d72ad"},
@@ -1452,7 +1452,7 @@ version = "2.5.3"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.12"
groups = ["main"]
groups = ["claude"]
files = [
{file = "numpy-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9"},
{file = "numpy-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c"},
@@ -2251,7 +2251,7 @@ version = "3.14.6"
description = "rapid fuzzy string matching"
optional = false
python-versions = ">=3.11"
groups = ["main"]
groups = ["claude"]
files = [
{file = "rapidfuzz-3.14.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c0dd0d765184366b6e213a8af3b0b3bb39dad27943bbfb193515d4ff96ac82a"},
{file = "rapidfuzz-3.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0c61cade182f130c9903231946bd1074539121721693a918e7b70382ae802bd8"},
@@ -2667,7 +2667,7 @@ version = "0.25.2"
description = "Python bindings to the Tree-sitter parsing library"
optional = false
python-versions = ">=3.10"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20"},
{file = "tree_sitter-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72a510931c3c25f134aac2daf4eb4feca99ffe37a35896d7150e50ac3eee06c7"},
@@ -2717,7 +2717,7 @@ version = "0.25.1"
description = "Bash grammar for tree-sitter"
optional = false
python-versions = ">=3.10"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_bash-0.25.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0e6235f59e366d220dde7d830196bed597d01e853e44d8ccd1a82c5dd2500acf"},
{file = "tree_sitter_bash-0.25.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f4a34a6504c7c5b2a9b8c5c4065531dea19ca2c35026e706cf2eeeebe2c92512"},
@@ -2739,7 +2739,7 @@ version = "0.24.2"
description = "C grammar for tree-sitter"
optional = false
python-versions = ">=3.10"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_c-0.24.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4d4579a8b54f0a442f903d88d3304cab77cd5c2031d4015baa4f2f8e15d6dcb7"},
{file = "tree_sitter_c-0.24.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:97bc80a224d48215d4e6e6376bf30d114f4c317b8145ff1b02afe785d4ba7bdd"},
@@ -2761,7 +2761,7 @@ version = "0.23.5"
description = "C# grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_c_sharp-0.23.5-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:61e1981cf21b09ee547b9c4c68e64fb4394325f8fc8d5f6d50d41471eba923ea"},
{file = "tree_sitter_c_sharp-0.23.5-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a75994a11f6fed3f5b8c36ad6a00e5dc43205bd912c43af3a2a54fdf649664eb"},
@@ -2783,7 +2783,7 @@ version = "0.23.4"
description = "C++ grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_cpp-0.23.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aacb1759f0efd9dbc25bd8ee88184a340483018869f75412d9c3bc32c039a520"},
{file = "tree_sitter_cpp-0.23.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc3c404d9f0cbd87951213a85440afbf4c31e718f8d907fa9ee12bea4b8d276f"},
@@ -2804,7 +2804,7 @@ version = "0.3.5"
description = "Elixir grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_elixir-0.3.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:514078a2f68d27da9a1e6b6e9601b8456faba6260ecfa252e898a848c4f8584d"},
{file = "tree_sitter_elixir-0.3.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:015f537731af690cfa238b0fb76a8af4f0d1a2c54a38563f159926d2967ce650"},
@@ -2826,7 +2826,7 @@ version = "0.6.0"
description = "Fortran grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_fortran-0.6.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b6495c4c25cf68785ffd30e615b5481219415761ca66dde14a9577d03075714d"},
{file = "tree_sitter_fortran-0.6.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:a0fe5929fd91d245aba5a3b414399a296fb9924942a549190cee226e5b1ec96c"},
@@ -2848,7 +2848,7 @@ version = "0.25.0"
description = "Go grammar for tree-sitter"
optional = false
python-versions = ">=3.10"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_go-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b852993063a3429a443e7bd0aa376dd7dd329d595819fabf56ac4cf9d7257b54"},
{file = "tree_sitter_go-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:503b81a2b4c31e302869a1de3a352ad0912ccab3df9ac9950197b0a9ceeabd8f"},
@@ -2870,7 +2870,7 @@ version = "0.1.2"
description = "Groovy grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_groovy-0.1.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27adb7a4077511782dbd94a12f4635dfb52ccb88f734fe1569393e2d28b18bbd"},
{file = "tree_sitter_groovy-0.1.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:db35a5bdceb826382c7f52d33db0b2075217473f698daf77eb8d4e557a161d51"},
@@ -2891,7 +2891,7 @@ version = "0.23.5"
description = "Java grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_java-0.23.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:355ce0308672d6f7013ec913dee4a0613666f4cda9044a7824240d17f38209df"},
{file = "tree_sitter_java-0.23.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:24acd59c4720dedad80d548fe4237e43ef2b7a4e94c8549b0ca6e4c4d7bf6e69"},
@@ -2912,7 +2912,7 @@ version = "0.25.0"
description = "JavaScript grammar for tree-sitter"
optional = false
python-versions = ">=3.10"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_javascript-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b70f887fb269d6e58c349d683f59fa647140c410cfe2bee44a883b20ec92e3dc"},
{file = "tree_sitter_javascript-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8264a996b8845cfce06965152a013b5d9cbb7d199bc3503e12b5682e62bb1de1"},
@@ -2934,7 +2934,7 @@ version = "0.24.8"
description = "JSON grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_json-0.24.8-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:59ac06c6db1877d0e2076bce54a5fddcdd2fc38ca778905662e80fa9ffcea2ab"},
{file = "tree_sitter_json-0.24.8-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:62b4c45b561db31436a81a3f037f71ec29049f4fc9bf5269b6ec3ebaaa35a1cd"},
@@ -2955,7 +2955,7 @@ version = "0.23.1"
description = "Julia grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_julia-0.23.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4bd4d8e76ab780a2de9af90cefada494cb174991d74993b6a243f28081e9432b"},
{file = "tree_sitter_julia-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8197c8d9b0cb51421aa2832f3fb539504d7b514cbb1fc79130bb1445c0b4a457"},
@@ -2976,7 +2976,7 @@ version = "1.1.0"
description = "Kotlin grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_kotlin-1.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6cca5ef06d090e8494ac1d9f0aac71ed32207d412766b5df7da00d94334181a2"},
{file = "tree_sitter_kotlin-1.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:910b41a580dae00d319e555075f3886a41386d1067931b14c7de504eeae3ae2a"},
@@ -2997,7 +2997,7 @@ version = "0.5.0"
description = "Lua grammar for tree-sitter"
optional = false
python-versions = ">=3.10"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_lua-0.5.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cc4f2eb734dc9223bf96c0eeffa78a9485db207d00841e27e52c8b036f2164f7"},
{file = "tree_sitter_lua-0.5.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c14714ad395c4166566f3e4dd0cc0979411684cbcd23702e3c631c3e6eae84fd"},
@@ -3019,7 +3019,7 @@ version = "3.0.2"
description = "Objective-C grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_objc-3.0.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bd25b3c4ca99263c0898aa7a362a1b8d9bb642692ae9ddd357755586019b1544"},
{file = "tree_sitter_objc-3.0.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa8b1221d2651a51cf42e1551c0804e9f48707da70f41f3195910c599b5522b"},
@@ -3040,7 +3040,7 @@ version = "0.24.1"
description = "PHP grammar for tree-sitter"
optional = false
python-versions = ">=3.10"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_php-0.24.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d56e2dcf025450f84a2cdbf4b18a09e6cb88b92e9e6858e63de3d4133ab2e43e"},
{file = "tree_sitter_php-0.24.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:29759c67d4c27a68c227ed82c0b7e4699617b1bd23757d50c081f81a12b4f80d"},
@@ -3060,7 +3060,7 @@ version = "0.26.4"
description = "A Powershell grammar for tree-sitter"
optional = false
python-versions = ">=3.10"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_powershell-0.26.4-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0bf8beac7ed4501d1c52456f8ae9728ab2a5a079325548b06b1bc9746655524e"},
{file = "tree_sitter_powershell-0.26.4-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:b5dde429c9de55b75906e240d6db1cf85417e2fc0a56d7b321810c2cd4cf3f98"},
@@ -3082,7 +3082,7 @@ version = "0.25.0"
description = "Python grammar for tree-sitter"
optional = false
python-versions = ">=3.10"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361"},
{file = "tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762"},
@@ -3104,7 +3104,7 @@ version = "0.23.1"
description = "Ruby grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_ruby-0.23.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:39f391322d2210843f07081182dbf00f8f69cfbfa4687b9575cac6d324bae443"},
{file = "tree_sitter_ruby-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:aa4ee7433bd42fac22e2dad4a3c0f332292ecf482e610316828c711a0bb7f794"},
@@ -3125,7 +3125,7 @@ version = "0.24.2"
description = "Rust grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_rust-0.24.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3620cfd12340efa43082d45df76349ff511893a9c361da2f8d6d51e307020a59"},
{file = "tree_sitter_rust-0.24.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:01a46622735498493f29f3e628a90de95c96a07bfbeb88996243eb986b1cee36"},
@@ -3147,7 +3147,7 @@ version = "0.26.2"
description = "Scala grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_scala-0.26.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:beab6a0397d4f45cecaed715f224f444652d6bb1c5318d46c641893cd91de98d"},
{file = "tree_sitter_scala-0.26.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:54d2e92824c7908b7e688f218dc1d65c5684c365cd7a3e8028e8860847aeabd4"},
@@ -3168,7 +3168,7 @@ version = "0.7.3"
description = "Swift grammar for tree-sitter"
optional = false
python-versions = ">=3.8"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_swift-0.7.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2531ec866c22ea52384e2786e07f3b2bb396c6446428a2df02cc74af3f7e6b6a"},
{file = "tree_sitter_swift-0.7.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ee627e027d0868c552beca13dcdfa9944662b126f642464c5038ee3204e68340"},
@@ -3190,7 +3190,7 @@ version = "0.23.2"
description = "TypeScript and TSX grammars for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_typescript-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3cd752d70d8e5371fdac6a9a4df9d8924b63b6998d268586f7d374c9fba2a478"},
{file = "tree_sitter_typescript-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c7cc1b0ff5d91bac863b0e38b1578d5505e718156c9db577c8baea2557f66de8"},
@@ -3211,7 +3211,7 @@ version = "1.0.3"
description = "Verilog grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_verilog-1.0.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ee20fe0e21c93bf1a10e20c13cbca959eb3c9693194afb90b0567758cbf1744e"},
{file = "tree_sitter_verilog-1.0.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5b9d70d86cf6913abc08766b6180e285d72848c7491a3f3f8e7bb8d8c440049d"},
@@ -3232,7 +3232,7 @@ version = "1.1.2"
description = "Zig grammar for tree-sitter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
groups = ["claude"]
files = [
{file = "tree_sitter_zig-1.1.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e7542354a5edba377b5692b2add4f346501306d455e192974b7e76bf1a61a282"},
{file = "tree_sitter_zig-1.1.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:daa2cdd7c1a2d278f2a917c85993adb6e84d37778bfc350ee9e342872e7f8be2"},
@@ -3445,4 +3445,4 @@ propcache = ">=0.2.1"
[metadata]
lock-version = "2.1"
python-versions = ">=3.13,<4.0"
content-hash = "b92e68793c9822e819514bba50be5bf35dd09dcb9d7d9068f1e489502da2bfe8"
content-hash = "fffbbc108bcb642d179b4d838b676bd848ab2f4af8d56314d056c74248fe5d06"
+9 -6
View File
@@ -36,20 +36,23 @@ class JwtCrudActions:
async def create_and_update_token(self, data: dict, old_jti: UUID, new_jti: UUID) -> bool:
async with self.Session() as session, session.begin():
new_token = RefreshTokens(**data)
query = (
revoke_query = (
update(RefreshTokens)
.where(RefreshTokens.id == old_jti, RefreshTokens.is_revoked.is_(False))
.values(is_revoked=True, replaced_by=new_jti)
.values(is_revoked=True)
.returning(RefreshTokens.id)
)
updated_id = (await session.execute(query)).scalar_one_or_none()
updated_id = (await session.execute(revoke_query)).scalar_one_or_none()
if updated_id is None:
return False
session.add(new_token)
session.add(RefreshTokens(**data))
await session.flush()
await session.execute(
update(RefreshTokens).where(RefreshTokens.id == old_jti).values(replaced_by=new_jti)
)
return True
+6 -1
View File
@@ -31,7 +31,12 @@ class Env(Base):
EMAIL_LOGIN:str
EMAIL_PASSWORD:str
FRONT_URL:str
FRONT_HOST:str
FRONT_PORT:int
VITE_BACKEND_HOST:str
VITE_BACKEND_PORT:str
PROD_MODE:bool
model_config=SettingsConfigDict(env_file="configs/.env", extra=None)
+4
View File
@@ -1,5 +1,6 @@
import asyncio
from src.database.auth.refresh_tokens import JwtCrudActions
from src.database.users.crud import UsersCrudActions
from src.errors.http_errors.errors import Errors
from src.models.pydantic_models.model import UserCreate, UserOut, UserUpdate
@@ -12,6 +13,7 @@ class CrudService:
self.errors=Errors()
self.crud_db_actions=UsersCrudActions()
self.hash_service=HashService()
self.jwt_db_actions=JwtCrudActions()
async def _plain_to_hash(self, user_data:dict)->dict:
@@ -78,6 +80,8 @@ class CrudService:
async def reset_password(self, email:str)->str:
new_password=await self.hash_service.generate_temp_password()
await self.update_user(email, UserUpdate(plain_password=new_password)) # type: ignore[call-arg]
return new_password
+3 -3
View File
@@ -93,6 +93,6 @@ async def reset(request: Request,
return {"detail":"New password was sent"}
@router.get("")
async def protected(current_user:UserOut=Depends(require_permissions()))->dict:
return {"protected router": "Hello, this is a protected router"}
@router.get("/me")
async def protected(current_user:UserOut=Depends(require_permissions()))->UserOut:
return current_user
+28
View File
@@ -6,6 +6,7 @@ from fastapi import HTTPException
from jose import jwt
from src.models.configs_read.env import env_settings
from src.models.pydantic_models.model import validate_password
from src.service.auth.jwt import HashService, JwtService
@@ -146,3 +147,30 @@ class TestJwt:
async def test_token_to_hash_different_input(self, hash_service:HashService)->None:
assert hash_service.token_to_hash("abc")!=hash_service.token_to_hash("xyz")
@pytest.mark.parametrize("complexity",[
pytest.param(8,id="low_edge"),
pytest.param(9,id="low_edge+1"),
pytest.param(16,id="default"),
pytest.param(72,id="high_edge"),
pytest.param(71,id="high_edge-1")
])
async def test_generate_temp_password_positive(self, hash_service:HashService, complexity:int)->None:
for _ in range(10000):
password=await hash_service.generate_temp_password(complexity)
try:
validate_password(password)
except ValueError as e:
pytest.fail(f"{password!r}: {e}")
@pytest.mark.parametrize("complexity", [
pytest.param(73,id="too_long"),
pytest.param(7,id="too_short")
])
async def test_generate_temp_password_negative(self, hash_service:HashService, complexity:int)->None:
password = await hash_service.generate_temp_password(complexity)
with pytest.raises(ValueError, match="8-72"):
validate_password(password)
+8
View File
@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
yarn@^1.22.22:
version "1.22.22"
resolved "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz"
integrity sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==