├── CHANGELOG.md ├── apps ├── api │ ├── .env │ ├── .yarnrc.yml │ ├── @types │ │ └── express.d.ts │ ├── .eslintrc.json │ ├── tsconfig.build.json │ ├── nest-cli.json │ ├── src │ │ ├── utils │ │ │ ├── normalizeString.ts │ │ │ ├── loadCommands.ts │ │ │ ├── executeCommands.ts │ │ │ └── runCommand.ts │ │ ├── prisma │ │ │ ├── prisma.module.ts │ │ │ └── prisma.service.ts │ │ ├── systeminfo │ │ │ ├── systeminfo.module.ts │ │ │ ├── systeminfo.controller.ts │ │ │ └── systeminfo.service.ts │ │ ├── auth.ts │ │ ├── simulations │ │ │ ├── simulation.types.ts │ │ │ └── simulation.module.ts │ │ ├── username.guard.ts │ │ ├── main.ts │ │ ├── app.module.ts │ │ └── multer.config.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── static │ │ └── mdp │ │ │ ├── ions.mdp │ │ │ ├── PME_cg_em.mdp │ │ │ └── PME_em.mdp │ └── package.json └── web │ ├── .env │ ├── locales │ ├── en-US.ts │ ├── server.ts │ └── client.ts │ ├── .yarnrc.yml │ ├── app │ ├── [locale] │ │ ├── globals.css │ │ ├── app │ │ │ ├── (home) │ │ │ │ ├── _components │ │ │ │ │ ├── NewSimulationButton.module.css │ │ │ │ │ ├── StepInfo │ │ │ │ │ │ ├── Step.module.css │ │ │ │ │ │ ├── StepInfo.module.css │ │ │ │ │ │ └── Step.tsx │ │ │ │ │ ├── ThreeDViewer │ │ │ │ │ │ ├── ThreeDViewer.module.css │ │ │ │ │ │ └── ThreeDViewer.tsx │ │ │ │ │ ├── ArtifactDownload.module.css │ │ │ │ │ ├── MySimulations.module.css │ │ │ │ │ ├── SimulationDetails.module.css │ │ │ │ │ ├── SimulationTabs │ │ │ │ │ │ └── SimulationTabs.module.css │ │ │ │ │ ├── SimulationCard.module.css │ │ │ │ │ ├── Log │ │ │ │ │ │ ├── Log.module.css │ │ │ │ │ │ └── RefetchTime.tsx │ │ │ │ │ └── NewSimulationButton.tsx │ │ │ │ ├── page.module.css │ │ │ │ └── page.tsx │ │ │ ├── admin │ │ │ │ ├── page.module.css │ │ │ │ ├── [systemId] │ │ │ │ │ ├── status │ │ │ │ │ │ ├── page.module.css │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── manage │ │ │ │ │ │ ├── page.module.css │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── settings │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── users │ │ │ │ │ ├── page.module.css │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── [userName] │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ └── page.module.css │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── simulations │ │ │ │ ├── about │ │ │ │ ├── page.module.css │ │ │ │ └── Summary.tsx │ │ │ │ └── new │ │ │ │ └── [simulationType] │ │ │ │ └── page.tsx │ │ ├── (public) │ │ │ ├── analytics │ │ │ │ ├── page.module.css │ │ │ │ └── page.tsx │ │ │ ├── guides │ │ │ │ ├── page.module.css │ │ │ │ └── page.tsx │ │ │ ├── auth │ │ │ │ ├── password-reset │ │ │ │ │ └── [resetId] │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── email-validation │ │ │ │ │ └── [activationId] │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── register │ │ │ │ │ ├── page.module.css │ │ │ │ │ └── page.tsx │ │ │ │ └── login │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── page.module.css │ │ │ └── page.tsx │ │ └── [...others] │ │ │ ├── NotFound.module.css │ │ │ ├── page.tsx │ │ │ └── Illustration.tsx │ ├── favicon.ico │ └── _constants │ │ ├── queries.ts │ │ └── routes.ts │ ├── assets │ ├── logo.png │ ├── unir.png │ ├── ufcspa.png │ ├── fiocruz.jpg │ ├── labioquim.png │ ├── logo_off.png │ ├── ufscpa1.png │ ├── fiocruz-ro.png │ ├── unir-white.png │ ├── epiamo-black.png │ ├── epiamo-white.png │ ├── fiocruz-white.png │ ├── visualdynamics.png │ ├── fiocruz-ro-white.png │ ├── logo_transparent.png │ ├── maintainers │ │ ├── ivo.jpg │ │ ├── rosimar.png │ │ ├── fernando.jpg │ │ └── jonathan.gif │ └── logo_off_transparent.png │ ├── .eslintrc.json │ ├── @types │ ├── user-files.d.ts │ ├── simulations.d.ts │ ├── general.d.ts │ ├── query-params.d.ts │ ├── forms.d.ts │ ├── globals.d.ts │ ├── auth.d.ts │ ├── system-information.d.ts │ ├── navigation.d.ts │ └── simulation-queue-information.d.ts │ ├── lib │ ├── apis.ts │ ├── queryClient.ts │ └── lucia.ts │ ├── components │ ├── Loader │ │ ├── CenteredLoader.module.css │ │ ├── Loader.module.css │ │ ├── CenteredLoader.tsx │ │ └── Loader.tsx │ ├── Layout │ │ ├── Shell │ │ │ ├── ServerTime │ │ │ │ ├── ServerTime.module.css │ │ │ │ └── ServerTime.tsx │ │ │ ├── Shell.module.css │ │ │ └── Footer │ │ │ │ ├── Footer.module.css │ │ │ │ └── index.tsx │ │ ├── PageLayout │ │ │ ├── PageLayout.module.css │ │ │ └── PageLayout.tsx │ │ ├── Container │ │ │ └── index.tsx │ │ └── GlobalLayout │ │ │ └── GlobalLayout.tsx │ ├── LoadingBox │ │ ├── LoadingBox.module.css │ │ └── index.tsx │ ├── Auth │ │ ├── PasswordReset │ │ │ ├── PasswordResetForm.module.css │ │ │ └── PasswordReset.module.css │ │ ├── User │ │ │ ├── User.module.css │ │ │ └── User.tsx │ │ ├── Register │ │ │ └── Register.module.css │ │ └── Login │ │ │ └── Login.module.css │ ├── VisualDynamics │ │ ├── NewSimulationForm │ │ │ ├── data │ │ │ │ ├── box-types.ts │ │ │ │ ├── water-models.ts │ │ │ │ └── force-fields.ts │ │ │ └── NewSimulationForm.module.css │ │ ├── RunningSimulation │ │ │ └── SubmissionInfo │ │ │ │ └── SubmissionInfo.module.css │ │ └── Simulations │ │ │ └── SimulationCard │ │ │ └── Download.module.css │ ├── Administration │ │ ├── Manage │ │ │ └── visualdynamics.tsx │ │ ├── Nav.module.css │ │ ├── Status │ │ │ └── visualdynamics.tsx │ │ ├── Users │ │ │ ├── BanUser.module.css │ │ │ ├── UserList.module.css │ │ │ ├── UpdateUser.module.css │ │ │ └── ResendValidationEmail.module.css │ │ ├── Simulations │ │ │ ├── Manager │ │ │ │ ├── SimulationInfo.module.css │ │ │ │ └── SimulationManagerList.module.css │ │ │ ├── SystemInfo.module.css │ │ │ ├── QueueInfo.module.css │ │ │ └── QueueInfo.tsx │ │ ├── NavSection.module.css │ │ ├── NavItem.module.css │ │ ├── NavItem.tsx │ │ ├── NavSection.tsx │ │ ├── Nav.tsx │ │ └── Settings │ │ │ └── Mode.tsx │ ├── Lander │ │ ├── BackingSection.module.css │ │ ├── Layout.tsx │ │ ├── Layout.module.css │ │ ├── DemoSection.tsx │ │ ├── FeatureCard.tsx │ │ ├── FeaturesSection.module.css │ │ ├── CallToActionSection.tsx │ │ ├── BackingSection.tsx │ │ ├── InteractiveParticles.module.css │ │ ├── HeroSection.tsx │ │ ├── Header.tsx │ │ ├── Footer.module.css │ │ ├── CallToActionSection.module.css │ │ ├── DemoSection.module.css │ │ └── Footer.tsx │ ├── Heading │ │ ├── Heading.module.css │ │ └── Heading.tsx │ ├── Logo │ │ ├── Logo.module.css │ │ └── index.tsx │ ├── FileManager │ │ └── NavCrumb.tsx │ ├── GoBackButton │ │ └── GoBackButton.tsx │ └── Alerts │ │ ├── SystemsStatus.tsx │ │ └── Alert.tsx │ ├── utils │ ├── normalizeString.ts │ └── dateFormat.ts │ ├── theme.ts │ ├── next.config.js │ ├── hooks │ ├── utils │ │ ├── useAllSettings.ts │ │ └── useSettings.ts │ ├── auth │ │ ├── useReloadAuth.ts │ │ ├── useAuth.ts │ │ └── usePasswordReset.ts │ ├── administration │ │ ├── useSimulationQueueInformation.ts │ │ ├── useSystemInformation.ts │ │ ├── useSimulation.ts │ │ ├── useSimulationsCount.ts │ │ ├── useUserFileTree.ts │ │ ├── useUserCount.ts │ │ ├── useUsers.ts │ │ └── useSimulations.ts │ ├── simulation │ │ ├── useLatestSimulations.ts │ │ ├── useSimulation.ts │ │ └── useLatestSimulationMacromolecules.ts │ └── useCountdown.ts │ ├── postcss.config.cjs │ ├── actions │ ├── auth │ │ ├── email-validation │ │ │ └── fetchEmailValidation.ts │ │ ├── getPasswordReset.ts │ │ ├── validateUserEmail.ts │ │ ├── invalidateAuth.ts │ │ ├── register.ts │ │ ├── createPasswordReset.ts │ │ ├── resetPassword.ts │ │ ├── validateAuth.ts │ │ └── login.ts │ ├── utils │ │ ├── sendMail.ts │ │ ├── getAllSettings.ts │ │ ├── updateSystemMode.ts │ │ └── getSettings.ts │ ├── administration │ │ ├── getSimulationSystemInfo.ts │ │ ├── getSimulation.ts │ │ ├── getSimulationQueueInfo.ts │ │ ├── downloadUserFile.ts │ │ ├── updateSimulation.ts │ │ ├── createUserValidation.ts │ │ ├── getSimulationsCount.ts │ │ ├── getUserCount.ts │ │ └── updateUser.ts │ └── simulation │ │ ├── getMDPFiles.ts │ │ ├── getLatestSimulations.ts │ │ ├── getGromacsLogs.ts │ │ ├── getCommandsTxt.ts │ │ ├── getFiguresZip.ts │ │ ├── getResultsZip.ts │ │ ├── getLatestSimulationMacromolecules.ts │ │ ├── getSimulation.ts │ │ └── submitNewSimulation.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── Dockerfile.dev │ ├── README.md │ ├── package.json │ └── providers │ └── Pagination.tsx ├── README.md ├── packages └── database │ ├── .env │ ├── README.md │ ├── .dockerignore │ ├── .yarnrc.yml │ ├── src │ ├── index.ts │ ├── client.ts │ ├── seed.ts │ └── utils │ │ └── crypto.ts │ ├── .gitignore │ ├── prisma │ └── migrations │ │ ├── migration_lock.toml │ │ ├── 20250418125838_add_usable_to_password_reset │ │ └── migration.sql │ │ ├── 20250418134713_user_can_have_multiple_password_resets │ │ └── migration.sql │ │ ├── 20250408211725_add_indexes │ │ └── migration.sql │ │ ├── 20250408201521_updated_at_can_be_nullable │ │ └── migration.sql │ │ ├── 20250408212108_add_indexes │ │ └── migration.sql │ │ ├── 20241103131719_adding_ligand_names │ │ └── migration.sql │ │ ├── 20241103112504_update_simulation_types │ │ └── migration.sql │ │ ├── 20250418123026_add_password_reset │ │ └── migration.sql │ │ ├── 20240323213511_add_simulations_table │ │ └── migration.sql │ │ └── 20240326192540_change_simulation_enum_names │ │ └── migration.sql │ ├── tsup.config.ts │ ├── tsconfig.json │ └── package.json ├── files └── files ├── commitlint.config.js ├── .husky └── commit-msg ├── .dockerignore ├── .yarnrc.yml ├── .gitattributes ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── feedback.md │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── release.yml ├── .gitignore ├── .release-it.json ├── .env.example ├── package.json └── compose.yml /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/.env: -------------------------------------------------------------------------------- 1 | ../../.env -------------------------------------------------------------------------------- /apps/web/.env: -------------------------------------------------------------------------------- 1 | ../../.env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VisualDynamics 2 | -------------------------------------------------------------------------------- /packages/database/.env: -------------------------------------------------------------------------------- 1 | ../../.env -------------------------------------------------------------------------------- /files/files: -------------------------------------------------------------------------------- 1 | /mnt/nfs/visualdynamics_files -------------------------------------------------------------------------------- /packages/database/README.md: -------------------------------------------------------------------------------- 1 | # database 2 | -------------------------------------------------------------------------------- /apps/web/locales/en-US.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /apps/api/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /apps/web/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /packages/database/.dockerignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules -------------------------------------------------------------------------------- /packages/database/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /packages/database/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./client"; 2 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/globals.css: -------------------------------------------------------------------------------- 1 | html { 2 | scroll-behavior: smooth; 3 | } -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | }; -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn commitlint ${1} -------------------------------------------------------------------------------- /apps/web/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/app/favicon.ico -------------------------------------------------------------------------------- /apps/web/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/logo.png -------------------------------------------------------------------------------- /apps/web/assets/unir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/unir.png -------------------------------------------------------------------------------- /apps/web/assets/ufcspa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/ufcspa.png -------------------------------------------------------------------------------- /apps/web/assets/fiocruz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/fiocruz.jpg -------------------------------------------------------------------------------- /apps/web/assets/labioquim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/labioquim.png -------------------------------------------------------------------------------- /apps/web/assets/logo_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/logo_off.png -------------------------------------------------------------------------------- /apps/web/assets/ufscpa1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/ufscpa1.png -------------------------------------------------------------------------------- /packages/database/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | !.env 4 | dist -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .yarn/cache 2 | .git 3 | .github 4 | .next 5 | dist 6 | files 7 | node_modules 8 | VDFiles 9 | 10 | -------------------------------------------------------------------------------- /apps/web/assets/fiocruz-ro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/fiocruz-ro.png -------------------------------------------------------------------------------- /apps/web/assets/unir-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/unir-white.png -------------------------------------------------------------------------------- /apps/web/assets/epiamo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/epiamo-black.png -------------------------------------------------------------------------------- /apps/web/assets/epiamo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/epiamo-white.png -------------------------------------------------------------------------------- /apps/web/assets/fiocruz-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/fiocruz-white.png -------------------------------------------------------------------------------- /apps/web/assets/visualdynamics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/visualdynamics.png -------------------------------------------------------------------------------- /apps/web/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@ivopr/eslint-config/react"], 3 | "rules": { 4 | "no-undef": "off" 5 | } 6 | } -------------------------------------------------------------------------------- /apps/web/@types/user-files.d.ts: -------------------------------------------------------------------------------- 1 | interface FileProps { 2 | path: string; 3 | name: string; 4 | children?: FileProps[]; 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/assets/fiocruz-ro-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/fiocruz-ro-white.png -------------------------------------------------------------------------------- /apps/web/assets/logo_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/logo_transparent.png -------------------------------------------------------------------------------- /apps/web/assets/maintainers/ivo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/maintainers/ivo.jpg -------------------------------------------------------------------------------- /apps/api/@types/express.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Express { 2 | export interface Request { 3 | userName?: string; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/assets/maintainers/rosimar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/maintainers/rosimar.png -------------------------------------------------------------------------------- /apps/api/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@ivopr/eslint-config/node"], 3 | "rules": { 4 | "no-useless-constructor": "off" 5 | } 6 | } -------------------------------------------------------------------------------- /apps/api/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /apps/web/assets/logo_off_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/logo_off_transparent.png -------------------------------------------------------------------------------- /apps/web/assets/maintainers/fernando.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/maintainers/fernando.jpg -------------------------------------------------------------------------------- /apps/web/assets/maintainers/jonathan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LABIOQUIM/visualdynamics/HEAD/apps/web/assets/maintainers/jonathan.gif -------------------------------------------------------------------------------- /apps/web/lib/apis.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export const api = axios.create({ 4 | baseURL: "http://api:3000/v1", 5 | }); 6 | -------------------------------------------------------------------------------- /apps/web/@types/simulations.d.ts: -------------------------------------------------------------------------------- 1 | type SimulationType = "acpype" | "apo" | "prodrg"; 2 | 3 | type StepState = "done" | "inprogress" | "waiting"; 4 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: false 4 | 5 | nodeLinker: node-modules 6 | 7 | yarnPath: .yarn/releases/yarn-4.11.0.cjs 8 | -------------------------------------------------------------------------------- /apps/web/@types/general.d.ts: -------------------------------------------------------------------------------- 1 | type ActionResponse = T | "unauthenticated"; 2 | type LABIOQUIMSystems = "visualdynamics" | "plasmoia" | "plasmoqsar"; 3 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/(home)/_components/NewSimulationButton.module.css: -------------------------------------------------------------------------------- 1 | .iconAPO { 2 | color: blue; 3 | } 4 | 5 | .iconACPYPE { 6 | color: red; 7 | } 8 | -------------------------------------------------------------------------------- /apps/web/components/Loader/CenteredLoader.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | align-items: center; 3 | display: flex; 4 | flex: 1; 5 | justify-content: center; 6 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/** linguist-vendored 2 | /.yarn/releases/* binary 3 | /.yarn/plugins/**/* binary 4 | /.pnp.* binary linguist-generated 5 | -------------------------------------------------------------------------------- /apps/web/app/_constants/queries.ts: -------------------------------------------------------------------------------- 1 | export const QueryParams = { 2 | SIMULATION_EXPANDED_DETAILS: "details", 3 | SIMULATION_EXPANDED_DETAILS_ACTIVE_TAB: "tab", 4 | } as const; 5 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /apps/web/components/Layout/Shell/ServerTime/ServerTime.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | align-items: center; 3 | display: flex; 4 | flex-direction: row; 5 | gap: var(--mantine-spacing-xs); 6 | } -------------------------------------------------------------------------------- /apps/web/components/LoadingBox/LoadingBox.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | align-items: center; 3 | display: flex; 4 | flex: 1; 5 | flex-direction: column; 6 | justify-content: center; 7 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feedback.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feedback 3 | about: Provide valuable feedback to shape whats next in LABIOQUIM 4 | title: '' 5 | labels: feedback 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /apps/web/@types/query-params.d.ts: -------------------------------------------------------------------------------- 1 | type SimulationDetailsActiveTab = 2 | | "3d-viewer" 3 | | "downloads" 4 | | "errored" 5 | | "run" 6 | | null; 7 | 8 | type SimulationDetails = "acpype" | "apo" | null; 9 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/admin/page.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: calc(100dvh - 45px - var(--app-shell-header-offset) - calc(var(--app-shell-padding) * 3)); 3 | gap: var(--mantine-spacing-md); 4 | } 5 | -------------------------------------------------------------------------------- /apps/web/@types/forms.d.ts: -------------------------------------------------------------------------------- 1 | type FormSubmissionStatus = 2 | | { status: "loading" } 3 | | { 4 | status: "info" | "error" | "warning" | "success"; 5 | message?: string; 6 | title: string; 7 | }; 8 | -------------------------------------------------------------------------------- /apps/api/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/components/Auth/PasswordReset/PasswordResetForm.module.css: -------------------------------------------------------------------------------- 1 | .formContainer { 2 | display: flex; 3 | flex-direction: column; 4 | gap: var(--mantine-spacing-md); 5 | max-width: var(--mantine-breakpoint-xs); 6 | } -------------------------------------------------------------------------------- /apps/web/components/Layout/PageLayout/PageLayout.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex: 1; 4 | flex-direction: column; 5 | gap: var(--mantine-spacing-md); 6 | position: relative; 7 | width: 100%; 8 | } -------------------------------------------------------------------------------- /apps/web/components/VisualDynamics/NewSimulationForm/data/box-types.ts: -------------------------------------------------------------------------------- 1 | export const boxTypes = { 2 | cubic: "Cubic", 3 | triclinic: "Triclinic", 4 | dodecahedron: "Dodecahedron", 5 | octahedron: "Octahedron", 6 | }; 7 | -------------------------------------------------------------------------------- /apps/web/locales/server.ts: -------------------------------------------------------------------------------- 1 | import { createI18nServer } from "next-international/server"; 2 | 3 | export const { getI18n, getScopedI18n, getStaticParams } = createI18nServer({ 4 | "en-US": () => import("./en-US"), 5 | }); 6 | -------------------------------------------------------------------------------- /apps/web/locales/client.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { createI18nClient } from "next-international/client"; 3 | 4 | export const { useI18n, useScopedI18n, I18nProviderClient } = createI18nClient({ 5 | "en-US": () => import("./en-US"), 6 | }); 7 | -------------------------------------------------------------------------------- /apps/web/lib/queryClient.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/react-query"; 2 | 3 | export const queryClient = new QueryClient({ 4 | defaultOptions: { 5 | queries: { 6 | refetchOnMount: true, 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /apps/web/utils/normalizeString.ts: -------------------------------------------------------------------------------- 1 | export function normalizeString(str: string) { 2 | return str 3 | .toLowerCase() 4 | .trim() 5 | .normalize("NFD") 6 | .replace(/[\u0300-\u036f]/g, "") 7 | .replace(/[^a-zA-Z0-9]/g, ""); 8 | } 9 | -------------------------------------------------------------------------------- /apps/api/src/utils/normalizeString.ts: -------------------------------------------------------------------------------- 1 | export function normalizeString(str: string) { 2 | return str 3 | .toLowerCase() 4 | .trim() 5 | .normalize("NFD") 6 | .replace(/[\u0300-\u036f]/g, "") 7 | .replace(/[^a-zA-Z0-9]/g, ""); 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/components/Administration/Manage/visualdynamics.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { SimulationManagerList } from "../Simulations/Manager/SimulationManagerList"; 3 | 4 | export default function Manage() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /apps/web/theme.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { createTheme } from "@mantine/core"; 4 | 5 | export const theme = createTheme({ 6 | /* Put your mantine theme override here */ 7 | focusRing: "never", 8 | primaryColor: "indigo", 9 | }); 10 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/(home)/_components/StepInfo/Step.module.css: -------------------------------------------------------------------------------- 1 | .step_container { 2 | display: flex; 3 | flex-direction: row; 4 | gap: var(--mantine-spacing-xs); 5 | } 6 | 7 | .step_icon_container { 8 | height: 24px; 9 | width: 24px; 10 | } -------------------------------------------------------------------------------- /apps/api/src/prisma/prisma.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | 3 | import { PrismaService } from "./prisma.service"; 4 | 5 | @Module({ 6 | providers: [PrismaService], 7 | exports: [PrismaService], 8 | }) 9 | export class PrismaModule {} 10 | -------------------------------------------------------------------------------- /apps/web/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: "standalone", 4 | experimental: { 5 | serverActions: { 6 | bodySizeLimit: "5mb", 7 | }, 8 | }, 9 | }; 10 | 11 | module.exports = nextConfig; 12 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250418125838_add_usable_to_password_reset/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "user_password_resets" ADD COLUMN "usable" BOOLEAN NOT NULL DEFAULT true, 3 | ALTER COLUMN "valid_until" SET DEFAULT NOW() + interval '15 min'; 4 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from "react"; 2 | 3 | import { Shell } from "@/components/Layout/Shell/Shell"; 4 | 5 | export default async function RootAppLayout({ children }: PropsWithChildren) { 6 | return {children}; 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | report.txt 3 | .env 4 | .plausible.env 5 | clickhouse 6 | __pycache__ 7 | 8 | *.h5 9 | *.hdf5 10 | 11 | *.txt 12 | *.a 13 | 14 | 15 | .yarn/* 16 | !.yarn/patches 17 | !.yarn/plugins 18 | !/.yarn/releases 19 | !.yarn/sdks 20 | !.yarn/versions 21 | -------------------------------------------------------------------------------- /apps/web/@types/globals.d.ts: -------------------------------------------------------------------------------- 1 | // src/globals.d.ts 2 | export {}; // This makes the file a module 3 | 4 | declare global { 5 | interface Window { 6 | YT: typeof YT; // This will be populated by @types/youtube 7 | onYouTubeIframeAPIReady?: () => void; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/(home)/_components/ThreeDViewer/ThreeDViewer.module.css: -------------------------------------------------------------------------------- 1 | .viewerContainer { 2 | border-radius: var(--mantine-radius-md); 3 | flex: 1; 4 | position: relative; 5 | overflow: hidden; 6 | box-shadow: var(--mantine-shadow-xs); 7 | min-height: 288px; 8 | } -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/admin/[systemId]/status/page.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | min-height: calc(100dvh - 45px - var(--app-shell-header-offset) - calc(var(--app-shell-padding) * 3)); 5 | gap: var(--mantine-spacing-md); 6 | } 7 | -------------------------------------------------------------------------------- /apps/web/components/Lander/BackingSection.module.css: -------------------------------------------------------------------------------- 1 | .backersWrapper { 2 | max-width: var(--mantine-breakpoint-lg); 3 | margin: 0 auto; 4 | } 5 | 6 | .backerImage { 7 | width: 100%; 8 | height: 100%; 9 | object-fit: contain; 10 | max-height: auto; 11 | margin: auto; 12 | } -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/(home)/_components/StepInfo/StepInfo.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: calc(var(--mantine-spacing-xs) / 2); 5 | width: fit-content; 6 | } 7 | 8 | .arrow_down_icon { 9 | color: var(--mantine-color-gray-6); 10 | } -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250418134713_user_can_have_multiple_password_resets/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "user_password_resets_user_id_key"; 3 | 4 | -- AlterTable 5 | ALTER TABLE "user_password_resets" ALTER COLUMN "valid_until" SET DEFAULT NOW() + interval '15 min'; 6 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/admin/[systemId]/manage/page.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | max-height: calc(100dvh - 45px - var(--app-shell-header-offset) - calc(var(--app-shell-padding) * 3)); 6 | gap: var(--mantine-spacing-md); 7 | } 8 | -------------------------------------------------------------------------------- /apps/web/components/Loader/Loader.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | gap: var(--mantine-spacing-xs); 4 | } 5 | 6 | .square { 7 | width: 20; 8 | height: 20; 9 | opacity: 1; 10 | border-radius: 0; 11 | display: "inline-block"; 12 | background: var(--mantine-primary-color-4); 13 | } -------------------------------------------------------------------------------- /apps/web/components/LoadingBox/index.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Loader } from "@mantine/core"; 2 | 3 | import classes from "./LoadingBox.module.css"; 4 | 5 | export function LoadingBox() { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/admin/users/page.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | max-height: calc(100dvh - 45px - var(--app-shell-header-offset) - calc(var(--app-shell-padding) * 3)); 6 | gap: var(--mantine-spacing-md); 7 | justify-content: space-between; 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/hooks/utils/useAllSettings.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | 3 | import { getAllSettings } from "@/actions/utils/getAllSettings"; 4 | 5 | export function useAllSettings() { 6 | return useQuery({ 7 | queryKey: ["all-settings"], 8 | queryFn: () => getAllSettings(), 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/database/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | const isProduction = process.env.NODE_ENV === "production"; 4 | 5 | export default defineConfig({ 6 | clean: true, 7 | dts: true, 8 | entry: ["src/index.ts"], 9 | format: ["cjs", "esm"], 10 | minify: isProduction, 11 | sourcemap: true, 12 | }); -------------------------------------------------------------------------------- /apps/web/components/VisualDynamics/NewSimulationForm/data/water-models.ts: -------------------------------------------------------------------------------- 1 | export const waterModels = { 2 | spc: "SPC simple point charge", 3 | spce: "SPC/E extended simple point charge", 4 | none: "None", 5 | tip3p: "TIP3P (AMBER e OPLS apenas)", 6 | tip4p: "TIP4P (AMBER e OPLS apenas)", 7 | tip5p: "TIP5P (AMBER e OPLS apenas)", 8 | }; 9 | -------------------------------------------------------------------------------- /apps/web/hooks/utils/useSettings.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | 3 | import { getSettings } from "@/actions/utils/getSettings"; 4 | 5 | export function useSettings(systemId: string) { 6 | return useQuery({ 7 | queryKey: ["settings", systemId], 8 | queryFn: () => getSettings(systemId), 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/database/src/client.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | declare global { 4 | var prisma: PrismaClient | undefined; 5 | } 6 | 7 | export const prisma = global.prisma || new PrismaClient(); 8 | 9 | if (process.env.NODE_ENV !== "production") global.prisma = prisma; 10 | 11 | export * from "@prisma/client"; 12 | -------------------------------------------------------------------------------- /apps/web/components/Heading/Heading.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | align-items: center; 4 | gap: var(--mantine-spacing-xs); 5 | } 6 | 7 | .icon { 8 | width: 70%; 9 | height: 70%; 10 | stroke: 1.5; 11 | } 12 | 13 | .rightElementContainer { 14 | margin-left: auto; 15 | display: flex; 16 | align-items: center; 17 | } -------------------------------------------------------------------------------- /apps/web/components/Logo/Logo.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | color: unset; 3 | display: flex; 4 | gap: var(--mantine-spacing-xs); 5 | text-decoration: none; 6 | user-select: none; 7 | } 8 | 9 | .title { 10 | font-size: var(--mantine-font-size-xl); 11 | } 12 | 13 | .titleLarge { 14 | font-size: calc(var(--mantine-font-size-xl) * 2); 15 | } -------------------------------------------------------------------------------- /apps/web/components/Administration/Nav.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: var(--mantine-spacing-md); 5 | } 6 | 7 | .section_container { 8 | display: grid; 9 | grid-template-columns: 1fr; 10 | 11 | @mixin larger-than $mantine-breakpoint-lg { 12 | grid-template-columns: repeat(1fr, 4); 13 | } 14 | } -------------------------------------------------------------------------------- /apps/web/components/Loader/CenteredLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Box } from "@mantine/core"; 3 | 4 | import { Loader } from "./Loader"; 5 | 6 | import classes from "./CenteredLoader.module.css"; 7 | 8 | export function CenteredLoader() { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/web/hooks/auth/useReloadAuth.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useCallback } from "react"; 3 | 4 | import { useAuth } from "./useAuth"; 5 | 6 | export function useReloadAuth() { 7 | const { refetch: reloadAuth } = useAuth(); 8 | 9 | const reload = useCallback(async () => { 10 | await reloadAuth(); 11 | }, [reloadAuth]); 12 | 13 | return reload; 14 | } 15 | -------------------------------------------------------------------------------- /apps/web/@types/auth.d.ts: -------------------------------------------------------------------------------- 1 | import { Session, User } from "lucia"; 2 | 3 | declare global { 4 | interface ValidateAuth { 5 | session: Session | null; 6 | user: User | null; 7 | } 8 | 9 | interface RegisterFormInputs { 10 | firstName: string; 11 | lastName: string; 12 | email: string; 13 | userName: string; 14 | password: string; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/web/components/Layout/Container/index.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from "react"; 2 | import { Box, BoxProps } from "@mantine/core"; 3 | 4 | export function Container({ 5 | className, 6 | children, 7 | ...props 8 | }: PropsWithChildren) { 9 | return ( 10 | 11 | {children} 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/systeminfo/systeminfo.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | 3 | import { SystemInfoController } from "./systeminfo.controller"; 4 | import { SystemInfoService } from "./systeminfo.service"; 5 | 6 | @Module({ 7 | imports: [], 8 | controllers: [SystemInfoController], 9 | providers: [SystemInfoService], 10 | }) 11 | export class SystemInfoModule {} 12 | -------------------------------------------------------------------------------- /apps/api/src/prisma/prisma.service.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication, Injectable } from "@nestjs/common"; 2 | import { PrismaClient } from "database"; 3 | 4 | @Injectable() 5 | export class PrismaService extends PrismaClient { 6 | async enableShutdownHooks(app: INestApplication) { 7 | // @ts-expect-error 8 | this.$on("beforeExit", async () => { 9 | await app.close(); 10 | }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250408211725_add_indexes/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateIndex 2 | CREATE INDEX "users_user_name_idx" ON "users"("user_name"); 3 | 4 | -- CreateIndex 5 | CREATE INDEX "users_email_idx" ON "users"("email"); 6 | 7 | -- CreateIndex 8 | CREATE INDEX "users_first_name_idx" ON "users"("first_name"); 9 | 10 | -- CreateIndex 11 | CREATE INDEX "users_last_name_idx" ON "users"("last_name"); 12 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/(public)/analytics/page.module.css: -------------------------------------------------------------------------------- 1 | .analyticsContainer { 2 | height: auto; 3 | width: 100%; 4 | max-width: 1280px; 5 | margin: auto; 6 | } 7 | 8 | .sectionTitle { 9 | font-family: 'Lexend', sans-serif; 10 | font-size: clamp(2.2rem, 5vw, 3.2rem); 11 | font-weight: 700; 12 | color: var(--mantine-color-black); 13 | text-align: center; 14 | margin-bottom: var(--mantine-spacing-lg); 15 | } -------------------------------------------------------------------------------- /apps/web/components/Layout/Shell/Shell.module.css: -------------------------------------------------------------------------------- 1 | .rootContainer { 2 | min-height: 100dvh; 3 | } 4 | 5 | .mainContainer { 6 | display: flex; 7 | } 8 | 9 | .footer { 10 | position: unset; 11 | display: flex; 12 | flex-direction: column; 13 | gap: var(--mantine-spacing-sm); 14 | padding: var(--mantine-spacing-xs); 15 | 16 | @mixin larger-than $mantine-breakpoint-lg { 17 | flex-direction: row; 18 | } 19 | } -------------------------------------------------------------------------------- /apps/web/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | "postcss-preset-mantine": {}, 4 | "postcss-simple-vars": { 5 | variables: { 6 | "mantine-breakpoint-xs": "36em", 7 | "mantine-breakpoint-sm": "48em", 8 | "mantine-breakpoint-md": "62em", 9 | "mantine-breakpoint-lg": "75em", 10 | "mantine-breakpoint-xl": "88em", 11 | }, 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /apps/web/@types/system-information.d.ts: -------------------------------------------------------------------------------- 1 | interface SystemInformation { 2 | cpu: { 3 | brand: string; 4 | vendor: string; 5 | cores: number; 6 | physicalCores: number; 7 | }; 8 | load: { 9 | current: number; 10 | average: number; 11 | }; 12 | mem: { 13 | total: number; 14 | used: number; 15 | }; 16 | fs: { 17 | size: number; 18 | used: number; 19 | available: number; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/components/Administration/Status/visualdynamics.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment } from "react"; 2 | 3 | import { AdminSimulationQueueInfo } from "../Simulations/QueueInfo"; 4 | import { AdminSimulationSystemInfo } from "../Simulations/SystemInfo"; 5 | 6 | export default function Status() { 7 | return ( 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/components/Layout/Shell/Footer/Footer.module.css: -------------------------------------------------------------------------------- 1 | .makers { 2 | align-items: center; 3 | display: flex; 4 | flex-direction: column; 5 | gap: var(--mantine-spacing-sm); 6 | width: 100%; 7 | 8 | @mixin larger-than $mantine-breakpoint-lg { 9 | flex-direction: row; 10 | } 11 | } 12 | 13 | .makerImage { 14 | height: 64px; 15 | width: auto; 16 | } 17 | 18 | .makerImageEpi { 19 | height: 48px; 20 | width: auto; 21 | } -------------------------------------------------------------------------------- /apps/web/components/Administration/Users/BanUser.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: var(--mantine-spacing-md); 5 | } 6 | 7 | .row_container { 8 | display: grid; 9 | grid-template-columns: 1fr 1fr; 10 | gap: var(--mantine-spacing-md) 11 | } 12 | 13 | .modalTitle { 14 | font-size: var(--mantine-font-size-xl); 15 | font-weight: bold; 16 | text-transform: uppercase; 17 | letter-spacing: 5px; 18 | } -------------------------------------------------------------------------------- /apps/api/src/auth.ts: -------------------------------------------------------------------------------- 1 | import { betterAuth } from "better-auth"; 2 | import { prismaAdapter } from "better-auth/adapters/prisma"; 3 | // If your Prisma file is located elsewhere, you can change the path 4 | import { PrismaClient } from "database"; 5 | 6 | const prisma = new PrismaClient(); 7 | 8 | export const auth = betterAuth({ 9 | database: prismaAdapter(prisma, { 10 | provider: "postgresql", // or "mysql", "postgresql", ...etc 11 | }), 12 | }); 13 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/admin/users/page.tsx: -------------------------------------------------------------------------------- 1 | import { AdministrationUserList } from "@/components/Administration/Users/UserList"; 2 | import { Heading } from "@/components/Heading/Heading"; 3 | import { PageLayout } from "@/components/Layout/PageLayout/PageLayout"; 4 | 5 | export default function Page() { 6 | return ( 7 | 8 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /apps/web/components/Administration/Simulations/Manager/SimulationInfo.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: var(--mantine-spacing-md); 5 | } 6 | 7 | .row_container { 8 | display: grid; 9 | grid-template-columns: 1fr 1fr; 10 | gap: var(--mantine-spacing-md) 11 | } 12 | 13 | .modalTitle { 14 | font-size: var(--mantine-font-size-xl); 15 | font-weight: bold; 16 | text-transform: uppercase; 17 | letter-spacing: 5px; 18 | } -------------------------------------------------------------------------------- /apps/web/hooks/administration/useSimulationQueueInformation.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | 3 | import { getSimulationQueueInfo } from "@/actions/administration/getSimulationQueueInfo"; 4 | 5 | export function useSimulationQueueInformation() { 6 | return useQuery({ 7 | queryKey: ["simulation-queue-information"], 8 | queryFn: () => getSimulationQueueInfo(), 9 | refetchInterval: 10 * 1000, 10 | staleTime: 9 * 1000, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /apps/web/hooks/administration/useSystemInformation.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | 3 | import { getSimulationSystemInfo } from "@/actions/administration/getSimulationSystemInfo"; 4 | 5 | export function useSimulationSystemInformation() { 6 | return useQuery({ 7 | queryKey: ["simulations-system-information"], 8 | queryFn: () => getSimulationSystemInfo(), 9 | refetchInterval: 10 * 1000, 10 | staleTime: 9 * 1000, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250408201521_updated_at_can_be_nullable/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `updatedAt` on the `simulations` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "simulations" DROP COLUMN "updatedAt", 9 | ADD COLUMN "updated_at" TIMESTAMP(3); 10 | 11 | -- AlterTable 12 | ALTER TABLE "user_email_validations" ALTER COLUMN "updated_at" DROP NOT NULL; 13 | -------------------------------------------------------------------------------- /apps/web/actions/auth/email-validation/fetchEmailValidation.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { prisma } from "database"; 3 | 4 | export async function fetchEmailValidation(activationId: string) { 5 | return await prisma.userEmailValidation.findFirst({ 6 | where: { 7 | id: activationId, 8 | }, 9 | include: { 10 | user: { 11 | select: { 12 | firstName: true, 13 | email: true, 14 | }, 15 | }, 16 | }, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /apps/web/actions/utils/sendMail.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import axios from "axios"; 3 | 4 | export async function sendMail(to: string, html: string, subject: string) { 5 | try { 6 | await axios.post("http://mailer:3000/send-email", { 7 | from: `LABIOQUIM <${process.env.SMTP_USER}>`, 8 | to, 9 | subject, 10 | html, 11 | }); 12 | 13 | return "success"; 14 | } catch (e) { 15 | console.log("sentMail: " + e); 16 | return "failure"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/web/components/VisualDynamics/RunningSimulation/SubmissionInfo/SubmissionInfo.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: grid; 3 | grid-auto-flow: column; 4 | grid-template-rows: repeat(2, 1fr); 5 | gap: calc(var(--mantine-spacing-xs) / 2); 6 | min-height: 71px; 7 | } 8 | 9 | .text_container { 10 | display: flex; 11 | flex-direction: column; 12 | 13 | @media (min-width: $mantine-breakpoint-xl) { 14 | flex-direction: row; 15 | gap: var(--mantine-spacing-xs); 16 | } 17 | } -------------------------------------------------------------------------------- /apps/web/hooks/auth/useAuth.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useQuery, 3 | UseQueryOptions, 4 | UseQueryResult, 5 | } from "@tanstack/react-query"; 6 | 7 | import { validateAuth } from "@/actions/auth/validateAuth"; 8 | 9 | export function useAuth( 10 | options?: UseQueryOptions 11 | ): UseQueryResult { 12 | return useQuery({ 13 | queryKey: ["user-auth"], 14 | queryFn: () => validateAuth(), 15 | ...options, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "requireBranch": "main", 4 | "commitMessage": "chore: release v${version}" 5 | }, 6 | "github": { 7 | "release": true 8 | }, 9 | "plugins": { 10 | "@release-it/bumper": { 11 | "out": ["apps/ui/package.json", "packages/database/package.json"] 12 | }, 13 | "@release-it/conventional-changelog": { 14 | "infile": "CHANGELOG.md", 15 | "preset": { 16 | "name": "conventionalcommits" 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /apps/api/src/simulations/simulation.types.ts: -------------------------------------------------------------------------------- 1 | import { SIMULATION_TYPE, User } from "database"; 2 | 3 | export interface NewSimulationBody { 4 | forceField: string; 5 | waterModel: string; 6 | boxType: string; 7 | boxDistance: string; 8 | successEmail: string; 9 | errorEmail: string; 10 | shouldRun?: "true" | "false"; 11 | } 12 | 13 | export interface SimulateData { 14 | simulationId: string; 15 | user: User; 16 | type: SIMULATION_TYPE; 17 | successEmail: string; 18 | errorEmail: string; 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/actions/auth/getPasswordReset.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { prisma } from "database"; 4 | 5 | export async function getPasswordReset(id: string) { 6 | const passwordReset = await prisma.userPasswordReset.findFirst({ 7 | where: { 8 | id, 9 | }, 10 | include: { 11 | user: { 12 | select: { 13 | id: true, 14 | }, 15 | }, 16 | }, 17 | }); 18 | 19 | if (!passwordReset) { 20 | return "no-reset"; 21 | } 22 | 23 | return passwordReset; 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/actions/utils/getAllSettings.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { prisma } from "database"; 3 | 4 | import { validateAuth } from "../auth/validateAuth"; 5 | 6 | export async function getAllSettings() { 7 | const { user } = await validateAuth(); 8 | 9 | if (!user) { 10 | return "unauthenticated"; 11 | } 12 | 13 | try { 14 | const settings = await prisma.settings.findMany(); 15 | 16 | return settings; 17 | } catch (e) { 18 | console.log("getAllSettings: " + e); 19 | return "error"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/actions/administration/getSimulationSystemInfo.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { api } from "@/lib/apis"; 3 | 4 | import { validateAuth } from "../auth/validateAuth"; 5 | 6 | export async function getSimulationSystemInfo() { 7 | const { user } = await validateAuth(); 8 | 9 | if (!user) { 10 | return "unauthenticated"; 11 | } 12 | 13 | const response = await api.get("/systeminfo", { 14 | headers: { 15 | "x-username": user.userName, 16 | }, 17 | }); 18 | 19 | return response.data; 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/actions/auth/validateUserEmail.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { prisma } from "database"; 3 | 4 | export async function validateUserEmail(validationCode: string) { 5 | try { 6 | await prisma.userEmailValidation.update({ 7 | where: { 8 | id: validationCode, 9 | }, 10 | data: { 11 | used: true, 12 | user: { 13 | update: { 14 | status: "ACTIVE", 15 | }, 16 | }, 17 | }, 18 | }); 19 | 20 | return true; 21 | } catch { 22 | return false; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/components/VisualDynamics/Simulations/SimulationCard/Download.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | flex: 1; 3 | overflow: hidden; 4 | } 5 | 6 | .inner { 7 | justify-content: flex-start; 8 | } 9 | 10 | .label { 11 | font-size: var(--mantine-font-size-lg); 12 | 13 | @media (min-width: $mantine-breakpoint-md) { 14 | font-size: calc(var(--mantine-font-size-xl)); 15 | } 16 | } 17 | 18 | .bg_icon { 19 | position: absolute; 20 | right: var(--mantine-spacing-md); 21 | top: -var(--mantine-spacing-md); 22 | color: var(--mantine-primary-color-3); 23 | } -------------------------------------------------------------------------------- /apps/api/src/systeminfo/systeminfo.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, UseGuards } from "@nestjs/common"; 2 | import { UsernameGuard } from "src/username.guard"; 3 | 4 | import { SystemInfoService } from "./systeminfo.service"; 5 | 6 | @Controller("systemInfo") 7 | export class SystemInfoController { 8 | constructor(private systemInfoService: SystemInfoService) {} 9 | 10 | @UseGuards(UsernameGuard) 11 | @Get("/") 12 | async getCPUInfo() { 13 | const systemInfo = await this.systemInfoService.getSystemInfo(); 14 | 15 | return systemInfo; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/(home)/_components/ArtifactDownload.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | flex: 1; 3 | overflow: hidden; 4 | min-height: 60px; 5 | } 6 | 7 | .inner { 8 | justify-content: flex-start; 9 | } 10 | 11 | .label { 12 | font-size: var(--mantine-font-size-lg); 13 | 14 | @media (min-width: $mantine-breakpoint-md) { 15 | font-size: calc(var(--mantine-font-size-xl)); 16 | } 17 | } 18 | 19 | .bg_icon { 20 | position: absolute; 21 | right: var(--mantine-spacing-md); 22 | top: -var(--mantine-spacing-md); 23 | color: var(--mantine-primary-color-3); 24 | } -------------------------------------------------------------------------------- /apps/api/src/utils/loadCommands.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | 4 | export async function loadCommands(folder: string): Promise { 5 | const fileCommandsPath = path.join(folder, "commands.txt"); 6 | try { 7 | const data = await fs.promises.readFile(fileCommandsPath, "utf-8"); 8 | return data.split(/\r?\n/); // Split on newline characters (including CR+LF) 9 | } catch (err) { 10 | console.error("Error reading commands file:", err); 11 | throw err; // Re-throw the error for handling in the calling code 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | !.env 3 | 4 | # dependencies 5 | node_modules 6 | /.pnp 7 | .pnp.js 8 | .yarn/install-state.gz 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | .next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /apps/web/actions/simulation/getMDPFiles.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { api } from "@/lib/apis"; 3 | 4 | import { validateAuth } from "../auth/validateAuth"; 5 | 6 | export async function getMDPFiles() { 7 | const { user } = await validateAuth(); 8 | 9 | if (!user) { 10 | return "unauthenticated"; 11 | } 12 | 13 | const response = await api.get(`/simulation/downloads/mdp`, { 14 | headers: { 15 | "x-username": user.userName, 16 | }, 17 | responseType: "arraybuffer", 18 | }); 19 | 20 | return Buffer.from(response.data).toString("base64"); 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/actions/auth/invalidateAuth.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { cookies } from "next/headers"; 3 | 4 | import { validateAuth } from "@/actions/auth/validateAuth"; 5 | import { lucia } from "@/lib/lucia"; 6 | 7 | export async function invalidateAuth() { 8 | const { session } = await validateAuth(); 9 | 10 | if (!session) { 11 | return { 12 | error: "Unauthorized", 13 | }; 14 | } 15 | 16 | await lucia.invalidateSession(session.id); 17 | 18 | const sessionCookie = lucia.createBlankSessionCookie(); 19 | (await cookies()).delete(sessionCookie.name); 20 | } 21 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250408212108_add_indexes/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateIndex 2 | CREATE INDEX "simulations_user_id_idx" ON "simulations"("user_id"); 3 | 4 | -- CreateIndex 5 | CREATE INDEX "simulations_molecule_name_idx" ON "simulations"("molecule_name"); 6 | 7 | -- CreateIndex 8 | CREATE INDEX "simulations_ligand_itp_name_idx" ON "simulations"("ligand_itp_name"); 9 | 10 | -- CreateIndex 11 | CREATE INDEX "simulations_ligand_pdb_name_idx" ON "simulations"("ligand_pdb_name"); 12 | 13 | -- CreateIndex 14 | CREATE INDEX "simulations_type_idx" ON "simulations"("type"); 15 | -------------------------------------------------------------------------------- /apps/web/actions/administration/getSimulation.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { prisma } from "database"; 4 | 5 | import { validateAuth } from "../auth/validateAuth"; 6 | 7 | export async function getSimulation(id: string) { 8 | const { user } = await validateAuth(); 9 | 10 | if (!user) { 11 | return "unauthenticated"; 12 | } 13 | 14 | if (user.role !== "ADMINISTRATOR") { 15 | return "unauthorized"; 16 | } 17 | 18 | const simulation = await prisma.simulation.findFirst({ 19 | where: { 20 | id, 21 | }, 22 | }); 23 | 24 | return simulation; 25 | } 26 | -------------------------------------------------------------------------------- /apps/api/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | dist 3 | node_modules 4 | report.txt 5 | !.env 6 | *.zip 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | pnpm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | lerna-debug.log* 16 | 17 | # OS 18 | .DS_Store 19 | 20 | # Tests 21 | /coverage 22 | /.nyc_output 23 | 24 | # IDEs and editors 25 | /.idea 26 | .project 27 | .classpath 28 | .c9/ 29 | *.launch 30 | .settings/ 31 | *.sublime-workspace 32 | 33 | # IDE - VSCode 34 | .vscode/* 35 | !.vscode/settings.json 36 | !.vscode/tasks.json 37 | !.vscode/launch.json 38 | !.vscode/extensions.json -------------------------------------------------------------------------------- /apps/web/actions/administration/getSimulationQueueInfo.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { api } from "@/lib/apis"; 3 | 4 | import { validateAuth } from "../auth/validateAuth"; 5 | 6 | export async function getSimulationQueueInfo() { 7 | const { user } = await validateAuth(); 8 | 9 | if (!user) { 10 | return "unauthenticated"; 11 | } 12 | 13 | const response = await api.get( 14 | "/simulation/queue-info", 15 | { 16 | headers: { 17 | "x-username": user.userName, 18 | }, 19 | } 20 | ); 21 | 22 | return response.data; 23 | } 24 | -------------------------------------------------------------------------------- /apps/web/@types/navigation.d.ts: -------------------------------------------------------------------------------- 1 | import { MantineColor } from "@mantine/core"; 2 | import type { Icon } from "@tabler/icons-react"; 3 | import type { USER_ROLE } from "database"; 4 | 5 | declare global { 6 | interface NavLink { 7 | icon: Icon; 8 | label: string; 9 | href: string; 10 | external?: boolean; 11 | disabled?: boolean; 12 | role?: USER_ROLE; 13 | badge?: { 14 | color: MantineColor; 15 | message: string; 16 | }; 17 | } 18 | 19 | interface NavSection { 20 | title: string; 21 | links: NavLink[]; 22 | disabled?: boolean; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/(home)/_components/MySimulations.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: grid; 3 | gap: var(--mantine-spacing-md); 4 | grid-template-columns: repeat(1, 1fr); 5 | 6 | @media (min-width: $mantine-breakpoint-lg) { 7 | grid-template-columns: repeat(2, 1fr); 8 | } 9 | } 10 | 11 | .containerDownOrMaintenance { 12 | display: flex; 13 | flex: 1; 14 | flex-direction: column; 15 | gap: var(--mantine-spacing-md); 16 | box-shadow: var(--mantine-shadow-xs); 17 | border-radius: var(--mantine-radius-md); 18 | align-items: center; 19 | justify-content: center; 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/hooks/auth/usePasswordReset.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useQuery, 3 | UseQueryOptions, 4 | UseQueryResult, 5 | } from "@tanstack/react-query"; 6 | 7 | import { getPasswordReset } from "@/actions/auth/getPasswordReset"; 8 | 9 | type Return = Awaited>; 10 | 11 | export function usePasswordReset( 12 | id: string, 13 | options?: UseQueryOptions 14 | ): UseQueryResult { 15 | return useQuery({ 16 | queryKey: ["password-reset", id], 17 | queryFn: () => getPasswordReset(id), 18 | ...options, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/admin/page.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@mantine/core"; 2 | 3 | import { AdministrationNav } from "@/components/Administration/Nav"; 4 | import { Heading } from "@/components/Heading/Heading"; 5 | import { PageLayout } from "@/components/Layout/PageLayout/PageLayout"; 6 | 7 | import classes from "./page.module.css"; 8 | 9 | export default function AdministrationPage() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/simulations/about/page.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: grid; 3 | gap: var(--mantine-spacing-md); 4 | grid-template-columns: repeat(1, 1fr); 5 | 6 | @media (min-width: $mantine-breakpoint-md) { 7 | grid-template-columns: 1fr 2fr; 8 | } 9 | } 10 | 11 | .containerText { 12 | display: flex; 13 | flex-direction: column; 14 | gap: var(--mantine-spacing-md); 15 | text-align: justify; 16 | } 17 | 18 | .image { 19 | height: auto; 20 | width: 100%; 21 | } 22 | 23 | .listItem { 24 | width: calc(100% - 21px); 25 | word-wrap: break-word; 26 | white-space: pre-wrap; 27 | } -------------------------------------------------------------------------------- /apps/web/components/Administration/NavSection.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: var(--mantine-spacing-xs); 5 | } 6 | 7 | .links_container { 8 | display: grid; 9 | grid-template-columns: 1fr; 10 | gap: var(--mantine-spacing-sm); 11 | 12 | @mixin larger-than $mantine-breakpoint-lg { 13 | grid-template-columns: repeat(2, 1fr); 14 | } 15 | } 16 | 17 | .section_title { 18 | color: var(--mantine-color-dark-3); 19 | font-size: var(--mantine-font-size-sm); 20 | font-weight: bold; 21 | padding-left: var(--mantine-spacing-md); 22 | user-select: none; 23 | } -------------------------------------------------------------------------------- /apps/web/app/[locale]/(public)/guides/page.module.css: -------------------------------------------------------------------------------- 1 | .videosContainer { 2 | display: grid; 3 | grid-template-columns: repeat(1, 1fr); 4 | gap: var(--mantine-spacing-xs); 5 | padding-left: var(--mantine-spacing-md); 6 | padding-right: var(--mantine-spacing-md); 7 | 8 | @media (min-width: $mantine-breakpoint-md) { 9 | grid-template-columns: repeat(2, 1fr); 10 | } 11 | } 12 | 13 | .sectionTitle { 14 | font-family: 'Lexend', sans-serif; 15 | font-size: clamp(2.2rem, 5vw, 3.2rem); 16 | font-weight: 700; 17 | color: var(--mantine-color-black); 18 | text-align: center; 19 | margin-bottom: var(--mantine-spacing-lg); 20 | } -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/(home)/_components/SimulationDetails.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | flex: 1; 5 | gap: var(--mantine-spacing-md); 6 | box-shadow: var(--mantine-shadow-xs); 7 | border-radius: var(--mantine-radius-md); 8 | } 9 | 10 | .noSelectionContainer { 11 | align-items: center; 12 | justify-content: center; 13 | } 14 | 15 | .headingContainer { 16 | display: grid; 17 | grid-template-columns: repeat(1, 1fr); 18 | 19 | @media (min-width: $mantine-breakpoint-md) { 20 | grid-template-columns: repeat(2, 1fr); 21 | gap: var(--mantine-spacing-xs); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20241103131719_adding_ligand_names/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `errored_on_command` on the `simulations` table. All the data in the column will be lost. 5 | - Added the required column `updatedAt` to the `simulations` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "simulations" DROP COLUMN "errored_on_command", 10 | ADD COLUMN "error_cause" TEXT, 11 | ADD COLUMN "ligand_itp_name" TEXT, 12 | ADD COLUMN "ligand_pdb_name" TEXT, 13 | ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL; 14 | -------------------------------------------------------------------------------- /apps/web/actions/utils/updateSystemMode.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { prisma, SYSTEM_MODE } from "database"; 3 | 4 | import { validateAuth } from "../auth/validateAuth"; 5 | 6 | export async function updateSystemMode(systemId: string, systemMode: string) { 7 | const { user } = await validateAuth(); 8 | 9 | if (!user) { 10 | return "unauthenticated"; 11 | } 12 | 13 | try { 14 | await prisma.settings.update({ 15 | where: { 16 | systemId, 17 | }, 18 | data: { 19 | systemMode: systemMode as SYSTEM_MODE, 20 | }, 21 | }); 22 | 23 | return "success"; 24 | } catch { 25 | return "error"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/(home)/page.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column !important; 4 | 5 | @media (min-width: $mantine-breakpoint-md) { 6 | flex-direction: row !important; 7 | } 8 | } 9 | 10 | .simulationsContainer { 11 | display: flex; 12 | flex-direction: column; 13 | flex: 1; 14 | gap: var(--mantine-spacing-lg); 15 | 16 | @media (min-width: $mantine-breakpoint-md) { 17 | min-width: 384px; 18 | width: 384px; 19 | max-width: 384px; 20 | } 21 | } 22 | 23 | .expandedDetailsContainer { 24 | display: flex; 25 | flex-direction: column; 26 | flex: 1; 27 | gap: var(--mantine-spacing-lg); 28 | } -------------------------------------------------------------------------------- /apps/web/components/Layout/GlobalLayout/GlobalLayout.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { PropsWithChildren } from "react"; 3 | import { ProgressProvider } from "@bprogress/next/app"; 4 | import { QueryClientProvider } from "@tanstack/react-query"; 5 | 6 | import { queryClient } from "@/lib/queryClient"; 7 | 8 | export function GlobalLayout({ children }: PropsWithChildren) { 9 | return ( 10 | 16 | {children} 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20241103112504_update_simulation_types/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The values [ACPYPE,APO,PRODRG] on the enum `SIMULATION_TYPE` will be removed. If these variants are still used in the database, this will fail. 5 | 6 | */ 7 | -- AlterEnum 8 | BEGIN; 9 | CREATE TYPE "SIMULATION_TYPE_new" AS ENUM ('acpype', 'apo'); 10 | ALTER TABLE "simulations" ALTER COLUMN "type" TYPE "SIMULATION_TYPE_new" USING ("type"::text::"SIMULATION_TYPE_new"); 11 | ALTER TYPE "SIMULATION_TYPE" RENAME TO "SIMULATION_TYPE_old"; 12 | ALTER TYPE "SIMULATION_TYPE_new" RENAME TO "SIMULATION_TYPE"; 13 | DROP TYPE "SIMULATION_TYPE_old"; 14 | COMMIT; 15 | -------------------------------------------------------------------------------- /apps/web/components/Administration/NavItem.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | align-items: center; 3 | background: var(--mantine-color-gray-0); 4 | display: flex; 5 | flex-direction: column; 6 | gap: var(--mantine-spacing-xs); 7 | height: 128px; 8 | justify-content: center; 9 | transition: all 0.2s linear; 10 | 11 | &:hover { 12 | opacity: 0.7; 13 | } 14 | } 15 | 16 | .content { 17 | color: var(--mantine-primary-color-4); 18 | } 19 | 20 | .content_label { 21 | color: var(--mantine-primary-color-4); 22 | text-decoration: none; 23 | } 24 | 25 | .disabled { 26 | opacity: 0.5; 27 | cursor: not-allowed; 28 | 29 | &:hover { 30 | opacity: 0.5; 31 | } 32 | } -------------------------------------------------------------------------------- /apps/web/components/Administration/Users/UserList.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | overflow: auto; 3 | flex: 1; 4 | } 5 | 6 | .table { 7 | flex: 1; 8 | } 9 | 10 | .loading_container { 11 | margin: auto; 12 | } 13 | 14 | .td { 15 | text-overflow: ellipsis; 16 | 17 | /* Needed to make it work */ 18 | overflow: hidden; 19 | white-space: nowrap; 20 | 21 | width: fit-content; 22 | 23 | @mixin larger-than $mantine-breakpoint-md { 24 | max-width: calc((100dvw - var(--app-shell-navbar-offset) - 32px) / 7); 25 | } 26 | } 27 | 28 | .actions_container { 29 | align-items: center; 30 | display: flex; 31 | flex-direction: row; 32 | gap: var(--mantine-spacing-xs); 33 | } -------------------------------------------------------------------------------- /apps/web/components/Auth/User/User.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | text-decoration: none; 3 | } 4 | 5 | .user { 6 | display: block; 7 | width: 100%; 8 | padding: var(--mantine-spacing-md); 9 | color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0)); 10 | 11 | cursor: default; 12 | 13 | height: 70px; 14 | } 15 | 16 | .signin { 17 | display: block; 18 | width: 100%; 19 | padding: var(--mantine-spacing-md); 20 | color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0)); 21 | 22 | cursor: pointer; 23 | 24 | @mixin hover { 25 | background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-8)); 26 | } 27 | } -------------------------------------------------------------------------------- /packages/database/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "composite": false, 5 | "declaration": true, 6 | "declarationMap": true, 7 | "inlineSources": false, 8 | "isolatedModules": true, 9 | "moduleResolution": "node", 10 | "noUnusedLocals": false, 11 | "noUnusedParameters": false, 12 | "preserveWatchOutput": true, 13 | "lib": ["ES2021"], 14 | "module": "commonjs", 15 | "target": "ES2021", 16 | "strict": true, 17 | "esModuleInterop": true, 18 | "skipLibCheck": true, 19 | "forceConsistentCasingInFileNames": true 20 | }, 21 | "exclude": ["node_modules"] 22 | } -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/(home)/_components/SimulationTabs/SimulationTabs.module.css: -------------------------------------------------------------------------------- 1 | .tabsContainer { 2 | display: flex; 3 | flex-direction: column; 4 | flex: 1; 5 | } 6 | 7 | .tabsPanelContainer { 8 | display: flex; 9 | flex-direction: column; 10 | flex: 1; 11 | padding-top: var(--mantine-spacing-md); 12 | } 13 | 14 | .tabsPanelDownload { 15 | display: flex; 16 | flex-direction: column; 17 | gap: var(--mantine-spacing-md); 18 | } 19 | 20 | .tabsPanelRun { 21 | display: flex; 22 | flex-direction: column; 23 | flex: 1; 24 | gap: var(--mantine-spacing-md); 25 | 26 | @media screen and (min-width: $mantine-breakpoint-md) { 27 | flex-direction: row !important; 28 | } 29 | } -------------------------------------------------------------------------------- /apps/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false, 20 | "paths": { 21 | "@/*": ["*"] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/actions/administration/downloadUserFile.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { api } from "@/lib/apis"; 3 | 4 | import { validateAuth } from "../auth/validateAuth"; 5 | 6 | export async function downloadUserFile(path: string) { 7 | const { user } = await validateAuth(); 8 | 9 | if (!user) { 10 | return "unauthenticated"; 11 | } 12 | 13 | if (user.role !== "ADMINISTRATOR") { 14 | return "unauthorized"; 15 | } 16 | 17 | const response = await api.get(`/simulation/download/file?path=${path}`, { 18 | headers: { 19 | "x-username": user.userName, 20 | }, 21 | responseType: "arraybuffer", 22 | }); 23 | 24 | return Buffer.from(response.data).toString("base64"); 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/actions/simulation/getLatestSimulations.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { Simulation, SIMULATION_TYPE } from "database"; 3 | 4 | import { api } from "@/lib/apis"; 5 | 6 | import { validateAuth } from "../auth/validateAuth"; 7 | 8 | export type LatestSimulations = { 9 | [key in SIMULATION_TYPE]: Simulation | null; 10 | }; 11 | 12 | export async function getLatestSimulations() { 13 | const { user } = await validateAuth(); 14 | 15 | if (!user) { 16 | return "unauthenticated"; 17 | } 18 | 19 | const response = await api.get("/simulation/latest", { 20 | headers: { 21 | "x-username": user.userName, 22 | }, 23 | }); 24 | 25 | return response.data; 26 | } 27 | -------------------------------------------------------------------------------- /apps/web/components/Auth/Register/Register.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | text-decoration: none; 3 | } 4 | 5 | .formContainer { 6 | display: flex; 7 | flex-direction: column; 8 | gap: var(--mantine-spacing-md); 9 | } 10 | 11 | .user { 12 | display: block; 13 | width: 100%; 14 | padding: var(--mantine-spacing-md); 15 | color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0)); 16 | 17 | @mixin hover { 18 | background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-8)); 19 | } 20 | } 21 | 22 | .modalTitle { 23 | font-size: var(--mantine-font-size-xl); 24 | font-weight: bold; 25 | text-transform: uppercase; 26 | letter-spacing: 5px; 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/hooks/administration/useSimulation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useQuery, 3 | UseQueryOptions, 4 | UseQueryResult, 5 | } from "@tanstack/react-query"; 6 | 7 | import { getSimulation } from "@/actions/administration/getSimulation"; 8 | 9 | type Return = Awaited>; 10 | 11 | export function useSimulation( 12 | id: string, 13 | options?: UseQueryOptions 14 | ): UseQueryResult { 15 | return useQuery({ 16 | queryKey: ["simulation", id], 17 | enabled: false, 18 | queryFn: () => getSimulation(id), 19 | refetchInterval: 10 * 60 * 1000, 20 | staleTime: 10 * 60 * 1000, 21 | ...options, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /apps/web/components/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@mantine/core"; 2 | import Image from "next/image"; 3 | import Link from "next/link"; 4 | 5 | import LogoImage from "@/assets/visualdynamics.svg"; 6 | 7 | import classes from "./Logo.module.css"; 8 | 9 | interface Props { 10 | size?: "normal" | "large"; 11 | } 12 | 13 | export function Logo({ size = "normal" }: Props) { 14 | const height = { 15 | normal: 48, 16 | large: 96, 17 | }; 18 | return ( 19 | 20 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /apps/web/@types/simulation-queue-information.d.ts: -------------------------------------------------------------------------------- 1 | interface SimulationQueueInformation { 2 | active: number; 3 | failed: number; 4 | paused: number; 5 | delayed: number; 6 | waiting: number; 7 | completed: number; 8 | jobs: { 9 | id: string; 10 | data: { 11 | simulationId: string; 12 | type: string; 13 | }; 14 | opts: { 15 | attempts: number; 16 | delay: number; 17 | timestamp: number; 18 | }; 19 | progress: number; 20 | delay: number; 21 | timestamp: number; 22 | attemptsMade: number; 23 | stacktrace: []; 24 | returnvalue: string; 25 | debounceId: null; 26 | finishedOn: number; 27 | processedOn: number; 28 | }[]; 29 | } 30 | -------------------------------------------------------------------------------- /apps/web/components/FileManager/NavCrumb.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Fragment } from "react"; 4 | import { Text, UnstyledButton } from "@mantine/core"; 5 | 6 | import classes from "./FileManager.module.css"; 7 | 8 | interface Props { 9 | crumb: string; 10 | isLastCrumb: boolean; 11 | onClick: () => void; 12 | } 13 | 14 | export function NavCrumb({ crumb, isLastCrumb, onClick }: Props) { 15 | return ( 16 | 17 | 18 | {crumb} 19 | 20 | {!isLastCrumb &&
/
} 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /apps/web/actions/simulation/getGromacsLogs.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { SIMULATION_TYPE } from "database"; 3 | 4 | import { api } from "@/lib/apis"; 5 | 6 | import { validateAuth } from "../auth/validateAuth"; 7 | 8 | export async function getGromacsLogs(simulationType: SIMULATION_TYPE) { 9 | const { user } = await validateAuth(); 10 | 11 | if (!user) { 12 | return "unauthenticated"; 13 | } 14 | 15 | const response = await api.get( 16 | `/simulation/downloads/logs?type=${simulationType}`, 17 | { 18 | headers: { 19 | "x-username": user.userName, 20 | }, 21 | responseType: "arraybuffer", 22 | } 23 | ); 24 | 25 | return Buffer.from(response.data).toString("base64"); 26 | } 27 | -------------------------------------------------------------------------------- /apps/web/actions/simulation/getCommandsTxt.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { SIMULATION_TYPE } from "database"; 3 | 4 | import { api } from "@/lib/apis"; 5 | 6 | import { validateAuth } from "../auth/validateAuth"; 7 | 8 | export async function getCommandsTxt(simulationType: SIMULATION_TYPE) { 9 | const { user } = await validateAuth(); 10 | 11 | if (!user) { 12 | return "unauthenticated"; 13 | } 14 | 15 | const response = await api.get( 16 | `/simulation/downloads/commands?type=${simulationType}`, 17 | { 18 | headers: { 19 | "x-username": user.userName, 20 | }, 21 | responseType: "arraybuffer", 22 | } 23 | ); 24 | 25 | return Buffer.from(response.data).toString("base64"); 26 | } 27 | -------------------------------------------------------------------------------- /apps/web/actions/simulation/getFiguresZip.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { SIMULATION_TYPE } from "database"; 3 | 4 | import { api } from "@/lib/apis"; 5 | 6 | import { validateAuth } from "../auth/validateAuth"; 7 | 8 | export async function getFiguresZip(simulationType: SIMULATION_TYPE) { 9 | const { user } = await validateAuth(); 10 | 11 | if (!user) { 12 | return "unauthenticated"; 13 | } 14 | 15 | const response = await api.get( 16 | `/simulation/downloads/figures?type=${simulationType}`, 17 | { 18 | headers: { 19 | "x-username": user.userName, 20 | }, 21 | responseType: "arraybuffer", 22 | } 23 | ); 24 | 25 | return Buffer.from(response.data).toString("base64"); 26 | } 27 | -------------------------------------------------------------------------------- /apps/web/actions/simulation/getResultsZip.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { SIMULATION_TYPE } from "database"; 3 | 4 | import { api } from "@/lib/apis"; 5 | 6 | import { validateAuth } from "../auth/validateAuth"; 7 | 8 | export async function getResultsZip(simulationType: SIMULATION_TYPE) { 9 | const { user } = await validateAuth(); 10 | 11 | if (!user) { 12 | return "unauthenticated"; 13 | } 14 | 15 | const response = await api.get( 16 | `/simulation/downloads/results?type=${simulationType}`, 17 | { 18 | headers: { 19 | "x-username": user.userName, 20 | }, 21 | responseType: "arraybuffer", 22 | } 23 | ); 24 | 25 | return Buffer.from(response.data).toString("base64"); 26 | } 27 | -------------------------------------------------------------------------------- /apps/web/actions/auth/register.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { argon2id, hash } from "argon2"; 3 | import { prisma } from "database"; 4 | 5 | import { normalizeString } from "@/utils/normalizeString"; 6 | 7 | export async function register(data: RegisterFormInputs) { 8 | try { 9 | data.userName = normalizeString(data.userName); 10 | 11 | data.password = await hash(data.password, { type: argon2id }); 12 | 13 | const user = await prisma.user.create({ 14 | data, 15 | }); 16 | 17 | return user; 18 | } catch (e: any) { 19 | if (e && e.code && e.code === "P2002") { 20 | return "existing-user"; 21 | } 22 | 23 | console.log("Register: " + e); 24 | 25 | return "unknown-error"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/(public)/auth/password-reset/[resetId]/page.tsx: -------------------------------------------------------------------------------- 1 | import { PasswordResetForm } from "@/components/Auth/PasswordReset/PasswordResetForm"; 2 | import { Heading } from "@/components/Heading/Heading"; 3 | import { LanderLayout } from "@/components/Lander/Layout"; 4 | 5 | interface Props { 6 | params: Promise<{ 7 | resetId: string; 8 | }>; 9 | } 10 | 11 | export default async function AccountActivationPage({ params }: Props) { 12 | const { resetId } = await params; 13 | 14 | if (!resetId) { 15 | return null; 16 | } 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/components/Administration/Simulations/SystemInfo.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: grid; 3 | background: var(--mantine-color-gray-0); 4 | grid-template-columns: 1fr; 5 | height: fit-content; 6 | padding: var(--mantine-spacing-md); 7 | 8 | @mixin larger-than $mantine-breakpoint-lg { 9 | grid-template-columns: repeat(3, 1fr); 10 | } 11 | } 12 | 13 | .info_container { 14 | align-items: center; 15 | display: flex; 16 | flex-direction: column; 17 | gap: var(--mantine-spacing-sm); 18 | } 19 | 20 | .info_container { 21 | align-items: center; 22 | display: flex; 23 | flex-direction: column; 24 | gap: var(--mantine-spacing-sm); 25 | } 26 | 27 | .info_value { 28 | font-size: var(--mantine-font-size-xl); 29 | } -------------------------------------------------------------------------------- /apps/web/hooks/administration/useSimulationsCount.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useQuery, 3 | UseQueryOptions, 4 | UseQueryResult, 5 | } from "@tanstack/react-query"; 6 | 7 | import { getSimulationsCount } from "@/actions/administration/getSimulationsCount"; 8 | 9 | type Return = Awaited>; 10 | 11 | export function useSimulationsCount( 12 | queryText?: string, 13 | options?: UseQueryOptions 14 | ): UseQueryResult { 15 | return useQuery({ 16 | queryKey: ["simulations-count", queryText], 17 | queryFn: () => getSimulationsCount(queryText), 18 | refetchInterval: 10 * 60 * 1000, 19 | staleTime: 10 * 60 * 1000, 20 | ...options, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /apps/web/components/GoBackButton/GoBackButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { ActionIcon } from "@mantine/core"; 3 | import { IconArrowLeft } from "@tabler/icons-react"; 4 | import Link from "next/link"; 5 | import { useRouter } from "next/navigation"; 6 | 7 | interface Props { 8 | to?: string; 9 | } 10 | 11 | export function GoBackButton({ to }: Props) { 12 | const router = useRouter(); 13 | 14 | if (!to) { 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/admin/[systemId]/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import { Title } from "@mantine/core"; 2 | 3 | import { SystemMode } from "@/components/Administration/Settings/Mode"; 4 | import { GoBackButton } from "@/components/GoBackButton/GoBackButton"; 5 | import { PageLayout } from "@/components/Layout/PageLayout/PageLayout"; 6 | 7 | interface Props { 8 | params: Promise<{ 9 | systemId: string; 10 | }>; 11 | } 12 | 13 | export default async function SystemSettingsPage({ params }: Props) { 14 | const systemId = (await params).systemId; 15 | 16 | return ( 17 | 18 | 19 | Settings for {systemId.toUpperCase()} 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /apps/web/hooks/administration/useUserFileTree.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useQuery, 3 | UseQueryOptions, 4 | UseQueryResult, 5 | } from "@tanstack/react-query"; 6 | 7 | import { getUserFileTree } from "@/actions/administration/getUserFileTree"; 8 | 9 | type Return = Awaited>; 10 | 11 | export function useUserFileTree( 12 | userName: string, 13 | options?: UseQueryOptions 14 | ): UseQueryResult { 15 | return useQuery({ 16 | queryKey: ["user-tree", userName], 17 | queryFn: () => getUserFileTree(userName), 18 | staleTime: 60 * 1000, 19 | refetchInterval: 60 * 1000, 20 | refetchOnMount: true, 21 | refetchOnWindowFocus: true, 22 | ...options, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/(public)/page.tsx: -------------------------------------------------------------------------------- 1 | import { LanderBackingSection } from "@/components/Lander/BackingSection"; 2 | import { LanderCallToActionSection } from "@/components/Lander/CallToActionSection"; 3 | import { LanderFeaturesSection } from "@/components/Lander/FeaturesSection"; 4 | import { LanderHeroSection } from "@/components/Lander/HeroSection"; 5 | import { LanderLayout } from "@/components/Lander/Layout"; 6 | 7 | export default function HomePage() { 8 | return ( 9 | 10 | 11 | 12 | {/* */} 13 | 14 | 15 | {/* Add other sections as needed */} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/admin/users/[userName]/page.tsx: -------------------------------------------------------------------------------- 1 | import { PageLayout } from "@/components/Layout/PageLayout/PageLayout"; 2 | 3 | import { UserFiles } from "./UserFiles"; 4 | 5 | import classes from "./page.module.css"; 6 | 7 | interface Props { 8 | params: Promise<{ userName: string }>; 9 | } 10 | 11 | export async function generateMetadata({ params }: Props) { 12 | const userName = (await params).userName; 13 | 14 | return { 15 | title: `Files - ${userName}`, 16 | }; 17 | } 18 | 19 | export default async function Page({ params }: Props) { 20 | const userName = (await params).userName; 21 | 22 | return ( 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/hooks/simulation/useLatestSimulations.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useQuery, 3 | UseQueryOptions, 4 | UseQueryResult, 5 | } from "@tanstack/react-query"; 6 | 7 | import { getLatestSimulations } from "@/actions/simulation/getLatestSimulations"; 8 | 9 | type Return = Awaited>; 10 | 11 | export function useLatestSimulations( 12 | options?: UseQueryOptions 13 | ): UseQueryResult { 14 | return useQuery({ 15 | queryKey: ["latest-simulations"], 16 | queryFn: () => getLatestSimulations(), 17 | staleTime: 10000, // 10 seconds 18 | refetchOnWindowFocus: true, 19 | refetchInterval: 10000, // 10 seconds 20 | refetchIntervalInBackground: true, 21 | ...options, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "components/Simulations/Content.module.xss"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/hooks/simulation/useSimulation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useQuery, 3 | UseQueryOptions, 4 | UseQueryResult, 5 | } from "@tanstack/react-query"; 6 | 7 | import { getSimulation } from "@/actions/simulation/getSimulation"; 8 | 9 | type Return = Awaited>; 10 | 11 | export function useSimulation( 12 | simulationId: string, 13 | options?: UseQueryOptions 14 | ): UseQueryResult { 15 | return useQuery({ 16 | queryKey: ["simulation", simulationId], 17 | queryFn: () => getSimulation(simulationId), 18 | staleTime: 10000, // 10 seconds 19 | refetchOnWindowFocus: true, 20 | refetchInterval: 10000, // 10 seconds 21 | refetchIntervalInBackground: true, 22 | ...options, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/hooks/simulation/useLatestSimulationMacromolecules.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useQuery, 3 | UseQueryOptions, 4 | UseQueryResult, 5 | } from "@tanstack/react-query"; 6 | import { SIMULATION_TYPE } from "database"; 7 | 8 | import { getLatestSimulationMacromolecules } from "@/actions/simulation/getLatestSimulationMacromolecules"; 9 | 10 | type Return = Awaited>; 11 | 12 | export function useLatestSimulationMacromolecules( 13 | type: SIMULATION_TYPE, 14 | options?: UseQueryOptions 15 | ): UseQueryResult { 16 | return useQuery({ 17 | queryKey: ["latest-simulation-macromolecules", type], 18 | queryFn: () => getLatestSimulationMacromolecules(type), 19 | ...options, 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/actions/administration/updateSimulation.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { Prisma, prisma } from "database"; 3 | 4 | import { validateAuth } from "../auth/validateAuth"; 5 | 6 | export async function updateSimulation( 7 | id: string, 8 | nextData: Prisma.SimulationUpdateInput 9 | ) { 10 | const { user } = await validateAuth(); 11 | 12 | if (!user) { 13 | return "unauthenticated"; 14 | } 15 | 16 | if (!(user.role === "ADMINISTRATOR")) { 17 | return "unauthorized"; 18 | } 19 | 20 | try { 21 | await prisma.simulation.update({ 22 | where: { 23 | id, 24 | }, 25 | data: { 26 | ...nextData, 27 | }, 28 | }); 29 | 30 | return "success"; 31 | } catch { 32 | // 33 | 34 | return "unhandled-prisma-error"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apps/web/hooks/administration/useUserCount.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useQuery, 3 | UseQueryOptions, 4 | UseQueryResult, 5 | } from "@tanstack/react-query"; 6 | import { USER_STATUS } from "database"; 7 | 8 | import { getUserCount } from "@/actions/administration/getUserCount"; 9 | 10 | type Return = Awaited>; 11 | 12 | export function useUserCount( 13 | queryText?: string, 14 | queryStatus?: USER_STATUS, 15 | options?: UseQueryOptions 16 | ): UseQueryResult { 17 | return useQuery({ 18 | queryKey: ["user-count", queryText, queryStatus], 19 | queryFn: () => getUserCount(queryText, queryStatus), 20 | refetchInterval: 10 * 60 * 1000, 21 | staleTime: 10 * 60 * 1000, 22 | ...options, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/components/Layout/Shell/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | import EpiAmOWhiteLogo from "@/assets/epiamo-white.png"; 4 | import fiocruzROLogo from "@/assets/fiocruz-ro.png"; 5 | import labioquimLogo from "@/assets/labioquim.png"; 6 | import unirWhiteLogo from "@/assets/unir-white.png"; 7 | 8 | import classes from "./Footer.module.css"; 9 | 10 | export function Footer() { 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/actions/utils/getSettings.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { prisma } from "database"; 3 | 4 | import { validateAuth } from "../auth/validateAuth"; 5 | 6 | export async function getSettings(systemId: string) { 7 | const { user } = await validateAuth(); 8 | 9 | if (!user) { 10 | return "unauthenticated"; 11 | } 12 | 13 | try { 14 | let settings = await prisma.settings.findFirst({ 15 | where: { 16 | systemId, 17 | }, 18 | }); 19 | 20 | if (!settings) { 21 | settings = await prisma.settings.create({ 22 | data: { 23 | systemId, 24 | systemMode: "DOWN", 25 | }, 26 | }); 27 | } 28 | 29 | return settings; 30 | } catch (e) { 31 | console.log("getSettings: " + e); 32 | return "error"; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/web/lib/lucia.ts: -------------------------------------------------------------------------------- 1 | import { PrismaAdapter } from "@lucia-auth/adapter-prisma"; 2 | import { prisma, User as PrismaUser } from "database"; 3 | import { Lucia } from "lucia"; 4 | 5 | const adapter = new PrismaAdapter(prisma.session, prisma.user); 6 | 7 | export const lucia = new Lucia(adapter, { 8 | sessionCookie: { 9 | name: "session", 10 | expires: false, 11 | attributes: { 12 | secure: process.env.NODE_ENV === "production", 13 | }, 14 | }, 15 | getUserAttributes: (attributes) => { 16 | return attributes; 17 | }, 18 | }); 19 | 20 | interface DatabaseUserAttributes extends Omit {} 21 | 22 | declare module "lucia" { 23 | interface Register { 24 | Lucia: typeof lucia; 25 | DatabaseUserAttributes: DatabaseUserAttributes; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/actions/simulation/getLatestSimulationMacromolecules.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { SIMULATION_TYPE } from "database"; 3 | 4 | import { api } from "@/lib/apis"; 5 | 6 | import { validateAuth } from "../auth/validateAuth"; 7 | 8 | export type Macromolecules = { 9 | macromolecule: string; 10 | ligandItp?: string; 11 | ligandPdb?: string; 12 | }; 13 | 14 | export async function getLatestSimulationMacromolecules(type: SIMULATION_TYPE) { 15 | const { user } = await validateAuth(); 16 | 17 | if (!user) { 18 | return "unauthenticated"; 19 | } 20 | 21 | const response = await api.get( 22 | `/simulation/macromolecule/${type}`, 23 | { 24 | headers: { 25 | "x-username": user.userName, 26 | }, 27 | } 28 | ); 29 | 30 | return response.data; 31 | } 32 | -------------------------------------------------------------------------------- /apps/web/components/Administration/NavItem.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Paper, Title } from "@mantine/core"; 3 | import clsx from "clsx"; 4 | import Link from "next/link"; 5 | 6 | import classes from "./NavItem.module.css"; 7 | 8 | interface Props { 9 | disabled?: boolean; 10 | link: NavLink; 11 | } 12 | 13 | export function AdministrationNavItem({ disabled, link }: Props) { 14 | return ( 15 | 23 | 24 | 25 | {link.label} 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /apps/web/components/Lander/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Box } from "@mantine/core"; 3 | 4 | import { LanderFooter } from "./Footer"; 5 | import { LanderHeader } from "./Header"; 6 | import { InteractiveParticles } from "./InteractiveParticles"; 7 | 8 | import styles from "./Layout.module.css"; 9 | 10 | interface LayoutProps { 11 | children: React.ReactNode; 12 | } 13 | 14 | export function LanderLayout({ children }: LayoutProps) { 15 | return ( 16 | 17 | 18 | 19 | {/* Header would need its own sticky positioning logic in Header.module.css */} 20 | 21 | {children} 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/web/components/Administration/NavSection.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Box, Text } from "@mantine/core"; 3 | 4 | import { AdministrationNavItem } from "./NavItem"; 5 | 6 | import classes from "./NavSection.module.css"; 7 | 8 | interface Props { 9 | section: NavSection; 10 | } 11 | 12 | export function AdministrationNavSection({ section }: Props) { 13 | return ( 14 | 15 | {section.title} 16 | 17 | {section.links.map((link) => ( 18 | 23 | ))} 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Should run applications as in development? 2 | DEBUG=1 3 | 4 | # TRAEFIK 5 | TRAEFIK_URL= 6 | 7 | # VISUAL DYNAMICS 8 | VISUALDYNAMICS_URL= 9 | DB_USER=postgres 10 | DB_PASS= 11 | DB_HOST=database 12 | DB_PORT=5432 13 | DB_DATABASE=visualdynamics 14 | 15 | DATABASE_URL=postgresql://postgres:${DB_PASS}@database:5432/${DB_DATABASE} 16 | 17 | NEXTAUTH_SECRET= 18 | NEXTAUTH_URL="http://${VISUALDYNAMICS_URL}" # set this to your external address (your domain) 19 | # NEXTAUTH_URL_INTERNAL="http://client:3001" # set this to the 127.0.0.1:3001 when running behind a proxy 20 | 21 | APP_URL=http://${VISUALDYNAMICS_URL} 22 | 23 | # MAILER 24 | SMTP_USER= 25 | SMTP_PASS= 26 | SMTP_PORT= 27 | SMTP_HOST= 28 | 29 | # SERVER 30 | CELERY_BROKER_URL=redis://redis:6379/0 31 | CELERY_RESULT_BACKEND=redis://redis:6379/0 32 | FLASK_DEBUG=${DEBUG} 33 | -------------------------------------------------------------------------------- /apps/web/components/Lander/Layout.module.css: -------------------------------------------------------------------------------- 1 | .layout { 2 | display: flex; 3 | flex-direction: column; 4 | min-height: 100dvh; 5 | background-color: var(--mantine-color-white); /* Main background is white */ 6 | position: relative; /* Establishes a stacking context */ 7 | /* Ensure the layout's own stacking context is above a default z-index: 0 particle container, 8 | but allows children to be above the particles. */ 9 | z-index: 1; /* This makes the layout box (and its opaque background) sit on top of z-index:0 particles */ 10 | overflow-x: hidden; 11 | } 12 | 13 | .mainContent { 14 | flex-grow: 1; 15 | position: relative; 16 | z-index: 2; /* Above particles */ 17 | /* Adjust padding as needed using Mantine variables */ 18 | /* padding-top: var(--mantine-spacing-xl); 19 | padding-bottom: var(--mantine-spacing-xl); */ 20 | } -------------------------------------------------------------------------------- /apps/api/src/utils/executeCommands.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | 3 | import { runCommand } from "./runCommand"; 4 | 5 | export async function executeCommands( 6 | commands: string[], 7 | fileStepPath: string, 8 | fileLogPath: string 9 | ) { 10 | for (const command of commands) { 11 | if (command.startsWith("#")) { 12 | try { 13 | await fs.promises.appendFile(fileStepPath, `${command}\n`); 14 | } catch (err) { 15 | console.error("Error writing step:", err); 16 | throw err; // Re-throw for handling in the calling code 17 | } 18 | } else if (command.length > 0) { 19 | const { returncode } = await runCommand(command, fileLogPath); 20 | 21 | if (returncode > 0) { 22 | throw new Error(`Command ${command} exited with non-zero return code`); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/api/src/username.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CanActivate, 3 | ExecutionContext, 4 | Injectable, 5 | UnauthorizedException, 6 | } from "@nestjs/common"; 7 | import { Request } from "express"; 8 | 9 | @Injectable() 10 | export class UsernameGuard implements CanActivate { 11 | async canActivate(context: ExecutionContext): Promise { 12 | const request = context.switchToHttp().getRequest(); 13 | const userName = this.extractUsernameFromHeader(request); 14 | 15 | if (!userName) { 16 | throw new UnauthorizedException(); 17 | } 18 | 19 | try { 20 | request.userName = userName; 21 | } catch { 22 | throw new UnauthorizedException(); 23 | } 24 | 25 | return true; 26 | } 27 | 28 | private extractUsernameFromHeader(request: Request): string { 29 | return request.headers["x-username"] as string; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /apps/web/app/[locale]/app/(home)/_components/StepInfo/Step.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Loader, Text } from "@mantine/core"; 2 | import { IconCircleCheckFilled, IconClockPause } from "@tabler/icons-react"; 3 | 4 | import classes from "./Step.module.css"; 5 | 6 | interface Props { 7 | state: StepState; 8 | label: string; 9 | } 10 | 11 | const Icons = { 12 | done: IconCircleCheckFilled, 13 | waiting: IconClockPause, 14 | }; 15 | 16 | export function Step({ label, state }: Props) { 17 | let Icon; 18 | 19 | if (state !== "inprogress") { 20 | Icon = Icons[state]; 21 | } 22 | 23 | return ( 24 | 25 | 26 | {state === "inprogress" ? : Icon && } 27 | 28 | {label} 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /apps/web/actions/administration/createUserValidation.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { prisma } from "database"; 3 | 4 | export async function createUserValidation(userId: string) { 5 | try { 6 | const existingValidation = await prisma.userEmailValidation.findFirst({ 7 | where: { 8 | userId, 9 | }, 10 | }); 11 | 12 | if (existingValidation) { 13 | await prisma.userEmailValidation.delete({ 14 | where: { 15 | userId, 16 | }, 17 | }); 18 | } 19 | 20 | const newValidation = await prisma.userEmailValidation.create({ 21 | data: { 22 | user: { 23 | connect: { 24 | id: userId, 25 | }, 26 | }, 27 | }, 28 | }); 29 | 30 | return newValidation.id; 31 | } catch (e) { 32 | console.log("createUserValidation: " + e); 33 | return "failure"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /apps/web/app/_constants/routes.ts: -------------------------------------------------------------------------------- 1 | export const RouteLinks = { 2 | HOME: "/", 3 | LOGIN: "/auth/login", 4 | REGISTER: "/auth/register", 5 | ANALYTICS: "/analytics", 6 | GUIDES: "/guides", 7 | EMAIL_VALIDATION: "/account/email-validation", 8 | PASSWORD_RESET: "/account/password-reset", 9 | SIMULATIONS: "/app", 10 | SIMULATIONS_ABOUT: "/app/simulations/about", 11 | SIMULATIONS_APO: "/app/simulations/new/apo", 12 | SIMULATIONS_ACPYPE: "/app/simulations/new/acpype", 13 | SIMULATIONS_RUNNING: "/app/simulations", 14 | 15 | ADMIN_DASHBOARD: "/app/admin", 16 | ADMIN_USERS: "/app/admin/users", 17 | ADMIN_SIMULATIONS: "/app/admin/simulations", 18 | ADMIN_STATUS: "/app/admin/status", 19 | ADMIN_SETTINGS: "/app/admin/settings", 20 | 21 | PLASMO_QSAR: "https://www.qsar.labioquim.fiocruz.br/", 22 | PLASMO_IA: "https://www.plasmoia.labioquim.fiocruz.br/", 23 | } as const; 24 | -------------------------------------------------------------------------------- /apps/web/components/Administration/Simulations/Manager/SimulationManagerList.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | overflow: auto; 3 | flex: 1; 4 | border: 1px solid var(--mantine-color-default-border); 5 | } 6 | 7 | .table { 8 | flex: 1; 9 | } 10 | 11 | .loading_container { 12 | margin: auto; 13 | } 14 | 15 | .td { 16 | text-overflow: ellipsis; 17 | 18 | /* Needed to make it work */ 19 | overflow: hidden; 20 | white-space: nowrap; 21 | 22 | width: fit-content; 23 | 24 | @mixin larger-than $mantine-breakpoint-md { 25 | max-width: calc((100dvw - var(--app-shell-navbar-offset) - 32px) / 7); 26 | } 27 | } 28 | 29 | .running { 30 | color: var(--mantine-color-blue-3); 31 | } 32 | 33 | .errored { 34 | color: var(--mantine-color-red-3); 35 | } 36 | 37 | .queued { 38 | color: var(--mantine-color-orange-3); 39 | } 40 | 41 | .completed { 42 | color: var(--mantine-color-green-3); 43 | } -------------------------------------------------------------------------------- /apps/api/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@nestjs/common"; 2 | import { NestFactory } from "@nestjs/core"; 3 | import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; 4 | 5 | import { AppModule } from "./app.module"; 6 | 7 | async function bootstrap(): Promise { 8 | const app = await NestFactory.create(AppModule, { 9 | bodyParser: false, 10 | }); 11 | app.setGlobalPrefix("v1"); 12 | 13 | const config = new DocumentBuilder() 14 | .setTitle("Visual Dynamics Simulation API") 15 | .setDescription("The Visual Dynamics Simulation API Documentation") 16 | .setVersion("0.1") 17 | .build(); 18 | 19 | const document = SwaggerModule.createDocument(app, config); 20 | SwaggerModule.setup("docs", app, document); 21 | 22 | await app.listen(3000); 23 | } 24 | 25 | bootstrap().then(() => 26 | new Logger("NestApplication").log("API is running on http://localhost:3000") 27 | ); 28 | -------------------------------------------------------------------------------- /apps/web/actions/simulation/getSimulation.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { Simulation } from "database"; 3 | 4 | import { api } from "@/lib/apis"; 5 | 6 | import { validateAuth } from "../auth/validateAuth"; 7 | 8 | export type RunningSimulation = 9 | | { 10 | status: "running"; 11 | logData: string[]; 12 | stepData: string[]; 13 | submissionInfo: Partial; 14 | } 15 | | { status: "not-running" } 16 | | { status: "queued"; position: number }; 17 | 18 | export async function getSimulation(simulationId: string) { 19 | const { user } = await validateAuth(); 20 | 21 | if (!user) { 22 | return "unauthenticated"; 23 | } 24 | 25 | const response = await api.get("/simulation", { 26 | headers: { 27 | "x-username": user.userName, 28 | }, 29 | params: { 30 | id: simulationId, 31 | }, 32 | }); 33 | 34 | return response.data; 35 | } 36 | -------------------------------------------------------------------------------- /apps/web/actions/auth/createPasswordReset.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { prisma } from "database"; 3 | 4 | export async function createPasswordReset(email: string) { 5 | const user = await prisma.user.findFirst({ 6 | where: { 7 | email, 8 | }, 9 | }); 10 | 11 | if (user) { 12 | const passwordReset = await prisma.userPasswordReset.create({ 13 | data: { 14 | user: { 15 | connect: { 16 | id: user.id, 17 | }, 18 | }, 19 | }, 20 | }); 21 | 22 | await prisma.userPasswordReset.updateMany({ 23 | where: { 24 | userId: user.id, 25 | id: { not: passwordReset.id }, 26 | }, 27 | data: { 28 | usable: false, 29 | }, 30 | }); 31 | 32 | return { 33 | resetId: passwordReset.id, 34 | email: user.email, 35 | firstName: user.firstName, 36 | }; 37 | } 38 | 39 | return "no-user"; 40 | } 41 | -------------------------------------------------------------------------------- /packages/database/prisma/migrations/20250418123026_add_password_reset/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "user_password_resets" ( 3 | "id" TEXT NOT NULL, 4 | "user_id" TEXT NOT NULL, 5 | "used" BOOLEAN NOT NULL DEFAULT false, 6 | "valid_until" TIMESTAMP(3) NOT NULL DEFAULT NOW() + interval '15 min', 7 | "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | "updated_at" TIMESTAMP(3), 9 | 10 | CONSTRAINT "user_password_resets_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "user_password_resets_id_key" ON "user_password_resets"("id"); 15 | 16 | -- CreateIndex 17 | CREATE UNIQUE INDEX "user_password_resets_user_id_key" ON "user_password_resets"("user_id"); 18 | 19 | -- AddForeignKey 20 | ALTER TABLE "user_password_resets" ADD CONSTRAINT "user_password_resets_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 21 | -------------------------------------------------------------------------------- /apps/web/components/Auth/Login/Login.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | text-decoration: none; 3 | } 4 | 5 | .formContainer { 6 | display: flex; 7 | flex-direction: column; 8 | gap: var(--mantine-spacing-md); 9 | } 10 | 11 | .user { 12 | display: block; 13 | width: 100%; 14 | padding: var(--mantine-spacing-md); 15 | color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0)); 16 | 17 | cursor: default; 18 | } 19 | 20 | .signin { 21 | display: block; 22 | width: 100%; 23 | padding: var(--mantine-spacing-md); 24 | color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0)); 25 | 26 | cursor: pointer; 27 | 28 | @mixin hover { 29 | background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-8)); 30 | } 31 | } 32 | 33 | .modalTitle { 34 | font-size: var(--mantine-font-size-xl); 35 | font-weight: bold; 36 | text-transform: uppercase; 37 | letter-spacing: 5px; 38 | } -------------------------------------------------------------------------------- /apps/web/app/[locale]/(public)/auth/email-validation/[activationId]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from "@mantine/core"; 2 | 3 | import { validateUserEmail } from "@/actions/auth/validateUserEmail"; 4 | import { Heading } from "@/components/Heading/Heading"; 5 | import { LanderLayout } from "@/components/Lander/Layout"; 6 | 7 | interface Props { 8 | params: Promise<{ 9 | activationId: string; 10 | }>; 11 | } 12 | 13 | export default async function AccountActivationPage({ params }: Props) { 14 | const { activationId } = await params; 15 | 16 | const validationStatus = await validateUserEmail(activationId); 17 | 18 | if (!validationStatus) { 19 | return null; 20 | } 21 | 22 | return ( 23 | 24 | 25 | 26 | 27 | Your email was validated and now you can login to use the available 28 | services. 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | 7 | permissions: 8 | contents: read # for checkout 9 | 10 | jobs: 11 | release: 12 | name: Bump Version and Release 13 | permissions: 14 | contents: write # to be able to publish a GitHub release 15 | issues: write # to be able to comment on released issues 16 | pull-requests: write # to be able to comment on released pull requests 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Setup Repo 20 | uses: actions/checkout@v4 21 | 22 | - name: Install Node 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: 22 26 | cache: 'yarn' 27 | 28 | - name: Install dependencies 29 | run: yarn install 30 | 31 | - name: Semantic release 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | run: yarn semantic-release -------------------------------------------------------------------------------- /apps/web/app/[locale]/(public)/analytics/page.tsx: -------------------------------------------------------------------------------- 1 | import { AspectRatio } from "@mantine/core"; 2 | 3 | import { Heading } from "@/components/Heading/Heading"; 4 | import { LanderCallToActionSection } from "@/components/Lander/CallToActionSection"; 5 | import { LanderLayout } from "@/components/Lander/Layout"; 6 | 7 | import classes from "./page.module.css"; 8 | 9 | export default function AnalyticsPage() { 10 | return ( 11 | 12 | 13 | 14 |