Compare commits
	
		
			1 Commits
		
	
	
		
			master
			...
			4922739106
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4922739106 | 
| @@ -14,7 +14,7 @@ steps: | |||||||
|   - cargo test |   - cargo test | ||||||
|  |  | ||||||
| - name: app_deploy | - name: app_deploy | ||||||
|   image: node:24 |   image: node:22 | ||||||
|   environment: |   environment: | ||||||
|     AWS_ACCESS_KEY_ID: |     AWS_ACCESS_KEY_ID: | ||||||
|       from_secret: AWS_ACCESS_KEY_ID |       from_secret: AWS_ACCESS_KEY_ID | ||||||
|   | |||||||
| @@ -1,28 +0,0 @@ | |||||||
| import js from '@eslint/js' |  | ||||||
| import globals from 'globals' |  | ||||||
| import reactHooks from 'eslint-plugin-react-hooks' |  | ||||||
| import reactRefresh from 'eslint-plugin-react-refresh' |  | ||||||
| import tseslint from 'typescript-eslint' |  | ||||||
|  |  | ||||||
| export default tseslint.config( |  | ||||||
|   { ignores: ['dist'] }, |  | ||||||
|   { |  | ||||||
|     extends: [js.configs.recommended, ...tseslint.configs.recommended], |  | ||||||
|     files: ['**/*.{ts,tsx}'], |  | ||||||
|     languageOptions: { |  | ||||||
|       ecmaVersion: 2020, |  | ||||||
|       globals: globals.browser, |  | ||||||
|     }, |  | ||||||
|     plugins: { |  | ||||||
|       'react-hooks': reactHooks, |  | ||||||
|       'react-refresh': reactRefresh, |  | ||||||
|     }, |  | ||||||
|     rules: { |  | ||||||
|       ...reactHooks.configs.recommended.rules, |  | ||||||
|       'react-refresh/only-export-components': [ |  | ||||||
|         'warn', |  | ||||||
|         { allowConstantExport: true }, |  | ||||||
|       ], |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
| ) |  | ||||||
							
								
								
									
										3710
									
								
								geneit_app/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3710
									
								
								geneit_app/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -3,54 +3,70 @@ | |||||||
|   "version": "0.1.0", |   "version": "0.1.0", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "type": "module", |   "type": "module", | ||||||
|  |   "dependencies": { | ||||||
|  |     "@babel/plugin-proposal-private-property-in-object": "^7.21.11", | ||||||
|  |     "@emotion/react": "^11.13.3", | ||||||
|  |     "@emotion/styled": "^11.13.0", | ||||||
|  |     "@fontsource/roboto": "^5.1.0", | ||||||
|  |     "@fullcalendar/core": "^6.1.15", | ||||||
|  |     "@fullcalendar/daygrid": "^6.1.15", | ||||||
|  |     "@fullcalendar/interaction": "^6.1.15", | ||||||
|  |     "@fullcalendar/list": "^6.1.15", | ||||||
|  |     "@fullcalendar/react": "^6.1.15", | ||||||
|  |     "@mdi/js": "^7.2.96", | ||||||
|  |     "@mdi/react": "^1.6.1", | ||||||
|  |     "@mui/icons-material": "^6.1.2", | ||||||
|  |     "@mui/lab": "^6.0.0-beta.10", | ||||||
|  |     "@mui/material": "^6.1.2", | ||||||
|  |     "@mui/x-data-grid": "^7.18.0", | ||||||
|  |     "@mui/x-date-pickers": "^7.18.0", | ||||||
|  |     "@mui/x-tree-view": "^7.18.0", | ||||||
|  |     "@testing-library/jest-dom": "^6.5.0", | ||||||
|  |     "@testing-library/react": "^16.0.1", | ||||||
|  |     "@testing-library/user-event": "^14.0.0", | ||||||
|  |     "@types/jest": "^29.5.13", | ||||||
|  |     "@types/react": "^18.3.11", | ||||||
|  |     "@types/react-dom": "^18.3.0", | ||||||
|  |     "@vitejs/plugin-react": "^4.3.2", | ||||||
|  |     "date-and-time": "^3.6.0", | ||||||
|  |     "dayjs": "^1.11.13", | ||||||
|  |     "email-validator": "^2.0.4", | ||||||
|  |     "filesize": "^10.1.6", | ||||||
|  |     "jspdf": "^2.5.2", | ||||||
|  |     "mui-color-input": "^5.0.0", | ||||||
|  |     "react": "^18.3.1", | ||||||
|  |     "react-dom": "^18.3.1", | ||||||
|  |     "react-easy-crop": "^5.0.8", | ||||||
|  |     "react-qr-code": "^2.0.14", | ||||||
|  |     "react-router-dom": "^7.0.0", | ||||||
|  |     "react-zoom-pan-pinch": "^3.4.4", | ||||||
|  |     "svg2pdf.js": "^2.2.3", | ||||||
|  |     "typescript": "^5.6.2", | ||||||
|  |     "vite": "^6.0.0", | ||||||
|  |     "vite-tsconfig-paths": "^5.0.1", | ||||||
|  |     "web-vitals": "^3.5.2" | ||||||
|  |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "dev": "vite", |     "start": "vite", | ||||||
|     "build": "tsc -b && vite build", |     "build": "tsc && vite build", | ||||||
|     "lint": "eslint .", |  | ||||||
|     "preview": "vite preview" |     "preview": "vite preview" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "eslintConfig": { | ||||||
|     "@emotion/react": "^11.14.0", |     "extends": [ | ||||||
|     "@emotion/styled": "^11.14.1", |       "react-app", | ||||||
|     "@fontsource/roboto": "^5.2.8", |       "react-app/jest" | ||||||
|     "@fullcalendar/core": "^6.1.19", |     ] | ||||||
|     "@fullcalendar/daygrid": "^6.1.19", |  | ||||||
|     "@fullcalendar/interaction": "^6.1.19", |  | ||||||
|     "@fullcalendar/list": "^6.1.19", |  | ||||||
|     "@fullcalendar/react": "^6.1.19", |  | ||||||
|     "@mdi/js": "^7.4.47", |  | ||||||
|     "@mdi/react": "^1.6.1", |  | ||||||
|     "@mui/icons-material": "^7.2.0", |  | ||||||
|     "@mui/lab": "^7.0.0-beta.17", |  | ||||||
|     "@mui/material": "^7.2.0", |  | ||||||
|     "@mui/x-data-grid": "^8.15.0", |  | ||||||
|     "@mui/x-date-pickers": "^8.15.0", |  | ||||||
|     "@mui/x-tree-view": "^8.15.0", |  | ||||||
|     "date-and-time": "^3.6.0", |  | ||||||
|     "dayjs": "^1.11.18", |  | ||||||
|     "email-validator": "^2.0.4", |  | ||||||
|     "filesize": "^11.0.13", |  | ||||||
|     "jspdf": "^3.0.3", |  | ||||||
|     "mui-color-input": "^7.0.0", |  | ||||||
|     "react": "^19.2.0", |  | ||||||
|     "react-dom": "^19.2.0", |  | ||||||
|     "react-easy-crop": "^5.5.3", |  | ||||||
|     "react-qr-code": "^2.0.18", |  | ||||||
|     "react-router-dom": "^7.4.0", |  | ||||||
|     "react-zoom-pan-pinch": "^3.7.0", |  | ||||||
|     "svg2pdf.js": "^2.6.0" |  | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "browserslist": { | ||||||
|     "@eslint/js": "^9.38.0", |     "production": [ | ||||||
|     "@types/react": "^19.2.2", |       ">0.2%", | ||||||
|     "@types/react-dom": "^19.2.2", |       "not dead", | ||||||
|     "@vitejs/plugin-react": "^4.7.0", |       "not op_mini all" | ||||||
|     "eslint": "^9.38.0", |     ], | ||||||
|     "eslint-plugin-react-hooks": "^5.2.0", |     "development": [ | ||||||
|     "eslint-plugin-react-refresh": "^0.4.24", |       "last 1 chrome version", | ||||||
|     "globals": "^16.4.0", |       "last 1 firefox version", | ||||||
|     "typescript": "^5.9.3", |       "last 1 safari version" | ||||||
|     "typescript-eslint": "^8.38.0", |     ] | ||||||
|     "vite": "^7.0.6" |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -59,10 +59,7 @@ export function App(): React.ReactElement { | |||||||
|  |  | ||||||
|   const context: AuthContext = { |   const context: AuthContext = { | ||||||
|     signedIn: signedIn, |     signedIn: signedIn, | ||||||
|     setSignedIn: (s) => { |     setSignedIn: (s) => setSignedIn(s), | ||||||
|       location.reload(); |  | ||||||
|       setSignedIn(s); |  | ||||||
|     }, |  | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const router = createBrowserRouter( |   const router = createBrowserRouter( | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client"; | |||||||
| import { App } from "./App"; | import { App } from "./App"; | ||||||
| import { ServerApi } from "./api/ServerApi"; | import { ServerApi } from "./api/ServerApi"; | ||||||
| import "./index.css"; | import "./index.css"; | ||||||
|  | import reportWebVitals from "./reportWebVitals"; | ||||||
|  |  | ||||||
| // Roboto font | // Roboto font | ||||||
| import "@fontsource/roboto/300.css"; | import "@fontsource/roboto/300.css"; | ||||||
| @@ -60,3 +61,8 @@ async function init() { | |||||||
| } | } | ||||||
|  |  | ||||||
| init(); | init(); | ||||||
|  |  | ||||||
|  | // If you want to start measuring performance in your app, pass a function | ||||||
|  | // to log results (for example: reportWebVitals(console.log)) | ||||||
|  | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals | ||||||
|  | reportWebVitals(); | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								geneit_app/src/reportWebVitals.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								geneit_app/src/reportWebVitals.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | import { ReportHandler } from 'web-vitals'; | ||||||
|  |  | ||||||
|  | const reportWebVitals = (onPerfEntry?: ReportHandler) => { | ||||||
|  |   if (onPerfEntry && onPerfEntry instanceof Function) { | ||||||
|  |     import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { | ||||||
|  |       getCLS(onPerfEntry); | ||||||
|  |       getFID(onPerfEntry); | ||||||
|  |       getFCP(onPerfEntry); | ||||||
|  |       getLCP(onPerfEntry); | ||||||
|  |       getTTFB(onPerfEntry); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default reportWebVitals; | ||||||
| @@ -1,5 +1,4 @@ | |||||||
| import VisibilityIcon from "@mui/icons-material/Visibility"; | import { Visibility, VisibilityOff } from "@mui/icons-material"; | ||||||
| import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; |  | ||||||
| import { | import { | ||||||
|   Alert, |   Alert, | ||||||
|   CircularProgress, |   CircularProgress, | ||||||
| @@ -12,7 +11,7 @@ import { | |||||||
| } from "@mui/material"; | } from "@mui/material"; | ||||||
| import Box from "@mui/material/Box"; | import Box from "@mui/material/Box"; | ||||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid2"; | ||||||
| import TextField from "@mui/material/TextField"; | import TextField from "@mui/material/TextField"; | ||||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||||
| import * as React from "react"; | import * as React from "react"; | ||||||
| @@ -149,7 +148,7 @@ export function LoginRoute(): React.ReactElement { | |||||||
|                     onMouseDown={handleMouseDownPassword} |                     onMouseDown={handleMouseDownPassword} | ||||||
|                     edge="end" |                     edge="end" | ||||||
|                   > |                   > | ||||||
|                     {showPassword ? <VisibilityOffIcon /> : <VisibilityIcon />} |                     {showPassword ? <VisibilityOff /> : <Visibility />} | ||||||
|                   </IconButton> |                   </IconButton> | ||||||
|                 </Tooltip> |                 </Tooltip> | ||||||
|               </InputAdornment> |               </InputAdornment> | ||||||
|   | |||||||
| @@ -137,10 +137,8 @@ function UsersTable(p: { | |||||||
|             <GridActionsCellItem |             <GridActionsCellItem | ||||||
|               icon={<SaveIcon />} |               icon={<SaveIcon />} | ||||||
|               label="Save" |               label="Save" | ||||||
|               material={{ |               sx={{ | ||||||
|                 sx: { |                 color: "primary.main", | ||||||
|                   color: 'primary.main', |  | ||||||
|                 }, |  | ||||||
|               }} |               }} | ||||||
|               onClick={handleSaveClick(id)} |               onClick={handleSaveClick(id)} | ||||||
|             />, |             />, | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import EditIcon from "@mui/icons-material/Edit"; | |||||||
| import FileDownloadIcon from "@mui/icons-material/FileDownload"; | import FileDownloadIcon from "@mui/icons-material/FileDownload"; | ||||||
| import SaveIcon from "@mui/icons-material/Save"; | import SaveIcon from "@mui/icons-material/Save"; | ||||||
| import { Button, Stack } from "@mui/material"; | import { Button, Stack } from "@mui/material"; | ||||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid2"; | ||||||
| import React from "react"; | import React from "react"; | ||||||
| import { useNavigate, useParams } from "react-router-dom"; | import { useNavigate, useParams } from "react-router-dom"; | ||||||
| import { ServerApi } from "../../../api/ServerApi"; | import { ServerApi } from "../../../api/ServerApi"; | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import { useFamily } from "../../../widgets/BaseFamilyRoute"; | ||||||
| import { FamilyPageTitle } from "../../../widgets/FamilyPageTitle"; | import { FamilyPageTitle } from "../../../widgets/FamilyPageTitle"; | ||||||
|  |  | ||||||
| export function FamilyHomeRoute(): React.ReactElement { | export function FamilyHomeRoute(): React.ReactElement { | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ import { | |||||||
|   ListItemText, |   ListItemText, | ||||||
|   Stack, |   Stack, | ||||||
| } from "@mui/material"; | } from "@mui/material"; | ||||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid2"; | ||||||
| import * as EmailValidator from "email-validator"; | import * as EmailValidator from "email-validator"; | ||||||
| import React from "react"; | import React from "react"; | ||||||
| import { useNavigate, useParams } from "react-router-dom"; | import { useNavigate, useParams } from "react-router-dom"; | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								geneit_app/src/setupTests.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								geneit_app/src/setupTests.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | // jest-dom adds custom jest matchers for asserting on DOM nodes. | ||||||
|  | // allows you to do things like: | ||||||
|  | // expect(element).toHaveTextContent(/react/i) | ||||||
|  | // learn more: https://github.com/testing-library/jest-dom | ||||||
|  | import '@testing-library/jest-dom'; | ||||||
| @@ -43,13 +43,9 @@ export function BaseAuthenticatedPage(): React.ReactElement { | |||||||
|     setAnchorEl(null); |     setAnchorEl(null); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const signOut = async () => { |   const signOut = () => { | ||||||
|     handleCloseMenu(); |     handleCloseMenu(); | ||||||
|     try { |     AuthApi.SignOut(); | ||||||
|       await AuthApi.SignOut(); |  | ||||||
|     } catch (e) { |  | ||||||
|       console.error(e); |  | ||||||
|     } |  | ||||||
|     navigate("/"); |     navigate("/"); | ||||||
|     auth.setSignedIn(false); |     auth.setSignedIn(false); | ||||||
|   }; |   }; | ||||||
|   | |||||||
| @@ -54,7 +54,7 @@ export function BaseFamilyRoute(): React.ReactElement { | |||||||
|  |  | ||||||
|   const loadKey = React.useRef(1); |   const loadKey = React.useRef(1); | ||||||
|  |  | ||||||
|   const loadPromise = React.useRef<() => void>(null); |   const loadPromise = React.useRef<() => void>(); | ||||||
|  |  | ||||||
|   const load = async () => { |   const load = async () => { | ||||||
|     const familyID = Number(familyId); |     const familyID = Number(familyId); | ||||||
| @@ -104,7 +104,7 @@ export function BaseFamilyRoute(): React.ReactElement { | |||||||
|       build={() => { |       build={() => { | ||||||
|         if (loadPromise.current != null) { |         if (loadPromise.current != null) { | ||||||
|           loadPromise.current?.(); |           loadPromise.current?.(); | ||||||
|           loadPromise.current = null; |           loadPromise.current = undefined; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return ( |         return ( | ||||||
|   | |||||||
| @@ -3,9 +3,10 @@ import Icon from "@mdi/react"; | |||||||
| import Avatar from "@mui/material/Avatar"; | import Avatar from "@mui/material/Avatar"; | ||||||
| import Box from "@mui/material/Box"; | import Box from "@mui/material/Box"; | ||||||
| import CssBaseline from "@mui/material/CssBaseline"; | import CssBaseline from "@mui/material/CssBaseline"; | ||||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid2"; | ||||||
| import Paper from "@mui/material/Paper"; | import Paper from "@mui/material/Paper"; | ||||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||||
|  | import * as React from "react"; | ||||||
| import { Link, Outlet } from "react-router-dom"; | import { Link, Outlet } from "react-router-dom"; | ||||||
| import { DarkThemeButton } from "./DarkThemeButton"; | import { DarkThemeButton } from "./DarkThemeButton"; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import VisibilityIcon from "@mui/icons-material/Visibility"; | import { Visibility, VisibilityOff } from "@mui/icons-material"; | ||||||
| import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; |  | ||||||
| import { | import { | ||||||
|   FormControl, |   FormControl, | ||||||
|   FormHelperText, |   FormHelperText, | ||||||
| @@ -51,7 +50,7 @@ export function PasswordInput(p: { | |||||||
|               onMouseDown={handleMouseDownPassword} |               onMouseDown={handleMouseDownPassword} | ||||||
|               edge="end" |               edge="end" | ||||||
|             > |             > | ||||||
|               {showPassword ? <VisibilityOffIcon /> : <VisibilityIcon />} |               {showPassword ? <VisibilityOff /> : <Visibility />} | ||||||
|             </IconButton> |             </IconButton> | ||||||
|           </InputAdornment> |           </InputAdornment> | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ export function BaseAccommodationsRoute(): React.ReactElement { | |||||||
|  |  | ||||||
|   const loadKey = React.useRef(1); |   const loadKey = React.useRef(1); | ||||||
|  |  | ||||||
|   const loadPromise = React.useRef<() => void>(null); |   const loadPromise = React.useRef<() => void>(); | ||||||
|  |  | ||||||
|   const load = async () => { |   const load = async () => { | ||||||
|     setAccommodations( |     setAccommodations( | ||||||
| @@ -53,7 +53,7 @@ export function BaseAccommodationsRoute(): React.ReactElement { | |||||||
|       build={() => { |       build={() => { | ||||||
|         if (loadPromise.current != null) { |         if (loadPromise.current != null) { | ||||||
|           loadPromise.current?.(); |           loadPromise.current?.(); | ||||||
|           loadPromise.current = null; |           loadPromise.current = undefined; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return ( |         return ( | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ export function BaseGenealogyRoute(): React.ReactElement { | |||||||
|  |  | ||||||
|   const loadKey = React.useRef(1); |   const loadKey = React.useRef(1); | ||||||
|  |  | ||||||
|   const loadPromise = React.useRef<() => void>(null); |   const loadPromise = React.useRef<() => void>(); | ||||||
|  |  | ||||||
|   const load = async () => { |   const load = async () => { | ||||||
|     setMembers(await MemberApi.GetEntireList(family.familyId)); |     setMembers(await MemberApi.GetEntireList(family.familyId)); | ||||||
| @@ -48,7 +48,7 @@ export function BaseGenealogyRoute(): React.ReactElement { | |||||||
|       build={() => { |       build={() => { | ||||||
|         if (loadPromise.current != null) { |         if (loadPromise.current != null) { | ||||||
|           loadPromise.current?.(); |           loadPromise.current?.(); | ||||||
|           loadPromise.current = null; |           loadPromise.current = undefined; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return ( |         return ( | ||||||
|   | |||||||
| @@ -1,26 +0,0 @@ | |||||||
| { |  | ||||||
|   "compilerOptions": { |  | ||||||
|     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |  | ||||||
|     "target": "ES2020", |  | ||||||
|     "useDefineForClassFields": true, |  | ||||||
|     "lib": ["ES2020", "DOM", "DOM.Iterable"], |  | ||||||
|     "module": "ESNext", |  | ||||||
|     "skipLibCheck": true, |  | ||||||
|  |  | ||||||
|     /* Bundler mode */ |  | ||||||
|     "moduleResolution": "bundler", |  | ||||||
|     "allowImportingTsExtensions": true, |  | ||||||
|     "isolatedModules": true, |  | ||||||
|     "moduleDetection": "force", |  | ||||||
|     "noEmit": true, |  | ||||||
|     "jsx": "react-jsx", |  | ||||||
|  |  | ||||||
|     /* Linting */ |  | ||||||
|     "strict": true, |  | ||||||
|     "noUnusedLocals": true, |  | ||||||
|     "noUnusedParameters": true, |  | ||||||
|     "noFallthroughCasesInSwitch": true, |  | ||||||
|     "noUncheckedSideEffectImports": true |  | ||||||
|   }, |  | ||||||
|   "include": ["src"] |  | ||||||
| } |  | ||||||
| @@ -1,7 +1,21 @@ | |||||||
| { | { | ||||||
|   "files": [], |   "compilerOptions": { | ||||||
|   "references": [ |     "target": "ESNext", | ||||||
|     { "path": "./tsconfig.app.json" }, |     "lib": ["dom", "dom.iterable", "esnext"], | ||||||
|     { "path": "./tsconfig.node.json" } |     "types": ["vite/client"], | ||||||
|   ] |     "allowJs": true, | ||||||
|  |     "skipLibCheck": true, | ||||||
|  |     "esModuleInterop": true, | ||||||
|  |     "allowSyntheticDefaultImports": true, | ||||||
|  |     "strict": true, | ||||||
|  |     "forceConsistentCasingInFileNames": true, | ||||||
|  |     "noFallthroughCasesInSwitch": true, | ||||||
|  |     "module": "esnext", | ||||||
|  |     "moduleResolution": "node", | ||||||
|  |     "resolveJsonModule": true, | ||||||
|  |     "isolatedModules": true, | ||||||
|  |     "noEmit": true, | ||||||
|  |     "jsx": "react-jsx" | ||||||
|  |   }, | ||||||
|  |   "include": ["src"] | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,24 +0,0 @@ | |||||||
| { |  | ||||||
|   "compilerOptions": { |  | ||||||
|     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |  | ||||||
|     "target": "ES2022", |  | ||||||
|     "lib": ["ES2023"], |  | ||||||
|     "module": "ESNext", |  | ||||||
|     "skipLibCheck": true, |  | ||||||
|  |  | ||||||
|     /* Bundler mode */ |  | ||||||
|     "moduleResolution": "bundler", |  | ||||||
|     "allowImportingTsExtensions": true, |  | ||||||
|     "isolatedModules": true, |  | ||||||
|     "moduleDetection": "force", |  | ||||||
|     "noEmit": true, |  | ||||||
|  |  | ||||||
|     /* Linting */ |  | ||||||
|     "strict": true, |  | ||||||
|     "noUnusedLocals": true, |  | ||||||
|     "noUnusedParameters": true, |  | ||||||
|     "noFallthroughCasesInSwitch": true, |  | ||||||
|     "noUncheckedSideEffectImports": true |  | ||||||
|   }, |  | ||||||
|   "include": ["vite.config.ts"] |  | ||||||
| } |  | ||||||
| @@ -1,7 +1,15 @@ | |||||||
| import { defineConfig } from 'vite' | import { defineConfig } from "vite"; | ||||||
| import react from '@vitejs/plugin-react' | import react from "@vitejs/plugin-react"; | ||||||
|  | import viteTsconfigPaths from "vite-tsconfig-paths"; | ||||||
|  |  | ||||||
| // https://vite.dev/config/ |  | ||||||
| export default defineConfig({ | export default defineConfig({ | ||||||
|   plugins: [react()], |   // depending on your application, base can also be "/" | ||||||
| }) |   base: "/", | ||||||
|  |   plugins: [react(), viteTsconfigPaths()], | ||||||
|  |   server: { | ||||||
|  |     // this ensures that the browser opens upon server start | ||||||
|  |     open: true, | ||||||
|  |     // this sets a default port to 3000 | ||||||
|  |     port: 3000, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|   | |||||||
							
								
								
									
										1638
									
								
								geneit_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1638
									
								
								geneit_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,43 +1,43 @@ | |||||||
| [package] | [package] | ||||||
| name = "geneit_backend" | name = "geneit_backend" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| edition = "2024" | edition = "2021" | ||||||
|  |  | ||||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| log = "0.4.28" | log = "0.4.21" | ||||||
| env_logger = "0.11.8" | env_logger = "0.11.5" | ||||||
| clap = { version = "4.5.50", features = ["derive", "env"] } | clap = { version = "4.5.17", features = ["derive", "env"] } | ||||||
| lazy_static = "1.5.0" | lazy_static = "1.5.0" | ||||||
| lazy-regex = "3.4.1" | lazy-regex = "3.3.0" | ||||||
| anyhow = "1.0.100" | anyhow = "1.0.87" | ||||||
| actix-web = "4.11.0" | actix-web = "4.9.0" | ||||||
| actix-cors = "0.7.1" | actix-cors = "0.7.0" | ||||||
| actix-multipart = "0.7.2" | actix-multipart = "0.7.0" | ||||||
| actix-remote-ip = "0.1.0" | actix-remote-ip = "0.1.0" | ||||||
| futures-util = "0.3.31" | futures-util = "0.3.30" | ||||||
| diesel = { version = "2.2.12", features = ["postgres"] } | diesel = { version = "2.2.4", features = ["postgres"] } | ||||||
| diesel_migrations = "2.2.0" | diesel_migrations = "2.1.0" | ||||||
| serde = { version = "1.0.228", features = ["derive"] } | serde = { version = "1.0.210", features = ["derive"] } | ||||||
| serde_json = "1.0.145" | serde_json = "1.0.128" | ||||||
| mailchecker = "6.0.19" | mailchecker = "6.0.8" | ||||||
| redis = "0.32.7" | redis = "0.27.0" | ||||||
| lettre = "0.11.19" | lettre = "0.11.8" | ||||||
| rand = "0.9.2" | rand = "0.8.5" | ||||||
| bcrypt = "0.17.1" | bcrypt = "0.16.0" | ||||||
| light-openid = "1.0.4" | light-openid = "1.0.2" | ||||||
| thiserror = "2.0.17" | thiserror = "1.0.60" | ||||||
| serde_with = "3.14.0" | serde_with = "3.8.1" | ||||||
| rust_iso3166 = "0.1.14" | rust_iso3166 = "0.1.12" | ||||||
| rust-s3 = "0.35.1" | rust-s3 = "0.35.1" | ||||||
| sha2 = "0.10.9" | sha2 = "0.10.8" | ||||||
| image = "0.25.8" | image = "0.25.1" | ||||||
| uuid = { version = "1.17.0", features = ["v4"] } | uuid = { version = "1.8.0", features = ["v4"] } | ||||||
| httpdate = "1.0.3" | httpdate = "1.0.3" | ||||||
| zip = "4.3.0" | zip = "2.2.0" | ||||||
| mime_guess = "2.0.5" | mime_guess = "2.0.4" | ||||||
| tempfile = "3.20.0" | tempfile = "3.12.0" | ||||||
| base64 = "0.22.1" | base64 = "0.22.0" | ||||||
| ical = { version = "0.11.0", features = ["generator", "ical", "vcard"] } | ical = { version = "0.11.0", features = ["generator", "ical", "vcard"] } | ||||||
| chrono = "0.4.42" | chrono = "0.4.38" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| use crate::app_config::AppConfig; | use crate::app_config::AppConfig; | ||||||
| use diesel::result::{DatabaseErrorKind, Error}; | use diesel::result::{DatabaseErrorKind, Error}; | ||||||
| use diesel::{Connection, PgConnection}; | use diesel::{Connection, PgConnection}; | ||||||
| use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations}; | use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; | ||||||
| use std::cell::RefCell; | use std::cell::RefCell; | ||||||
|  |  | ||||||
| const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); | const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); | ||||||
| @@ -21,7 +21,7 @@ where | |||||||
|     if POSTGRES_CONNECTION.with(|i| i.borrow().is_none()) { |     if POSTGRES_CONNECTION.with(|i| i.borrow().is_none()) { | ||||||
|         let database_url = AppConfig::get().db_connection_chain(); |         let database_url = AppConfig::get().db_connection_chain(); | ||||||
|         let conn = PgConnection::establish(&database_url) |         let conn = PgConnection::establish(&database_url) | ||||||
|             .unwrap_or_else(|_| panic!("Error connecting to {database_url}")); |             .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)); | ||||||
|  |  | ||||||
|         POSTGRES_CONNECTION.with(|i| *i.borrow_mut() = Some(conn)) |         POSTGRES_CONNECTION.with(|i| *i.borrow_mut() = Some(conn)) | ||||||
|     } |     } | ||||||
| @@ -38,7 +38,7 @@ where | |||||||
|                 POSTGRES_CONNECTION.with(|i| *i.borrow_mut() = None) |                 POSTGRES_CONNECTION.with(|i| *i.borrow_mut() = None) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             log::error!("Database query error! {e:?}"); |             log::error!("Database query error! {:?}", e); | ||||||
|             Err(e.into()) |             Err(e.into()) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ pub async fn create_bucket_if_required() -> anyhow::Result<()> { | |||||||
|             log::warn!("The bucket does not seem to exists, trying to create it!") |             log::warn!("The bucket does not seem to exists, trying to create it!") | ||||||
|         } |         } | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             log::error!("Got unexpected error when querying bucket info: {e}"); |             log::error!("Got unexpected error when querying bucket info: {}", e); | ||||||
|             return Err(BucketServiceError::FailedFetchBucketInfo.into()); |             return Err(BucketServiceError::FailedFetchBucketInfo.into()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ use crate::extractors::accommodation_extractor::FamilyAndAccommodationInPath; | |||||||
| use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership}; | use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership}; | ||||||
| use crate::models::Accommodation; | use crate::models::Accommodation; | ||||||
| use crate::services::accommodations_list_service; | use crate::services::accommodations_list_service; | ||||||
| use actix_web::{HttpResponse, web}; | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| #[derive(thiserror::Error, Debug)] | #[derive(thiserror::Error, Debug)] | ||||||
| enum AccommodationListControllerErr { | enum AccommodationListControllerErr { | ||||||
| @@ -34,16 +34,18 @@ impl AccommodationRequest { | |||||||
|         } |         } | ||||||
|         accommodation.name = self.name; |         accommodation.name = self.name; | ||||||
|  |  | ||||||
|         if let Some(d) = &self.description |         if let Some(d) = &self.description { | ||||||
|             && !c.accommodation_description_len.validate(d) { |             if !c.accommodation_description_len.validate(d) { | ||||||
|                 return Err(AccommodationListControllerErr::InvalidDescriptionLength.into()); |                 return Err(AccommodationListControllerErr::InvalidDescriptionLength.into()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|         accommodation.description.clone_from(&self.description); |         accommodation.description.clone_from(&self.description); | ||||||
|  |  | ||||||
|         if let Some(c) = &self.color |         if let Some(c) = &self.color { | ||||||
|             && !lazy_regex::regex!("[a-fA-F0-9]{6}").is_match(c) { |             if !lazy_regex::regex!("[a-fA-F0-9]{6}").is_match(c) { | ||||||
|                 return Err(AccommodationListControllerErr::MalformedColor.into()); |                 return Err(AccommodationListControllerErr::MalformedColor.into()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|         accommodation.color.clone_from(&self.color); |         accommodation.color.clone_from(&self.color); | ||||||
|  |  | ||||||
|         accommodation.need_validation = self.need_validation; |         accommodation.need_validation = self.need_validation; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| use ical::{generator::*, *}; | use ical::{generator::*, *}; | ||||||
|  |  | ||||||
| use actix_web::{HttpResponse, web}; | use actix_web::{web, HttpResponse}; | ||||||
| use chrono::DateTime; | use chrono::DateTime; | ||||||
|  |  | ||||||
| use crate::constants::StaticConstraints; | use crate::constants::StaticConstraints; | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ use crate::extractors::family_extractor::FamilyInPath; | |||||||
| use crate::models::{Accommodation, AccommodationReservationID, NewAccommodationReservation}; | use crate::models::{Accommodation, AccommodationReservationID, NewAccommodationReservation}; | ||||||
| use crate::services::accommodations_reservations_service; | use crate::services::accommodations_reservations_service; | ||||||
| use crate::utils::time_utils::time; | use crate::utils::time_utils::time; | ||||||
| use actix_web::{HttpResponse, web}; | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| #[derive(serde::Deserialize)] | #[derive(serde::Deserialize)] | ||||||
| pub struct UpdateReservationQuery { | pub struct UpdateReservationQuery { | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ use crate::services::login_token_service::LoginTokenValue; | |||||||
| use crate::services::rate_limiter_service::RatedAction; | use crate::services::rate_limiter_service::RatedAction; | ||||||
| use crate::services::{login_token_service, openid_service, rate_limiter_service, users_service}; | use crate::services::{login_token_service, openid_service, rate_limiter_service, users_service}; | ||||||
| use actix_remote_ip::RemoteIP; | use actix_remote_ip::RemoteIP; | ||||||
| use actix_web::{HttpResponse, web}; | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| #[derive(serde::Deserialize)] | #[derive(serde::Deserialize)] | ||||||
| pub struct CreateAccountBody { | pub struct CreateAccountBody { | ||||||
| @@ -79,7 +79,10 @@ pub async fn request_reset_password( | |||||||
|     match users_service::get_by_mail(&req.mail).await { |     match users_service::get_by_mail(&req.mail).await { | ||||||
|         Ok(mut user) => users_service::request_reset_password(&mut user).await?, |         Ok(mut user) => users_service::request_reset_password(&mut user).await?, | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             log::error!("Could not locate user account {e}! (error silently ignored)"); |             log::error!( | ||||||
|  |                 "Could not locate user account {}! (error silently ignored)", | ||||||
|  |                 e | ||||||
|  |             ); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -119,7 +122,7 @@ pub async fn check_reset_password_token( | |||||||
|                 RatedAction::CheckResetPasswordTokenFailed, |                 RatedAction::CheckResetPasswordTokenFailed, | ||||||
|             ) |             ) | ||||||
|             .await?; |             .await?; | ||||||
|             log::error!("Password reset token could not be used: {e}"); |             log::error!("Password reset token could not be used: {}", e); | ||||||
|             return Ok(HttpResponse::NotFound().finish()); |             return Ok(HttpResponse::NotFound().finish()); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| @@ -153,7 +156,7 @@ pub async fn reset_password(remote_ip: RemoteIP, req: web::Json<ResetPasswordBod | |||||||
|                 RatedAction::CheckResetPasswordTokenFailed, |                 RatedAction::CheckResetPasswordTokenFailed, | ||||||
|             ) |             ) | ||||||
|             .await?; |             .await?; | ||||||
|             log::error!("Password reset token could not be used: {e}"); |             log::error!("Password reset token could not be used: {}", e); | ||||||
|             return Ok(HttpResponse::NotFound().finish()); |             return Ok(HttpResponse::NotFound().finish()); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| @@ -193,7 +196,7 @@ pub async fn password_login(remote_ip: RemoteIP, req: web::Json<PasswordLoginQue | |||||||
|     let user = match users_service::get_by_mail(&req.mail).await { |     let user = match users_service::get_by_mail(&req.mail).await { | ||||||
|         Ok(u) => u, |         Ok(u) => u, | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             log::error!("Auth failed: could not find account by mail! {e}"); |             log::error!("Auth failed: could not find account by mail! {}", e); | ||||||
|             rate_limiter_service::record_action(remote_ip.0, RatedAction::FailedPasswordLogin) |             rate_limiter_service::record_action(remote_ip.0, RatedAction::FailedPasswordLogin) | ||||||
|                 .await?; |                 .await?; | ||||||
|             return Ok(HttpResponse::Unauthorized().json("Invalid credentials")); |             return Ok(HttpResponse::Unauthorized().json("Invalid credentials")); | ||||||
|   | |||||||
| @@ -1,12 +1,12 @@ | |||||||
| use crate::controllers::HttpResult; |  | ||||||
| use crate::controllers::members_controller::RequestDate; | use crate::controllers::members_controller::RequestDate; | ||||||
|  | use crate::controllers::HttpResult; | ||||||
| use crate::extractors::couple_extractor::FamilyAndCoupleInPath; | use crate::extractors::couple_extractor::FamilyAndCoupleInPath; | ||||||
| use crate::extractors::family_extractor::FamilyInPath; | use crate::extractors::family_extractor::FamilyInPath; | ||||||
| use crate::models::{Couple, CoupleState, MemberID, PhotoID}; | use crate::models::{Couple, CoupleState, MemberID, PhotoID}; | ||||||
| use crate::services::{couples_service, members_service, photos_service}; | use crate::services::{couples_service, members_service, photos_service}; | ||||||
| use actix_multipart::form::MultipartForm; |  | ||||||
| use actix_multipart::form::tempfile::TempFile; | use actix_multipart::form::tempfile::TempFile; | ||||||
| use actix_web::{HttpResponse, web}; | use actix_multipart::form::MultipartForm; | ||||||
|  | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| serde_with::with_prefix!(prefix_wedding "wedding_"); | serde_with::with_prefix!(prefix_wedding "wedding_"); | ||||||
| serde_with::with_prefix!(prefix_divorce "divorce_"); | serde_with::with_prefix!(prefix_divorce "divorce_"); | ||||||
| @@ -48,20 +48,23 @@ impl CoupleRequest { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if let Some(husband) = self.husband |         if let Some(husband) = self.husband { | ||||||
|             && !members_service::exists(couple.family_id(), husband).await? { |             if !members_service::exists(couple.family_id(), husband).await? { | ||||||
|                 return Err(CoupleControllerErr::HusbandNotExisting.into()); |                 return Err(CoupleControllerErr::HusbandNotExisting.into()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if let Some(d) = &self.wedding |         if let Some(d) = &self.wedding { | ||||||
|             && !d.check() { |             if !d.check() { | ||||||
|                 return Err(CoupleControllerErr::MalformedDateOfWedding.into()); |                 return Err(CoupleControllerErr::MalformedDateOfWedding.into()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if let Some(d) = &self.divorce |         if let Some(d) = &self.divorce { | ||||||
|             && !d.check() { |             if !d.check() { | ||||||
|                 return Err(CoupleControllerErr::MalformedDateOfDivorce.into()); |                 return Err(CoupleControllerErr::MalformedDateOfDivorce.into()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         couple.set_wife(self.wife); |         couple.set_wife(self.wife); | ||||||
|         couple.set_husband(self.husband); |         couple.set_husband(self.husband); | ||||||
|   | |||||||
| @@ -1,14 +1,14 @@ | |||||||
| use crate::connections::s3_connection; | use crate::connections::s3_connection; | ||||||
| use crate::constants; | use crate::constants; | ||||||
| use crate::controllers::HttpResult; |  | ||||||
| use crate::controllers::couples_controller::CoupleRequest; | use crate::controllers::couples_controller::CoupleRequest; | ||||||
| use crate::controllers::members_controller::MemberRequest; | use crate::controllers::members_controller::MemberRequest; | ||||||
|  | use crate::controllers::HttpResult; | ||||||
| use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership}; | use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership}; | ||||||
| use crate::models::{CoupleID, MemberID, PhotoID}; | use crate::models::{CoupleID, MemberID, PhotoID}; | ||||||
| use crate::services::photos_service::UploadedFile; | use crate::services::photos_service::UploadedFile; | ||||||
| use crate::services::{couples_service, members_service, photos_service}; | use crate::services::{couples_service, members_service, photos_service}; | ||||||
| use actix_multipart::form::MultipartForm; |  | ||||||
| use actix_multipart::form::tempfile::TempFile; | use actix_multipart::form::tempfile::TempFile; | ||||||
|  | use actix_multipart::form::MultipartForm; | ||||||
| use actix_web::HttpResponse; | use actix_web::HttpResponse; | ||||||
| use mime_guess::Mime; | use mime_guess::Mime; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| @@ -183,9 +183,9 @@ pub async fn import_family( | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if let Err(e) = req_member_data.to_member(member).await { |         if let Err(e) = req_member_data.to_member(member).await { | ||||||
|             log::error!("Error while processing import (member {req_id:?}) - {e}"); |             log::error!("Error while processing import (member {:?}) - {e}", req_id); | ||||||
|             return Ok( |             return Ok( | ||||||
|                 HttpResponse::BadRequest().json(format!("Failed to validate member {req_id:?}!")) |                 HttpResponse::BadRequest().json(format!("Failed to validate member {:?}!", req_id)) | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| use crate::constants::{FAMILY_INVITATION_CODE_LEN, StaticConstraints}; | use crate::constants::{StaticConstraints, FAMILY_INVITATION_CODE_LEN}; | ||||||
| use crate::controllers::HttpResult; | use crate::controllers::HttpResult; | ||||||
| use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership}; | use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership}; | ||||||
| use crate::models::{FamilyMembership, UserID}; | use crate::models::{FamilyMembership, UserID}; | ||||||
| @@ -7,7 +7,7 @@ use crate::services::rate_limiter_service::RatedAction; | |||||||
| use crate::services::{families_service, rate_limiter_service}; | use crate::services::{families_service, rate_limiter_service}; | ||||||
| use crate::utils::string_utils::rand_str; | use crate::utils::string_utils::rand_str; | ||||||
| use actix_remote_ip::RemoteIP; | use actix_remote_ip::RemoteIP; | ||||||
| use actix_web::{HttpResponse, web}; | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| #[derive(Debug, serde::Deserialize)] | #[derive(Debug, serde::Deserialize)] | ||||||
| pub struct CreateFamilyReq { | pub struct CreateFamilyReq { | ||||||
|   | |||||||
| @@ -5,9 +5,9 @@ use crate::extractors::member_extractor::FamilyAndMemberInPath; | |||||||
| use crate::models::{Member, MemberID, PhotoID, Sex}; | use crate::models::{Member, MemberID, PhotoID, Sex}; | ||||||
| use crate::services::{members_service, photos_service}; | use crate::services::{members_service, photos_service}; | ||||||
| use crate::utils::countries_utils; | use crate::utils::countries_utils; | ||||||
| use actix_multipart::form::MultipartForm; |  | ||||||
| use actix_multipart::form::tempfile::TempFile; | use actix_multipart::form::tempfile::TempFile; | ||||||
| use actix_web::{HttpResponse, web}; | use actix_multipart::form::MultipartForm; | ||||||
|  | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| serde_with::with_prefix!(prefix_birth "birth_"); | serde_with::with_prefix!(prefix_birth "birth_"); | ||||||
| serde_with::with_prefix!(prefix_death "death_"); | serde_with::with_prefix!(prefix_death "death_"); | ||||||
| @@ -95,10 +95,11 @@ fn check_opt_str_val( | |||||||
|     c: SizeConstraint, |     c: SizeConstraint, | ||||||
|     err: MemberControllerErr, |     err: MemberControllerErr, | ||||||
| ) -> anyhow::Result<()> { | ) -> anyhow::Result<()> { | ||||||
|     if let Some(v) = val |     if let Some(v) = val { | ||||||
|         && !c.validate(v) { |         if !c.validate(v) { | ||||||
|             return Err(err.into()); |             return Err(err.into()); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -150,10 +151,11 @@ impl MemberRequest { | |||||||
|             MemberControllerErr::MalformedEmailAddress, |             MemberControllerErr::MalformedEmailAddress, | ||||||
|         )?; |         )?; | ||||||
|  |  | ||||||
|         if let Some(mail) = &self.email |         if let Some(mail) = &self.email { | ||||||
|             && !mailchecker::is_valid(mail) { |             if !mailchecker::is_valid(mail) { | ||||||
|                 return Err(MemberControllerErr::InvalidEmailAddress.into()); |                 return Err(MemberControllerErr::InvalidEmailAddress.into()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         check_opt_str_val( |         check_opt_str_val( | ||||||
|             &self.phone, |             &self.phone, | ||||||
| @@ -185,20 +187,23 @@ impl MemberRequest { | |||||||
|             MemberControllerErr::MalformedCountry, |             MemberControllerErr::MalformedCountry, | ||||||
|         )?; |         )?; | ||||||
|  |  | ||||||
|         if let Some(c) = &self.country |         if let Some(c) = &self.country { | ||||||
|             && !countries_utils::is_code_valid(c) { |             if !countries_utils::is_code_valid(c) { | ||||||
|                 return Err(MemberControllerErr::InvalidCountryCode.into()); |                 return Err(MemberControllerErr::InvalidCountryCode.into()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if let Some(d) = &self.birth |         if let Some(d) = &self.birth { | ||||||
|             && !d.check() { |             if !d.check() { | ||||||
|                 return Err(MemberControllerErr::MalformedDateOfBirth.into()); |                 return Err(MemberControllerErr::MalformedDateOfBirth.into()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if let Some(d) = &self.death |         if let Some(d) = &self.death { | ||||||
|             && !d.check() { |             if !d.check() { | ||||||
|                 return Err(MemberControllerErr::MalformedDateOfDeath.into()); |                 return Err(MemberControllerErr::MalformedDateOfDeath.into()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         check_opt_str_val( |         check_opt_str_val( | ||||||
|             &self.note, |             &self.note, | ||||||
| @@ -216,10 +221,11 @@ impl MemberRequest { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if let Some(father) = self.father |         if let Some(father) = self.father { | ||||||
|             && !members_service::exists(member.family_id(), father).await? { |             if !members_service::exists(member.family_id(), father).await? { | ||||||
|                 return Err(MemberControllerErr::FatherNotExisting.into()); |                 return Err(MemberControllerErr::FatherNotExisting.into()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         member.first_name = self.first_name; |         member.first_name = self.first_name; | ||||||
|         member.last_name = self.last_name; |         member.last_name = self.last_name; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| //! # API controller | //! # API controller | ||||||
|  |  | ||||||
| use actix_web::HttpResponse; |  | ||||||
| use actix_web::body::BoxBody; | use actix_web::body::BoxBody; | ||||||
|  | use actix_web::HttpResponse; | ||||||
| use std::fmt::{Debug, Display, Formatter}; | use std::fmt::{Debug, Display, Formatter}; | ||||||
| use zip::result::ZipError; | use zip::result::ZipError; | ||||||
|  |  | ||||||
| @@ -31,7 +31,7 @@ impl Display for HttpErr { | |||||||
|  |  | ||||||
| impl actix_web::error::ResponseError for HttpErr { | impl actix_web::error::ResponseError for HttpErr { | ||||||
|     fn error_response(&self) -> HttpResponse<BoxBody> { |     fn error_response(&self) -> HttpResponse<BoxBody> { | ||||||
|         log::error!("Error while processing request! {self}"); |         log::error!("Error while processing request! {}", self); | ||||||
|         HttpResponse::InternalServerError().body("Failed to execute request!") |         HttpResponse::InternalServerError().body("Failed to execute request!") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ use crate::controllers::HttpResult; | |||||||
| use crate::models::PhotoID; | use crate::models::PhotoID; | ||||||
| use crate::services::photos_service; | use crate::services::photos_service; | ||||||
| use actix_web::http::header; | use actix_web::http::header; | ||||||
| use actix_web::{HttpRequest, HttpResponse, web}; | use actix_web::{web, HttpRequest, HttpResponse}; | ||||||
| use std::ops::Add; | use std::ops::Add; | ||||||
| use std::time::{Duration, UNIX_EPOCH}; | use std::time::{Duration, UNIX_EPOCH}; | ||||||
|  |  | ||||||
| @@ -36,15 +36,16 @@ async fn get_photo(id: &PhotoIdPath, full_size: bool, req: HttpRequest) -> HttpR | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     // Check if an upload is un-necessary |     // Check if an upload is un-necessary | ||||||
|     if let Some(c) = req.headers().get(header::IF_NONE_MATCH) |     if let Some(c) = req.headers().get(header::IF_NONE_MATCH) { | ||||||
|         && c.to_str().unwrap_or("") == hash { |         if c.to_str().unwrap_or("") == hash { | ||||||
|             return Ok(HttpResponse::NotModified().finish()); |             return Ok(HttpResponse::NotModified().finish()); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if let Some(c) = req.headers().get(header::IF_MODIFIED_SINCE) { |     if let Some(c) = req.headers().get(header::IF_MODIFIED_SINCE) { | ||||||
|         let date_str = c.to_str().unwrap_or(""); |         let date_str = c.to_str().unwrap_or(""); | ||||||
|         if let Ok(date) = httpdate::parse_http_date(date_str) |         if let Ok(date) = httpdate::parse_http_date(date_str) { | ||||||
|             && date |             if date | ||||||
|                 .add(Duration::from_secs(1)) |                 .add(Duration::from_secs(1)) | ||||||
|                 .duration_since(UNIX_EPOCH) |                 .duration_since(UNIX_EPOCH) | ||||||
|                 .unwrap() |                 .unwrap() | ||||||
| @@ -53,6 +54,7 @@ async fn get_photo(id: &PhotoIdPath, full_size: bool, req: HttpRequest) -> HttpR | |||||||
|             { |             { | ||||||
|                 return Ok(HttpResponse::NotModified().finish()); |                 return Ok(HttpResponse::NotModified().finish()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let bytes = s3_connection::get_file(&match full_size { |     let bytes = s3_connection::get_file(&match full_size { | ||||||
|   | |||||||
| @@ -9,8 +9,8 @@ use crate::services::login_token_service::LoginToken; | |||||||
| use crate::services::rate_limiter_service::RatedAction; | use crate::services::rate_limiter_service::RatedAction; | ||||||
| use crate::services::{rate_limiter_service, users_service}; | use crate::services::{rate_limiter_service, users_service}; | ||||||
| use actix_remote_ip::RemoteIP; | use actix_remote_ip::RemoteIP; | ||||||
| use actix_web::HttpResponse; |  | ||||||
| use actix_web::web::Json; | use actix_web::web::Json; | ||||||
|  | use actix_web::HttpResponse; | ||||||
|  |  | ||||||
| #[derive(serde::Serialize)] | #[derive(serde::Serialize)] | ||||||
| struct UserAPI<'a> { | struct UserAPI<'a> { | ||||||
|   | |||||||
| @@ -75,7 +75,7 @@ impl FromRequest for FamilyAndAccommodationInPath { | |||||||
|             Self::load_accommodation_from_path(family, accommodation_id) |             Self::load_accommodation_from_path(family, accommodation_id) | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| { |                 .map_err(|e| { | ||||||
|                     log::error!("Failed to extract accommodation ID from URL! {e}"); |                     log::error!("Failed to extract accommodation ID from URL! {}", e); | ||||||
|                     actix_web::error::ErrorNotFound("Could not fetch accommodation information!") |                     actix_web::error::ErrorNotFound("Could not fetch accommodation information!") | ||||||
|                 }) |                 }) | ||||||
|         }) |         }) | ||||||
|   | |||||||
| @@ -85,7 +85,7 @@ impl FromRequest for FamilyAndAccommodationReservationCalendarInPath { | |||||||
|             Self::load_calendar_from_path(family, accommodation_id) |             Self::load_calendar_from_path(family, accommodation_id) | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| { |                 .map_err(|e| { | ||||||
|                     log::error!("Failed to extract calendar ID from URL! {e}"); |                     log::error!("Failed to extract calendar ID from URL! {}", e); | ||||||
|                     actix_web::error::ErrorNotFound("Could not fetch calendar information!") |                     actix_web::error::ErrorNotFound("Could not fetch calendar information!") | ||||||
|                 }) |                 }) | ||||||
|         }) |         }) | ||||||
|   | |||||||
| @@ -95,7 +95,7 @@ impl FromRequest for FamilyAndAccommodationReservationInPath { | |||||||
|             Self::load_accommodation_reservation_from_path(family, reservation_id) |             Self::load_accommodation_reservation_from_path(family, reservation_id) | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| { |                 .map_err(|e| { | ||||||
|                     log::error!("Failed to extract accommodation ID from URL! {e}"); |                     log::error!("Failed to extract accommodation ID from URL! {}", e); | ||||||
|                     actix_web::error::ErrorNotFound("Could not fetch accommodation information!") |                     actix_web::error::ErrorNotFound("Could not fetch accommodation information!") | ||||||
|                 }) |                 }) | ||||||
|         }) |         }) | ||||||
|   | |||||||
| @@ -71,7 +71,7 @@ impl FromRequest for FamilyAndCoupleInPath { | |||||||
|             FamilyAndCoupleInPath::load_couple_from_path(family, couple_id) |             FamilyAndCoupleInPath::load_couple_from_path(family, couple_id) | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| { |                 .map_err(|e| { | ||||||
|                     log::error!("Failed to extract couple ID from URL! {e}"); |                     log::error!("Failed to extract couple ID from URL! {}", e); | ||||||
|                     actix_web::error::ErrorNotFound("Could not fetch couple information!") |                     actix_web::error::ErrorNotFound("Could not fetch couple information!") | ||||||
|                 }) |                 }) | ||||||
|         }) |         }) | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ impl FromRequest for FamilyInPath { | |||||||
|             FamilyInPath::load_family_from_path(&token, family_id) |             FamilyInPath::load_family_from_path(&token, family_id) | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| { |                 .map_err(|e| { | ||||||
|                     log::error!("Failed to extract family ID from URL! {e}"); |                     log::error!("Failed to extract family ID from URL! {}", e); | ||||||
|                     actix_web::error::ErrorNotFound("Could not fetch family information!") |                     actix_web::error::ErrorNotFound("Could not fetch family information!") | ||||||
|                 }) |                 }) | ||||||
|         }) |         }) | ||||||
|   | |||||||
| @@ -71,7 +71,7 @@ impl FromRequest for FamilyAndMemberInPath { | |||||||
|             FamilyAndMemberInPath::load_member_from_path(family, member_id) |             FamilyAndMemberInPath::load_member_from_path(family, member_id) | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| { |                 .map_err(|e| { | ||||||
|                     log::error!("Failed to extract member ID from URL! {e}"); |                     log::error!("Failed to extract member ID from URL! {}", e); | ||||||
|                     actix_web::error::ErrorNotFound("Could not fetch member information!") |                     actix_web::error::ErrorNotFound("Could not fetch member information!") | ||||||
|                 }) |                 }) | ||||||
|         }) |         }) | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ use actix_cors::Cors; | |||||||
| use actix_multipart::form::tempfile::TempFileConfig; | use actix_multipart::form::tempfile::TempFileConfig; | ||||||
| use actix_remote_ip::RemoteIPConfig; | use actix_remote_ip::RemoteIPConfig; | ||||||
| use actix_web::middleware::Logger; | use actix_web::middleware::Logger; | ||||||
| use actix_web::{App, HttpServer, web}; | use actix_web::{web, App, HttpServer}; | ||||||
| use geneit_backend::app_config::AppConfig; | use geneit_backend::app_config::AppConfig; | ||||||
| use geneit_backend::connections::{db_connection, s3_connection}; | use geneit_backend::connections::{db_connection, s3_connection}; | ||||||
| use geneit_backend::controllers::{ | use geneit_backend::controllers::{ | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ impl User { | |||||||
|             .as_deref() |             .as_deref() | ||||||
|             .map(|hash| { |             .map(|hash| { | ||||||
|                 bcrypt::verify(password, hash).unwrap_or_else(|e| { |                 bcrypt::verify(password, hash).unwrap_or_else(|e| { | ||||||
|                     log::error!("Failed to validate password! {e}"); |                     log::error!("Failed to validate password! {}", e); | ||||||
|                     false |                     false | ||||||
|                 }) |                 }) | ||||||
|             }) |             }) | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ use crate::utils::string_utils::rand_str; | |||||||
| use crate::utils::time_utils::time; | use crate::utils::time_utils::time; | ||||||
| use actix_web::dev::Payload; | use actix_web::dev::Payload; | ||||||
| use actix_web::{FromRequest, HttpRequest}; | use actix_web::{FromRequest, HttpRequest}; | ||||||
| use std::future::{Ready, ready}; | use std::future::{ready, Ready}; | ||||||
|  |  | ||||||
| #[derive(thiserror::Error, Debug)] | #[derive(thiserror::Error, Debug)] | ||||||
| enum LoginTokenServiceError { | enum LoginTokenServiceError { | ||||||
| @@ -125,13 +125,13 @@ async fn load_token_info(token: &LoginTokenValue) -> anyhow::Result<Option<Login | |||||||
|     let token = match user_tokens.iter_mut().find(|t| t.key == key) { |     let token = match user_tokens.iter_mut().find(|t| t.key == key) { | ||||||
|         Some(t) => t, |         Some(t) => t, | ||||||
|         None => { |         None => { | ||||||
|             log::error!("Could not find token for key '{key}' (missing token)"); |             log::error!("Could not find token for key '{}' (missing token)", key); | ||||||
|             return Ok(None); |             return Ok(None); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     if token.is_expired() { |     if token.is_expired() { | ||||||
|         log::error!("Could not find token for key '{key}' (token expired)"); |         log::error!("Could not find token for key '{}' (token expired)", key); | ||||||
|         return Ok(None); |         return Ok(None); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -169,7 +169,7 @@ impl FromRequest for LoginToken { | |||||||
|  |  | ||||||
|             let token = match load_token_info(&token).await { |             let token = match load_token_info(&token).await { | ||||||
|                 Err(e) => { |                 Err(e) => { | ||||||
|                     log::error!("Failed to load auth token! {e}"); |                     log::error!("Failed to load auth token! {}", e); | ||||||
|                     return Err(actix_web::error::ErrorPreconditionFailed( |                     return Err(actix_web::error::ErrorPreconditionFailed( | ||||||
|                         "Failed to check auth token!", |                         "Failed to check auth token!", | ||||||
|                     )); |                     )); | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ pub async fn send_mail<D: Display>(to: &str, subject: &str, body: D) -> anyhow:: | |||||||
|     let mailer = mailer.build(); |     let mailer = mailer.build(); | ||||||
|  |  | ||||||
|     mailer.send(&email)?; |     mailer.send(&email)?; | ||||||
|     log::debug!("A mail was sent to {to} (subject = {subject})"); |     log::debug!("A mail was sent to {} (subject = {})", to, subject); | ||||||
|  |  | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,8 +3,8 @@ use crate::models::{FamilyID, Member, MemberID, NewMember}; | |||||||
| use crate::schema::members; | use crate::schema::members; | ||||||
| use crate::services::{couples_service, photos_service}; | use crate::services::{couples_service, photos_service}; | ||||||
| use crate::utils::time_utils::time; | use crate::utils::time_utils::time; | ||||||
| use diesel::RunQueryDsl; |  | ||||||
| use diesel::prelude::*; | use diesel::prelude::*; | ||||||
|  | use diesel::RunQueryDsl; | ||||||
|  |  | ||||||
| /// Create a new family member | /// Create a new family member | ||||||
| pub async fn create(family_id: FamilyID) -> anyhow::Result<Member> { | pub async fn create(family_id: FamilyID) -> anyhow::Result<Member> { | ||||||
| @@ -149,10 +149,11 @@ pub mod loop_detection { | |||||||
|  |  | ||||||
|     impl LoopStack<'_> { |     impl LoopStack<'_> { | ||||||
|         pub fn contains(&self, id: MemberID) -> bool { |         pub fn contains(&self, id: MemberID) -> bool { | ||||||
|             if let Some(ls) = &self.prev |             if let Some(ls) = &self.prev { | ||||||
|                 && ls.contains(id) { |                 if ls.contains(id) { | ||||||
|                     return true; |                     return true; | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|             self.curr == id |             self.curr == id | ||||||
|         } |         } | ||||||
| @@ -167,7 +168,7 @@ pub mod loop_detection { | |||||||
|             None => false, |             None => false, | ||||||
|             Some(id) => { |             Some(id) => { | ||||||
|                 if curr_stack.contains(id) { |                 if curr_stack.contains(id) { | ||||||
|                     log::debug!("Loop detected! {curr_stack:?}"); |                     log::debug!("Loop detected! {:?}", curr_stack); | ||||||
|                     return true; |                     return true; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| @@ -187,10 +188,7 @@ pub mod loop_detection { | |||||||
|         let member = match members.get(&curr_stack.curr) { |         let member = match members.get(&curr_stack.curr) { | ||||||
|             Some(m) => m, |             Some(m) => m, | ||||||
|             None => { |             None => { | ||||||
|                 log::warn!( |                 log::warn!("Member {:?} not found in the tree for loop detection, this should never happen!", curr_stack.curr); | ||||||
|                     "Member {:?} not found in the tree for loop detection, this should never happen!", |  | ||||||
|                     curr_stack.curr |  | ||||||
|                 ); |  | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|   | |||||||
| @@ -64,7 +64,7 @@ fn redis_key(state: &str) -> String { | |||||||
|     format!("oidc-state-{state}") |     format!("oidc-state-{state}") | ||||||
| } | } | ||||||
|  |  | ||||||
| async fn load_provider_info(prov_id: &str) -> anyhow::Result<OpenIDClient<'_>> { | async fn load_provider_info(prov_id: &str) -> anyhow::Result<OpenIDClient> { | ||||||
|     let prov = AppConfig::get() |     let prov = AppConfig::get() | ||||||
|         .openid_providers() |         .openid_providers() | ||||||
|         .into_iter() |         .into_iter() | ||||||
|   | |||||||
| @@ -6,8 +6,8 @@ use crate::utils::crypt_utils::sha512; | |||||||
| use crate::utils::time_utils::time; | use crate::utils::time_utils::time; | ||||||
| use actix_multipart::form::tempfile::TempFile; | use actix_multipart::form::tempfile::TempFile; | ||||||
| use diesel::prelude::*; | use diesel::prelude::*; | ||||||
| use image::ImageFormat; |  | ||||||
| use image::imageops::FilterType; | use image::imageops::FilterType; | ||||||
|  | use image::ImageFormat; | ||||||
| use mime_guess::Mime; | use mime_guess::Mime; | ||||||
| use std::fs::File; | use std::fs::File; | ||||||
| use std::io::{Cursor, Read, Seek, Write}; | use std::io::{Cursor, Read, Seek, Write}; | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ use crate::utils::string_utils::rand_str; | |||||||
| use crate::utils::time_utils::time; | use crate::utils::time_utils::time; | ||||||
| use bcrypt::DEFAULT_COST; | use bcrypt::DEFAULT_COST; | ||||||
| use diesel::prelude::*; | use diesel::prelude::*; | ||||||
|  | use std::io::ErrorKind; | ||||||
|  |  | ||||||
| /// Get the information of a user, by its id | /// Get the information of a user, by its id | ||||||
| pub async fn get_by_id(id: UserID) -> anyhow::Result<User> { | pub async fn get_by_id(id: UserID) -> anyhow::Result<User> { | ||||||
| @@ -24,7 +25,8 @@ pub async fn get_by_mail(mail: &str) -> anyhow::Result<User> { | |||||||
| /// Get the information of a user, by its password reset token | /// Get the information of a user, by its password reset token | ||||||
| pub async fn get_by_pwd_reset_token(token: &str) -> anyhow::Result<User> { | pub async fn get_by_pwd_reset_token(token: &str) -> anyhow::Result<User> { | ||||||
|     if token.is_empty() { |     if token.is_empty() { | ||||||
|         return Err(anyhow::Error::from(std::io::Error::other( |         return Err(anyhow::Error::from(std::io::Error::new( | ||||||
|  |             ErrorKind::Other, | ||||||
|             "Token is empty!", |             "Token is empty!", | ||||||
|         ))); |         ))); | ||||||
|     } |     } | ||||||
| @@ -44,7 +46,8 @@ pub async fn get_by_pwd_reset_token(token: &str) -> anyhow::Result<User> { | |||||||
| /// Get the information of a user, by its account deletion token | /// Get the information of a user, by its account deletion token | ||||||
| pub async fn get_by_account_delete_token(token: &str) -> anyhow::Result<User> { | pub async fn get_by_account_delete_token(token: &str) -> anyhow::Result<User> { | ||||||
|     if token.is_empty() { |     if token.is_empty() { | ||||||
|         return Err(anyhow::Error::from(std::io::Error::other( |         return Err(anyhow::Error::from(std::io::Error::new( | ||||||
|  |             ErrorKind::Other, | ||||||
|             "Token is empty!", |             "Token is empty!", | ||||||
|         ))); |         ))); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ pub fn sha256(bytes: &[u8]) -> String { | |||||||
|     let mut hasher = Sha256::new(); |     let mut hasher = Sha256::new(); | ||||||
|     hasher.update(bytes); |     hasher.update(bytes); | ||||||
|     let h = hasher.finalize(); |     let h = hasher.finalize(); | ||||||
|     format!("{h:x}") |     format!("{:x}", h) | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Compute hash of a slice of bytes (sha512) | /// Compute hash of a slice of bytes (sha512) | ||||||
| @@ -13,5 +13,5 @@ pub fn sha512(bytes: &[u8]) -> String { | |||||||
|     let mut hasher = Sha512::new(); |     let mut hasher = Sha512::new(); | ||||||
|     hasher.update(bytes); |     hasher.update(bytes); | ||||||
|     let h = hasher.finalize(); |     let h = hasher.finalize(); | ||||||
|     format!("{h:x}") |     format!("{:x}", h) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| use base64::{Engine as _, engine::general_purpose}; | use base64::{engine::general_purpose, Engine as _}; | ||||||
|  |  | ||||||
| /// Encode a base64 string | /// Encode a base64 string | ||||||
| pub fn base64_enc<T: AsRef<[u8]>>(b: T) -> String { | pub fn base64_enc<T: AsRef<[u8]>>(b: T) -> String { | ||||||
|   | |||||||
| @@ -1,6 +1,11 @@ | |||||||
| use rand::distr::{Alphanumeric, SampleString}; | use rand::distributions::Alphanumeric; | ||||||
|  | use rand::Rng; | ||||||
|  |  | ||||||
| /// Generate a random string of a given length | /// Generate a random string of a given size | ||||||
| pub fn rand_str(len: usize) -> String { | pub fn rand_str(len: usize) -> String { | ||||||
|     Alphanumeric.sample_string(&mut rand::rng(), len) |     rand::thread_rng() | ||||||
|  |         .sample_iter(&Alphanumeric) | ||||||
|  |         .map(char::from) | ||||||
|  |         .take(len) | ||||||
|  |         .collect() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,3 +1,9 @@ | |||||||
| { | { | ||||||
|   "extends": ["local>renovate/presets"] |   "$schema": "https://docs.renovatebot.com/renovate-schema.json", | ||||||
|  |   "packageRules": [ | ||||||
|  |     { | ||||||
|  |       "matchUpdateTypes": ["minor", "patch"], | ||||||
|  |       "automerge": true | ||||||
|  |     } | ||||||
|  |   ] | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user