Rewrote backend in Rust
This commit is contained in:
		
							
								
								
									
										4
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project version="4">
 | 
				
			||||||
 | 
					  <component name="ExternalStorageConfigurationManager" enabled="true" />
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
							
								
								
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project version="4">
 | 
				
			||||||
 | 
					  <component name="VcsDirectoryMappings">
 | 
				
			||||||
 | 
					    <mapping directory="" vcs="Git" />
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
							
								
								
									
										3
									
								
								backend/.cargo/config.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								backend/.cargo/config.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					[build]
 | 
				
			||||||
 | 
					rustflags = ["--cfg", "tokio_unstable"]
 | 
				
			||||||
 | 
					incremental = true
 | 
				
			||||||
							
								
								
									
										1
									
								
								backend/.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/.env
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					DATABASE_URL=sqlite.db
 | 
				
			||||||
							
								
								
									
										133
									
								
								backend/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								backend/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
				
			|||||||
 | 
					# Created by https://www.toptal.com/developers/gitignore/api/clion,rust
 | 
				
			||||||
 | 
					# Edit at https://www.toptal.com/developers/gitignore?templates=clion,rust
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### CLion ###
 | 
				
			||||||
 | 
					# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
 | 
				
			||||||
 | 
					# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# User-specific stuff
 | 
				
			||||||
 | 
					.idea/**/workspace.xml
 | 
				
			||||||
 | 
					.idea/**/tasks.xml
 | 
				
			||||||
 | 
					.idea/**/usage.statistics.xml
 | 
				
			||||||
 | 
					.idea/**/dictionaries
 | 
				
			||||||
 | 
					.idea/**/shelf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# AWS User-specific
 | 
				
			||||||
 | 
					.idea/**/aws.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Generated files
 | 
				
			||||||
 | 
					.idea/**/contentModel.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Sensitive or high-churn files
 | 
				
			||||||
 | 
					.idea/**/dataSources/
 | 
				
			||||||
 | 
					.idea/**/dataSources.ids
 | 
				
			||||||
 | 
					.idea/**/dataSources.local.xml
 | 
				
			||||||
 | 
					.idea/**/sqlDataSources.xml
 | 
				
			||||||
 | 
					.idea/**/dynamic.xml
 | 
				
			||||||
 | 
					.idea/**/uiDesigner.xml
 | 
				
			||||||
 | 
					.idea/**/dbnavigator.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Gradle
 | 
				
			||||||
 | 
					.idea/**/gradle.xml
 | 
				
			||||||
 | 
					.idea/**/libraries
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Gradle and Maven with auto-import
 | 
				
			||||||
 | 
					# When using Gradle or Maven with auto-import, you should exclude module files,
 | 
				
			||||||
 | 
					# since they will be recreated, and may cause churn.  Uncomment if using
 | 
				
			||||||
 | 
					# auto-import.
 | 
				
			||||||
 | 
					# .idea/artifacts
 | 
				
			||||||
 | 
					# .idea/compiler.xml
 | 
				
			||||||
 | 
					# .idea/jarRepositories.xml
 | 
				
			||||||
 | 
					# .idea/modules.xml
 | 
				
			||||||
 | 
					# .idea/*.iml
 | 
				
			||||||
 | 
					# .idea/modules
 | 
				
			||||||
 | 
					# *.iml
 | 
				
			||||||
 | 
					# *.ipr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# CMake
 | 
				
			||||||
 | 
					cmake-build-*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Mongo Explorer plugin
 | 
				
			||||||
 | 
					.idea/**/mongoSettings.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# File-based project format
 | 
				
			||||||
 | 
					*.iws
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IntelliJ
 | 
				
			||||||
 | 
					out/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# mpeltonen/sbt-idea plugin
 | 
				
			||||||
 | 
					.idea_modules/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# JIRA plugin
 | 
				
			||||||
 | 
					atlassian-ide-plugin.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Cursive Clojure plugin
 | 
				
			||||||
 | 
					.idea/replstate.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SonarLint plugin
 | 
				
			||||||
 | 
					.idea/sonarlint/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Crashlytics plugin (for Android Studio and IntelliJ)
 | 
				
			||||||
 | 
					com_crashlytics_export_strings.xml
 | 
				
			||||||
 | 
					crashlytics.properties
 | 
				
			||||||
 | 
					crashlytics-build.properties
 | 
				
			||||||
 | 
					fabric.properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Editor-based Rest Client
 | 
				
			||||||
 | 
					.idea/httpRequests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Android studio 3.1+ serialized cache file
 | 
				
			||||||
 | 
					.idea/caches/build_file_checksums.ser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### CLion Patch ###
 | 
				
			||||||
 | 
					# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# *.iml
 | 
				
			||||||
 | 
					# modules.xml
 | 
				
			||||||
 | 
					# .idea/misc.xml
 | 
				
			||||||
 | 
					# *.ipr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Sonarlint plugin
 | 
				
			||||||
 | 
					# https://plugins.jetbrains.com/plugin/7973-sonarlint
 | 
				
			||||||
 | 
					.idea/**/sonarlint/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SonarQube Plugin
 | 
				
			||||||
 | 
					# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
 | 
				
			||||||
 | 
					.idea/**/sonarIssues.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Markdown Navigator plugin
 | 
				
			||||||
 | 
					# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
 | 
				
			||||||
 | 
					.idea/**/markdown-navigator.xml
 | 
				
			||||||
 | 
					.idea/**/markdown-navigator-enh.xml
 | 
				
			||||||
 | 
					.idea/**/markdown-navigator/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Cache file creation bug
 | 
				
			||||||
 | 
					# See https://youtrack.jetbrains.com/issue/JBR-2257
 | 
				
			||||||
 | 
					.idea/$CACHE_FILE$
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# CodeStream plugin
 | 
				
			||||||
 | 
					# https://plugins.jetbrains.com/plugin/12206-codestream
 | 
				
			||||||
 | 
					.idea/codestream.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Azure Toolkit for IntelliJ plugin
 | 
				
			||||||
 | 
					# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
 | 
				
			||||||
 | 
					.idea/**/azureSettings.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Rust ###
 | 
				
			||||||
 | 
					# Generated by Cargo
 | 
				
			||||||
 | 
					# will have compiled files and executables
 | 
				
			||||||
 | 
					debug/
 | 
				
			||||||
 | 
					target/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
 | 
				
			||||||
 | 
					# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
 | 
				
			||||||
 | 
					Cargo.lock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# These are backup files generated by rustfmt
 | 
				
			||||||
 | 
					**/*.rs.bk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# MSVC Windows builds of rustc generate these, which store debugging information
 | 
				
			||||||
 | 
					*.pdb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# End of https://www.toptal.com/developers/gitignore/api/clion,rust
 | 
				
			||||||
							
								
								
									
										8
									
								
								backend/.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								backend/.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -1,8 +0,0 @@
 | 
				
			|||||||
# Default ignored files
 | 
					 | 
				
			||||||
/shelf/
 | 
					 | 
				
			||||||
/workspace.xml
 | 
					 | 
				
			||||||
# Editor-based HTTP Client requests
 | 
					 | 
				
			||||||
/httpRequests/
 | 
					 | 
				
			||||||
# Datasource local storage ignored files
 | 
					 | 
				
			||||||
/dataSources/
 | 
					 | 
				
			||||||
/dataSources.local.xml
 | 
					 | 
				
			||||||
							
								
								
									
										1
									
								
								backend/.idea/.name
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								backend/.idea/.name
									
									
									
										generated
									
									
									
								
							@@ -1 +0,0 @@
 | 
				
			|||||||
backend
 | 
					 | 
				
			||||||
							
								
								
									
										11
									
								
								backend/.idea/backend.iml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								backend/.idea/backend.iml
									
									
									
										generated
									
									
									
								
							@@ -1,2 +1,11 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
<module classpath="CMake" type="CPP_MODULE" version="4" />
 | 
					<module type="CPP_MODULE" version="4">
 | 
				
			||||||
 | 
					  <component name="NewModuleRootManager">
 | 
				
			||||||
 | 
					    <content url="file://$MODULE_DIR$">
 | 
				
			||||||
 | 
					      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
 | 
				
			||||||
 | 
					      <excludeFolder url="file://$MODULE_DIR$/target" />
 | 
				
			||||||
 | 
					    </content>
 | 
				
			||||||
 | 
					    <orderEntry type="inheritedJdk" />
 | 
				
			||||||
 | 
					    <orderEntry type="sourceFolder" forTests="false" />
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</module>
 | 
				
			||||||
							
								
								
									
										9
									
								
								backend/.idea/cmake.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								backend/.idea/cmake.xml
									
									
									
										generated
									
									
									
								
							@@ -1,9 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<project version="4">
 | 
					 | 
				
			||||||
  <component name="CMakeSharedSettings">
 | 
					 | 
				
			||||||
    <configurations>
 | 
					 | 
				
			||||||
      <configuration PROFILE_NAME="Debug" ENABLED="true" CONFIG_NAME="Debug" GENERATION_OPTIONS="-G Ninja --toolchain C:\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_INSTALL_PREFIX=./cmake_install" />
 | 
					 | 
				
			||||||
      <configuration PROFILE_NAME="Release" ENABLED="false" CONFIG_NAME="Release" GENERATION_OPTIONS="-G Ninja --toolchain C:\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_INSTALL_PREFIX=./cmake_install" />
 | 
					 | 
				
			||||||
    </configurations>
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
</project>
 | 
					 | 
				
			||||||
							
								
								
									
										12
									
								
								backend/.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								backend/.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							@@ -1,12 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<project version="4">
 | 
					 | 
				
			||||||
  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
 | 
					 | 
				
			||||||
    <data-source source="LOCAL" name="sqlite.db [2]" uuid="788293bd-abec-4b6b-a13e-26da21cb36dd">
 | 
					 | 
				
			||||||
      <driver-ref>sqlite.xerial</driver-ref>
 | 
					 | 
				
			||||||
      <synchronize>true</synchronize>
 | 
					 | 
				
			||||||
      <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
 | 
					 | 
				
			||||||
      <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../run/sqlite.db</jdbc-url>
 | 
					 | 
				
			||||||
      <working-dir>$ProjectFileDir$</working-dir>
 | 
					 | 
				
			||||||
    </data-source>
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
</project>
 | 
					 | 
				
			||||||
							
								
								
									
										2
									
								
								backend/.idea/file_server.iml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								backend/.idea/file_server.iml
									
									
									
										generated
									
									
									
								
							@@ -1,2 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<module classpath="CMake" type="CPP_MODULE" version="4" />
 | 
					 | 
				
			||||||
							
								
								
									
										9
									
								
								backend/.idea/misc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								backend/.idea/misc.xml
									
									
									
										generated
									
									
									
								
							@@ -1,9 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<project version="4">
 | 
					 | 
				
			||||||
  <component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
 | 
					 | 
				
			||||||
  <component name="CidrRootsConfiguration">
 | 
					 | 
				
			||||||
    <libraryRoots>
 | 
					 | 
				
			||||||
      <file path="$PROJECT_DIR$/backend/lib" />
 | 
					 | 
				
			||||||
    </libraryRoots>
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
</project>
 | 
					 | 
				
			||||||
							
								
								
									
										2
									
								
								backend/.idea/modules.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								backend/.idea/modules.xml
									
									
									
										generated
									
									
									
								
							@@ -2,7 +2,7 @@
 | 
				
			|||||||
<project version="4">
 | 
					<project version="4">
 | 
				
			||||||
  <component name="ProjectModuleManager">
 | 
					  <component name="ProjectModuleManager">
 | 
				
			||||||
    <modules>
 | 
					    <modules>
 | 
				
			||||||
      <module fileurl="file://$PROJECT_DIR$/.idea/file_server.iml" filepath="$PROJECT_DIR$/.idea/file_server.iml" />
 | 
					      <module fileurl="file://$PROJECT_DIR$/.idea/backend.iml" filepath="$PROJECT_DIR$/.idea/backend.iml" />
 | 
				
			||||||
    </modules>
 | 
					    </modules>
 | 
				
			||||||
  </component>
 | 
					  </component>
 | 
				
			||||||
</project>
 | 
					</project>
 | 
				
			||||||
							
								
								
									
										7
									
								
								backend/.idea/runConfigurations/backend.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								backend/.idea/runConfigurations/backend.xml
									
									
									
										generated
									
									
									
								
							@@ -1,7 +0,0 @@
 | 
				
			|||||||
<component name="ProjectRunConfigurationManager">
 | 
					 | 
				
			||||||
  <configuration default="false" name="backend" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="--dev" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" WORKING_DIR="file://../../run" PASS_PARENT_ENVS_2="true" PROJECT_NAME="backend" TARGET_NAME="backend" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="backend" RUN_TARGET_NAME="backend">
 | 
					 | 
				
			||||||
    <method v="2">
 | 
					 | 
				
			||||||
      <option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
 | 
					 | 
				
			||||||
    </method>
 | 
					 | 
				
			||||||
  </configuration>
 | 
					 | 
				
			||||||
</component>
 | 
					 | 
				
			||||||
@@ -1,96 +0,0 @@
 | 
				
			|||||||
cmake_minimum_required(VERSION 3.21)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if (WIN32 AND (NOT VCPKG_TARGET_TRIPLET))
 | 
					 | 
				
			||||||
    set(VCPKG_TARGET_TRIPLET x64-windows-static)
 | 
					 | 
				
			||||||
endif (WIN32 AND (NOT VCPKG_TARGET_TRIPLET))
 | 
					 | 
				
			||||||
#set(VCPKG_LIBRARY_LINKAGE static)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
project(backend)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
set(CMAKE_CXX_STANDARD 20)
 | 
					 | 
				
			||||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
add_executable(backend
 | 
					 | 
				
			||||||
        src/main.cpp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        src/dto/dto.h
 | 
					 | 
				
			||||||
        src/dto/responses.cpp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        src/db/db.h
 | 
					 | 
				
			||||||
        src/db/db.cpp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        src/controllers/controllers.h
 | 
					 | 
				
			||||||
        src/controllers/admin.cpp
 | 
					 | 
				
			||||||
        src/controllers/user.cpp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        src/controllers/fs/fs_routes.cpp
 | 
					 | 
				
			||||||
        src/controllers/fs/fs_functions.cpp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        src/controllers/auth/auth_common.cpp
 | 
					 | 
				
			||||||
        src/controllers/auth/auth_basic.cpp
 | 
					 | 
				
			||||||
        src/controllers/auth/auth_2fa.cpp
 | 
					 | 
				
			||||||
        src/controllers/auth/auth_gitlab.cpp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        src/filters/filters.h
 | 
					 | 
				
			||||||
        src/filters/filters.cpp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        model/Inode.cc
 | 
					 | 
				
			||||||
        model/Inode.h
 | 
					 | 
				
			||||||
        model/Tokens.cc
 | 
					 | 
				
			||||||
        model/Tokens.h
 | 
					 | 
				
			||||||
        model/User.cc
 | 
					 | 
				
			||||||
        model/User.h
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        SMTPMail-drogon-master/SMTPMail.cc
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
find_package(Drogon CONFIG REQUIRED)
 | 
					 | 
				
			||||||
find_package(OpenSSL REQUIRED)
 | 
					 | 
				
			||||||
find_package(OpenCV CONFIG REQUIRED)
 | 
					 | 
				
			||||||
find_package(kubazip CONFIG REQUIRED)
 | 
					 | 
				
			||||||
find_path(JWT_CPP_INCLUDE_DIRS "jwt-cpp/base.h")
 | 
					 | 
				
			||||||
find_path(BOTAN_INCLUDE_DIRS "botan/botan.h")
 | 
					 | 
				
			||||||
find_path(QR_INCLUDE_DIRS "qrcodegen.hpp")
 | 
					 | 
				
			||||||
find_library(BOTAN_LIBRARY NAMES botan-2 botan)
 | 
					 | 
				
			||||||
find_library(QR_LIBRARY nayuki-qr-code-generator)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
target_include_directories(backend PRIVATE
 | 
					 | 
				
			||||||
        src
 | 
					 | 
				
			||||||
        model
 | 
					 | 
				
			||||||
        shl
 | 
					 | 
				
			||||||
        SMTPMail-drogon-master
 | 
					 | 
				
			||||||
        ${OpenCV_INCLUDE_DIRS}
 | 
					 | 
				
			||||||
        ${JWT_CPP_INCLUDE_DIRS}
 | 
					 | 
				
			||||||
        ${BOTAN_INCLUDE_DIRS}
 | 
					 | 
				
			||||||
        ${QR_INCLUDE_DIRS}
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
target_link_libraries(backend
 | 
					 | 
				
			||||||
        Drogon::Drogon
 | 
					 | 
				
			||||||
        OpenSSL::SSL
 | 
					 | 
				
			||||||
        kubazip::kubazip
 | 
					 | 
				
			||||||
        ${OpenCV_LIBS}
 | 
					 | 
				
			||||||
        ${BOTAN_LIBRARY}
 | 
					 | 
				
			||||||
        ${QR_LIBRARY}
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
set_property(TARGET backend PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
install(TARGETS backend RUNTIME_DEPENDENCY_SET backend_deps DESTINATION .)
 | 
					 | 
				
			||||||
install(RUNTIME_DEPENDENCY_SET backend_deps)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if(NOT MSVC)
 | 
					 | 
				
			||||||
    target_compile_options(backend PRIVATE
 | 
					 | 
				
			||||||
            $<$<CONFIG:Debug>:-g -Wall -Wno-unknown-pragmas>
 | 
					 | 
				
			||||||
            $<$<CONFIG:Release>:-O3>
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
else()
 | 
					 | 
				
			||||||
    target_compile_options(backend PRIVATE /W4 /wd4068)
 | 
					 | 
				
			||||||
endif(NOT MSVC)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if(WIN32)
 | 
					 | 
				
			||||||
    target_link_libraries(backend iphlpapi)
 | 
					 | 
				
			||||||
    target_compile_definitions(backend PRIVATE NOMINMAX _WIN32_WINNT=0x0A00)
 | 
					 | 
				
			||||||
endif()
 | 
					 | 
				
			||||||
							
								
								
									
										44
									
								
								backend/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								backend/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "backend_rust"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					rusqlite = { version = "0.28.0", features = ["bundled"] }
 | 
				
			||||||
 | 
					diesel = { version = "2.0.1", features = ["sqlite", "r2d2", "returning_clauses_for_sqlite_3_35"] }
 | 
				
			||||||
 | 
					diesel_migrations = "2.0.0"
 | 
				
			||||||
 | 
					r2d2_sqlite = "0.21.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					warp = { version = "0.3.3", features = ["compression", "compression-brotli", "compression-gzip"] }
 | 
				
			||||||
 | 
					headers = "0.3"
 | 
				
			||||||
 | 
					tokio = { version = "1.21.2", features = ["full", "tracing"] }
 | 
				
			||||||
 | 
					tokio-util = { version = "0.7.4", features = ["codec"] }
 | 
				
			||||||
 | 
					console-subscriber = "0.1.8"
 | 
				
			||||||
 | 
					futures = "0.3.24"
 | 
				
			||||||
 | 
					bytes = "1.2.1"
 | 
				
			||||||
 | 
					tracing = { version = "0.1.37", features = ["log-always"] }
 | 
				
			||||||
 | 
					log = "0.4.17"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					serde = { version = "1.0.145", features = ["derive"] }
 | 
				
			||||||
 | 
					serde_repr = "0.1.9"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pretty_env_logger = "0.4"
 | 
				
			||||||
 | 
					lazy_static = "1.4.0"
 | 
				
			||||||
 | 
					json = "0.12.4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jsonwebtoken = "8.1.1"
 | 
				
			||||||
 | 
					thiserror = "1.0.37"
 | 
				
			||||||
 | 
					chrono = "0.4.22"
 | 
				
			||||||
 | 
					rust-argon2 = "1.0.0"
 | 
				
			||||||
 | 
					lettre = "0.10.1"
 | 
				
			||||||
 | 
					ureq = { version = "2.5.0", features = ["json"] }
 | 
				
			||||||
 | 
					totp-rs = { version = "3.0.1", features = ["qr"] }
 | 
				
			||||||
 | 
					ring = { version = "0.16.20", default-features = false }
 | 
				
			||||||
 | 
					mime_guess = "2.0.4"
 | 
				
			||||||
 | 
					zip = "0.6.2"
 | 
				
			||||||
 | 
					base64 = "0.13.0"
 | 
				
			||||||
 | 
					image = "0.24.4"
 | 
				
			||||||
 | 
					cached = "0.39.0"
 | 
				
			||||||
 | 
					stretto = "0.7.1"
 | 
				
			||||||
@@ -1,21 +0,0 @@
 | 
				
			|||||||
MIT License
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Copyright (c) 2020 ihmc3jn09hk
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					 | 
				
			||||||
in the Software without restriction, including without limitation the rights
 | 
					 | 
				
			||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
					 | 
				
			||||||
copies of the Software, and to permit persons to whom the Software is
 | 
					 | 
				
			||||||
furnished to do so, subject to the following conditions:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The above copyright notice and this permission notice shall be included in all
 | 
					 | 
				
			||||||
copies or substantial portions of the Software.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
					 | 
				
			||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
					 | 
				
			||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
					 | 
				
			||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
					 | 
				
			||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
					 | 
				
			||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
					 | 
				
			||||||
SOFTWARE.
 | 
					 | 
				
			||||||
@@ -1,79 +0,0 @@
 | 
				
			|||||||
# SMTPMail-drogon
 | 
					 | 
				
			||||||
Simple Mail for the Drogon framework.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
It is made as a plugin for the [drogon](https://github.com/an-tao/drogon) framework.
 | 
					 | 
				
			||||||
It can be included into the drogon build with little
 | 
					 | 
				
			||||||
modification of the class declaration.
 | 
					 | 
				
			||||||
## Updates 
 | 
					 | 
				
			||||||
- **[ 13-06-2022 ] Fixed vulnerability issues reported by [Sam](https://snoopysecurity.github.io/about).**
 | 
					 | 
				
			||||||
- [ 13-09-2021 ] Added [HTML content support](https://github.com/ihmc3jn09hk/SMTPMail-drogon/pull/1).
 | 
					 | 
				
			||||||
- [ 23-12-2020 ] Added DNS support.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Acknowledgement
 | 
					 | 
				
			||||||
* The implementation takes SMTPClient for Qt from [kelvins](https://github.com/kelvins/SMTPClient) as reference.
 | 
					 | 
				
			||||||
* There requires a delay SSL encryption from the Tcp-socket (named TcpClient in trantor/drogon) and the major
 | 
					 | 
				
			||||||
author of drogon [reponsed](https://github.com/an-tao/drogon/issues/346) quickly.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Usage
 | 
					 | 
				
			||||||
Download to the plugin directory of the target drogon app, E.g. ~/drogon-app/plugins
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
$ git clone https://github.com/ihmc3jn09hk/SMTPMail-drogon.git
 | 
					 | 
				
			||||||
$ cp SMTPMail-drogon/SMTPMail.* ~/drogon-app/plugins
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* _Be aware of add the plugin into the config.json. Set the "name" field to "SMTPMail"_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Add the reference header and get the plugin from the app(), E.g.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```c++
 | 
					 | 
				
			||||||
...
 | 
					 | 
				
			||||||
#include "../plugins/SMTPMail.h"
 | 
					 | 
				
			||||||
...
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//Inside some function, E.g. A controller function.
 | 
					 | 
				
			||||||
...
 | 
					 | 
				
			||||||
//Send an email
 | 
					 | 
				
			||||||
auto *smtpmailPtr = app().getPlugin<SMTPMail>();
 | 
					 | 
				
			||||||
auto id = smtpmailPtr->sendEmail(
 | 
					 | 
				
			||||||
          "127.0.0.1",                  //The server IP/DNS
 | 
					 | 
				
			||||||
          587,                          //The port
 | 
					 | 
				
			||||||
          "mailer@something.com",       //Who send the email
 | 
					 | 
				
			||||||
          "receiver@otherthing.com",    //Send to whom
 | 
					 | 
				
			||||||
          "Testing SMTPMail Function",  //Email Subject/Title
 | 
					 | 
				
			||||||
          "Hello from drogon plugin",   //Content
 | 
					 | 
				
			||||||
          "mailer@something.com",       //Login user
 | 
					 | 
				
			||||||
          "123456",                     //User password
 | 
					 | 
				
			||||||
          false                         //Is HTML content
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
...
 | 
					 | 
				
			||||||
//Or get noted when email is sent
 | 
					 | 
				
			||||||
...
 | 
					 | 
				
			||||||
void callback(const std::string &msg)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  LOG_INFO << msg; /*Output e.g. "EMail sent. ID : 96ESERVDDFH17588ECF0C7B00326E3"*/
 | 
					 | 
				
			||||||
  /*Do whatever you like*/
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
...
 | 
					 | 
				
			||||||
auto *smtpmailPtr = app().getPlugin<SMTPMail>();
 | 
					 | 
				
			||||||
auto id = smtpmailPtr->sendEmail(
 | 
					 | 
				
			||||||
          "127.0.0.1",                  //The server IP/DNS
 | 
					 | 
				
			||||||
          587,                          //The port
 | 
					 | 
				
			||||||
          "mailer@something.com",       //Who send the email
 | 
					 | 
				
			||||||
          "receiver@otherthing.com",    //Send to whom
 | 
					 | 
				
			||||||
          "Testing SMTPMail Function",  //Email Subject/Title
 | 
					 | 
				
			||||||
          "Hello from drogon plugin",   //Content
 | 
					 | 
				
			||||||
          "mailer@something.com",       //Login user
 | 
					 | 
				
			||||||
          "123456",                     //User password
 | 
					 | 
				
			||||||
          false,                        //Is HTML content
 | 
					 | 
				
			||||||
          callback                      //Callback
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
$ cd ~/drogon-app/build
 | 
					 | 
				
			||||||
$ make
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Licence
 | 
					 | 
				
			||||||
* Feel free to use, thanks to open-source.
 | 
					 | 
				
			||||||
* For the sake of concern on commercial usage, a simple licence is included in each of the files.
 | 
					 | 
				
			||||||
@@ -1,400 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
*
 | 
					 | 
				
			||||||
* SMTPMail.cc *
 | 
					 | 
				
			||||||
*
 | 
					 | 
				
			||||||
* This plugin is for SMTP mail delivery for the Drogon web-framework.
 | 
					 | 
				
			||||||
Implementation
 | 
					 | 
				
			||||||
* reference from the project "SMTPClient" with Qt5 by kelvins. Please check out
 | 
					 | 
				
			||||||
* https://github.com/kelvins/SMTPClient.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Feel free to use the code. For the sake of any concern, the following licence is
 | 
					 | 
				
			||||||
attached.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 Copyright 2020 ihmc3jn09hk
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
 | 
					 | 
				
			||||||
this software and associated documentation files (the "Software"), to deal in
 | 
					 | 
				
			||||||
the Software without restriction, including without limitation the rights to
 | 
					 | 
				
			||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 | 
					 | 
				
			||||||
the Software, and to permit persons to whom the Software is furnished to do so,
 | 
					 | 
				
			||||||
subject to the following conditions:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The above copyright notice and this permission notice shall be included in all
 | 
					 | 
				
			||||||
copies or substantial portions of the Software.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
					 | 
				
			||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 | 
					 | 
				
			||||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 | 
					 | 
				
			||||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 | 
					 | 
				
			||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 | 
					 | 
				
			||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "SMTPMail.h"
 | 
					 | 
				
			||||||
#include <drogon/HttpAppFramework.h>
 | 
					 | 
				
			||||||
#include <drogon/utils/Utilities.h>
 | 
					 | 
				
			||||||
#include <trantor/net/EventLoopThread.h>
 | 
					 | 
				
			||||||
#include <trantor/net/TcpClient.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using namespace drogon;
 | 
					 | 
				
			||||||
using namespace trantor;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct EMail {
 | 
					 | 
				
			||||||
  enum states {
 | 
					 | 
				
			||||||
    Init,
 | 
					 | 
				
			||||||
    HandShake,
 | 
					 | 
				
			||||||
    Tls,
 | 
					 | 
				
			||||||
    Auth,
 | 
					 | 
				
			||||||
    User,
 | 
					 | 
				
			||||||
    Pass,
 | 
					 | 
				
			||||||
    Mail,
 | 
					 | 
				
			||||||
    Rcpt,
 | 
					 | 
				
			||||||
    Data,
 | 
					 | 
				
			||||||
    Body,
 | 
					 | 
				
			||||||
    Quit,
 | 
					 | 
				
			||||||
    Close
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  std::string m_from;
 | 
					 | 
				
			||||||
  std::string m_to;
 | 
					 | 
				
			||||||
  std::string m_subject;
 | 
					 | 
				
			||||||
  std::string m_content;
 | 
					 | 
				
			||||||
  std::string m_user;
 | 
					 | 
				
			||||||
  std::string m_passwd;
 | 
					 | 
				
			||||||
  states m_status;
 | 
					 | 
				
			||||||
  std::string m_uuid;
 | 
					 | 
				
			||||||
  bool m_isHTML{false};
 | 
					 | 
				
			||||||
  std::shared_ptr<trantor::TcpClient> m_socket;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  EMail(std::string from, std::string to, std::string subject,
 | 
					 | 
				
			||||||
        std::string content, std::string user, std::string passwd, bool isHTML,
 | 
					 | 
				
			||||||
        std::shared_ptr<trantor::TcpClient> socket)
 | 
					 | 
				
			||||||
      : m_from(std::move(from)), m_to(std::move(to)),
 | 
					 | 
				
			||||||
        m_subject(std::move(subject)), m_content(std::move(content)),
 | 
					 | 
				
			||||||
        m_user(std::move(user)), m_passwd(std::move(passwd)),
 | 
					 | 
				
			||||||
        m_socket(std::move(socket)), m_isHTML(isHTML),
 | 
					 | 
				
			||||||
        m_uuid(drogon::utils::getUuid()) {
 | 
					 | 
				
			||||||
    m_status = Init;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ~EMail() = default;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static std::unordered_map<std::string, std::shared_ptr<EMail>>
 | 
					 | 
				
			||||||
      m_emails; // Container for processing emails
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
std::unordered_map<std::string, std::shared_ptr<EMail>> EMail::m_emails;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void SMTPMail::initAndStart(const Json::Value &config) {
 | 
					 | 
				
			||||||
  /// Initialize and start the plugin
 | 
					 | 
				
			||||||
  LOG_INFO << "SMTPMail initialized and started";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void SMTPMail::shutdown() {
 | 
					 | 
				
			||||||
  /// Shutdown the plugin
 | 
					 | 
				
			||||||
  LOG_INFO << "STMPMail shutdown";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void messagesHandle(const trantor::TcpConnectionPtr &connPtr,
 | 
					 | 
				
			||||||
                    trantor::MsgBuffer *msg,
 | 
					 | 
				
			||||||
                    const std::shared_ptr<EMail> &email,
 | 
					 | 
				
			||||||
                    const std::function<void(const std::string &msg)> &cb) {
 | 
					 | 
				
			||||||
  std::string receivedMsg;
 | 
					 | 
				
			||||||
  while (msg->readableBytes() > 0) {
 | 
					 | 
				
			||||||
    std::string buf(msg->peek(), msg->readableBytes());
 | 
					 | 
				
			||||||
    receivedMsg.append(buf);
 | 
					 | 
				
			||||||
    //        LOG_INFO << buf;
 | 
					 | 
				
			||||||
    msg->retrieveAll();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  LOG_TRACE << "receive: " << receivedMsg;
 | 
					 | 
				
			||||||
  std::string responseCode(receivedMsg.begin(), receivedMsg.begin() + 3);
 | 
					 | 
				
			||||||
  //    std::string responseMsg(receivedMsg.begin() + 4, receivedMsg.end());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (email->m_status == EMail::Init && responseCode == "220") {
 | 
					 | 
				
			||||||
    std::string outMsg;
 | 
					 | 
				
			||||||
    trantor::MsgBuffer out;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append("EHLO smtpclient.qw");
 | 
					 | 
				
			||||||
    outMsg.append("\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out.append(outMsg.data(), outMsg.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    connPtr->send(std::move(out));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    email->m_status = EMail::HandShake;
 | 
					 | 
				
			||||||
  } else if (email->m_status == EMail::HandShake && responseCode == "220") {
 | 
					 | 
				
			||||||
    std::string outMsg;
 | 
					 | 
				
			||||||
    trantor::MsgBuffer out;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append("EHLO smtpclient.qw");
 | 
					 | 
				
			||||||
    outMsg.append("\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out.append(outMsg.data(), outMsg.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    connPtr->startClientEncryption(
 | 
					 | 
				
			||||||
        [connPtr, out]() {
 | 
					 | 
				
			||||||
          // LOG_TRACE << "SSL established";
 | 
					 | 
				
			||||||
          connPtr->send(out);
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        false, false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    email->m_status = EMail::Auth;
 | 
					 | 
				
			||||||
  } else if (email->m_status == EMail::HandShake && responseCode == "250") {
 | 
					 | 
				
			||||||
    std::string outMsg;
 | 
					 | 
				
			||||||
    trantor::MsgBuffer out;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append("STARTTLS");
 | 
					 | 
				
			||||||
    outMsg.append("\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out.append(outMsg.data(), outMsg.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    connPtr->send(std::move(out));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    email->m_status = EMail::HandShake;
 | 
					 | 
				
			||||||
  } else if (email->m_status == EMail::Auth && responseCode == "250") {
 | 
					 | 
				
			||||||
    trantor::MsgBuffer out;
 | 
					 | 
				
			||||||
    std::string outMsg;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append("AUTH LOGIN");
 | 
					 | 
				
			||||||
    outMsg.append("\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out.append(outMsg.data(), outMsg.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    connPtr->send(std::move(out));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    email->m_status = EMail::User;
 | 
					 | 
				
			||||||
  } else if (email->m_status == EMail::User && responseCode == "334") {
 | 
					 | 
				
			||||||
    trantor::MsgBuffer out;
 | 
					 | 
				
			||||||
    std::string outMsg;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::string secret(email->m_user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // outMsg.append(base64_encode(reinterpret_cast<const unsigned
 | 
					 | 
				
			||||||
    // char*>(secret.c_str()), secret.length()));
 | 
					 | 
				
			||||||
    outMsg.append(drogon::utils::base64Encode(
 | 
					 | 
				
			||||||
        reinterpret_cast<const unsigned char *>(secret.c_str()),
 | 
					 | 
				
			||||||
        secret.length()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append("\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out.append(outMsg.data(), outMsg.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    connPtr->send(std::move(out));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    email->m_status = EMail::Pass;
 | 
					 | 
				
			||||||
  } else if (email->m_status == EMail::Pass && responseCode == "334") {
 | 
					 | 
				
			||||||
    trantor::MsgBuffer out;
 | 
					 | 
				
			||||||
    std::string outMsg;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::string secret(email->m_passwd);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append(drogon::utils::base64Encode(
 | 
					 | 
				
			||||||
        reinterpret_cast<const unsigned char *>(secret.c_str()),
 | 
					 | 
				
			||||||
        secret.length()));
 | 
					 | 
				
			||||||
    outMsg.append("\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out.append(outMsg.data(), outMsg.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    connPtr->send(std::move(out));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    email->m_status = EMail::Mail;
 | 
					 | 
				
			||||||
  } else if (email->m_status == EMail::Mail && responseCode == "235") {
 | 
					 | 
				
			||||||
    trantor::MsgBuffer out;
 | 
					 | 
				
			||||||
    std::string outMsg;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append("MAIL FROM:<");
 | 
					 | 
				
			||||||
    outMsg.append(email->m_from);
 | 
					 | 
				
			||||||
    outMsg.append(">\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out.append(outMsg.data(), outMsg.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    connPtr->send(std::move(out));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    email->m_status = EMail::Rcpt;
 | 
					 | 
				
			||||||
  } else if (email->m_status == EMail::Rcpt && responseCode == "250") {
 | 
					 | 
				
			||||||
    trantor::MsgBuffer out;
 | 
					 | 
				
			||||||
    std::string outMsg;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append("RCPT TO:<");
 | 
					 | 
				
			||||||
    outMsg.append(email->m_to);
 | 
					 | 
				
			||||||
    outMsg.append(">\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out.append(outMsg.data(), outMsg.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    connPtr->send(std::move(out));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    email->m_status = EMail::Data;
 | 
					 | 
				
			||||||
  } else if (email->m_status == EMail::Data && responseCode == "250") {
 | 
					 | 
				
			||||||
    trantor::MsgBuffer out;
 | 
					 | 
				
			||||||
    std::string outMsg;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append("DATA");
 | 
					 | 
				
			||||||
    outMsg.append("\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out.append(outMsg.data(), outMsg.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    connPtr->send(std::move(out));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    email->m_status = EMail::Body;
 | 
					 | 
				
			||||||
  } else if (email->m_status == EMail::Body && responseCode == "354") {
 | 
					 | 
				
			||||||
    trantor::MsgBuffer out;
 | 
					 | 
				
			||||||
    std::string outMsg;
 | 
					 | 
				
			||||||
    std::time_t t = std::time(nullptr);
 | 
					 | 
				
			||||||
    char buf[100];
 | 
					 | 
				
			||||||
    std::strftime(buf, 100, "%a, %d %b %Y %T %z",  std::localtime(&t));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append("To: " + email->m_to + "\r\n");
 | 
					 | 
				
			||||||
    outMsg.append("From: " + email->m_from + "\r\n");
 | 
					 | 
				
			||||||
    outMsg.append("Date: " + std::string(buf) + "\r\n");
 | 
					 | 
				
			||||||
    if (email->m_isHTML) {
 | 
					 | 
				
			||||||
      outMsg.append("Content-Type: text/html;\r\n");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    outMsg.append("Subject: " + email->m_subject + "\r\n\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append(email->m_content);
 | 
					 | 
				
			||||||
    outMsg.append("\r\n.\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out.append(outMsg.data(), outMsg.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    connPtr->send(std::move(out));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    email->m_status = EMail::Quit;
 | 
					 | 
				
			||||||
  } else if (email->m_status == EMail::Quit && responseCode == "250") {
 | 
					 | 
				
			||||||
    trantor::MsgBuffer out;
 | 
					 | 
				
			||||||
    std::string outMsg;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    outMsg.append("QUIT");
 | 
					 | 
				
			||||||
    outMsg.append("\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    out.append(outMsg.data(), outMsg.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    connPtr->send(std::move(out));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    email->m_status = EMail::Close;
 | 
					 | 
				
			||||||
  } else if (email->m_status == EMail::Close) {
 | 
					 | 
				
			||||||
    /*Callback here for succeed delivery is probable*/
 | 
					 | 
				
			||||||
    cb("EMail sent. ID : " + email->m_uuid);
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    email->m_status = EMail::Close;
 | 
					 | 
				
			||||||
    /*Callback here for notification is probable*/
 | 
					 | 
				
			||||||
    cb(receivedMsg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
std::string
 | 
					 | 
				
			||||||
SMTPMail::sendEmail(const std::string &mailServer, const uint16_t &port,
 | 
					 | 
				
			||||||
                    const std::string &from, const std::string &to,
 | 
					 | 
				
			||||||
                    const std::string &subject, const std::string &content,
 | 
					 | 
				
			||||||
                    const std::string &user, const std::string &passwd,
 | 
					 | 
				
			||||||
                    bool isHTML,
 | 
					 | 
				
			||||||
                    const std::function<void(const std::string &)> &cb) {
 | 
					 | 
				
			||||||
  if (mailServer.empty() || from.empty() || to.empty() || subject.empty() ||
 | 
					 | 
				
			||||||
      user.empty() || passwd.empty()) {
 | 
					 | 
				
			||||||
    LOG_WARN << "Invalid input(s) - "
 | 
					 | 
				
			||||||
             << "\nServer : " << mailServer << "\nPort : " << port
 | 
					 | 
				
			||||||
             << "\nfrom : " << from << "\nto : " << to
 | 
					 | 
				
			||||||
             << "\nsubject : " << subject << "\nuser : " << user
 | 
					 | 
				
			||||||
             << "\npasswd : " << passwd;
 | 
					 | 
				
			||||||
    return {};
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static auto hasLineBreak = [](const std::string &msg) {
 | 
					 | 
				
			||||||
    if (std::string::npos != msg.find_first_of("\n") ||
 | 
					 | 
				
			||||||
        std::string::npos != msg.find_first_of("\r")) {
 | 
					 | 
				
			||||||
      return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (hasLineBreak(from)) {
 | 
					 | 
				
			||||||
    LOG_WARN << "Invalid \"FROM\" data : " << from;
 | 
					 | 
				
			||||||
    return {};
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (hasLineBreak(to)) {
 | 
					 | 
				
			||||||
    LOG_WARN << "Invalid \"TO\" data : " << to;
 | 
					 | 
				
			||||||
    return {};
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (hasLineBreak(subject)) {
 | 
					 | 
				
			||||||
    LOG_WARN << "Invalid \"SUBJECT\" data : " << subject.data();
 | 
					 | 
				
			||||||
    return {};
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  LOG_TRACE << "New TcpClient : " << mailServer << ":" << port;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Create the email
 | 
					 | 
				
			||||||
  auto email = std::make_shared<EMail>(from, to, subject, content, user, passwd,
 | 
					 | 
				
			||||||
                                       isHTML, nullptr);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  auto resolver = app().getResolver();
 | 
					 | 
				
			||||||
  resolver->resolve(
 | 
					 | 
				
			||||||
      mailServer, [email, port, cb](const trantor::InetAddress &addr) {
 | 
					 | 
				
			||||||
        constexpr size_t defaultLoopIdA = 10;
 | 
					 | 
				
			||||||
        constexpr size_t defaultLoopIdB = 9;
 | 
					 | 
				
			||||||
        auto loopA = app().getIOLoop(defaultLoopIdA);
 | 
					 | 
				
			||||||
        auto loopB = app().getIOLoop(defaultLoopIdB);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if ( loopA == loopB ) {
 | 
					 | 
				
			||||||
          LOG_WARN << "Please provide at least 2 threads for this plugin";
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto loop = loopA->isInLoopThread() ? loopB : loopA;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        assert(loop);                    // Should never be null
 | 
					 | 
				
			||||||
        trantor::InetAddress addr_(addr.toIp(), port, false);
 | 
					 | 
				
			||||||
        auto tcpSocket =
 | 
					 | 
				
			||||||
            std::make_shared<trantor::TcpClient>(loop, addr_, "SMTPMail");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        email->m_socket = tcpSocket;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::weak_ptr<EMail> email_wptr = email;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        EMail::m_emails.emplace(email->m_uuid,
 | 
					 | 
				
			||||||
                                email); // Assuming there is no uuid collision
 | 
					 | 
				
			||||||
        tcpSocket->setConnectionCallback(
 | 
					 | 
				
			||||||
            [email_wptr](const trantor::TcpConnectionPtr &connPtr) {
 | 
					 | 
				
			||||||
              auto email_ptr = email_wptr.lock();
 | 
					 | 
				
			||||||
              if (!email_ptr) {
 | 
					 | 
				
			||||||
                LOG_WARN << "EMail pointer gone";
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
              if (connPtr->connected()) {
 | 
					 | 
				
			||||||
                // send request;
 | 
					 | 
				
			||||||
                LOG_TRACE << "Connection established!";
 | 
					 | 
				
			||||||
              } else {
 | 
					 | 
				
			||||||
                LOG_TRACE << "Connection disconnect";
 | 
					 | 
				
			||||||
                EMail::m_emails.erase(
 | 
					 | 
				
			||||||
                    email_ptr->m_uuid); // Remove the email in list
 | 
					 | 
				
			||||||
                // thisPtr->onError(std::string("ReqResult::NetworkFailure"));
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        tcpSocket->setConnectionErrorCallback([email_wptr]() {
 | 
					 | 
				
			||||||
          auto email_ptr = email_wptr.lock();
 | 
					 | 
				
			||||||
          if (!email_ptr) {
 | 
					 | 
				
			||||||
            LOG_ERROR << "EMail pointer gone";
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          // can't connect to server
 | 
					 | 
				
			||||||
          LOG_ERROR << "Bad Server address";
 | 
					 | 
				
			||||||
          EMail::m_emails.erase(email_ptr->m_uuid); // Remove the email in list
 | 
					 | 
				
			||||||
          // thisPtr->onError(std::string("ReqResult::BadServerAddress"));
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        auto cb_(cb ? cb : [](const std::string &msg) {
 | 
					 | 
				
			||||||
          LOG_INFO << "Default email callback : " << msg;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        tcpSocket->setMessageCallback(
 | 
					 | 
				
			||||||
            [email_wptr, cb_](const trantor::TcpConnectionPtr &connPtr,
 | 
					 | 
				
			||||||
                              trantor::MsgBuffer *msg) {
 | 
					 | 
				
			||||||
              auto email_ptr = email_wptr.lock();
 | 
					 | 
				
			||||||
              if (!email_ptr) {
 | 
					 | 
				
			||||||
                LOG_ERROR << "EMail pointer gone";
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
              // email->m_socket->disconnect();
 | 
					 | 
				
			||||||
              messagesHandle(connPtr, msg, email_ptr, cb_);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        tcpSocket->connect(); // Start trying to send the email
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  return email->m_uuid;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,63 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 *  SMTPMail.h
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This plugin is for SMTP mail delievery for the Drogon web-framework.
 | 
					 | 
				
			||||||
Implementation
 | 
					 | 
				
			||||||
 * reference from the project "SMTPClient" with Qt5 by kelvins. Please check out
 | 
					 | 
				
			||||||
 * https://github.com/kelvins/SMTPClient.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Copyright 2020 ihmc3jn09hk
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
 | 
					 | 
				
			||||||
this software and associated documentation files (the "Software"), to deal in
 | 
					 | 
				
			||||||
the Software without restriction, including without limitation the rights to
 | 
					 | 
				
			||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 | 
					 | 
				
			||||||
the Software, and to permit persons to whom the Software is furnished to do so,
 | 
					 | 
				
			||||||
subject to the following conditions:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The above copyright notice and this permission notice shall be included in all
 | 
					 | 
				
			||||||
copies or substantial portions of the Software.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
					 | 
				
			||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 | 
					 | 
				
			||||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 | 
					 | 
				
			||||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 | 
					 | 
				
			||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 | 
					 | 
				
			||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <drogon/plugins/Plugin.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SMTPMail : public drogon::Plugin<SMTPMail> {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
  SMTPMail() = default;
 | 
					 | 
				
			||||||
  /// This method must be called by drogon to initialize and start the plugin.
 | 
					 | 
				
			||||||
  /// It must be implemented by the user.
 | 
					 | 
				
			||||||
  void initAndStart(const Json::Value &config) override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// This method must be called by drogon to shutdown the plugin.
 | 
					 | 
				
			||||||
  /// It must be implemented by the user.
 | 
					 | 
				
			||||||
  void shutdown() override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /** Send an email
 | 
					 | 
				
			||||||
   * return : An ID of the email.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  std::string sendEmail(
 | 
					 | 
				
			||||||
      const std::string
 | 
					 | 
				
			||||||
          &mailServer, // Mail server address/dns E.g. 127.0.0.1/smtp.mail.com
 | 
					 | 
				
			||||||
      const uint16_t &port,       // Port  E.g. 587
 | 
					 | 
				
			||||||
      const std::string &from,    // Send from whom E.g. drogon@gmail.com
 | 
					 | 
				
			||||||
      const std::string &to,      // Reciever       E.g. drogon@yahoo.com
 | 
					 | 
				
			||||||
      const std::string &subject, // The email title/subject
 | 
					 | 
				
			||||||
      const std::string &content, // The email content.
 | 
					 | 
				
			||||||
      const std::string &user,    // User      (Usually same as "from")
 | 
					 | 
				
			||||||
      const std::string &passwd,  // Password
 | 
					 | 
				
			||||||
      bool isHTML,                // content type
 | 
					 | 
				
			||||||
      const std::function<void(const std::string &)> &cb = {}
 | 
					 | 
				
			||||||
      // The callback for email sent notification
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,11 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "gitlab_id": "",
 | 
					 | 
				
			||||||
  "gitlab_secret": "",
 | 
					 | 
				
			||||||
  "gitlab_url": "",
 | 
					 | 
				
			||||||
  "gitlab_api_url": "",
 | 
					 | 
				
			||||||
  "gitlab_redirect_url": "",
 | 
					 | 
				
			||||||
  "smtp_server": "",
 | 
					 | 
				
			||||||
  "smtp_port": 25,
 | 
					 | 
				
			||||||
  "smtp_user": "",
 | 
					 | 
				
			||||||
  "smtp_password": ""
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										8
									
								
								backend/diesel.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								backend/diesel.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					# For documentation on how to configure this file,
 | 
				
			||||||
 | 
					# see https://diesel.rs/guides/configuring-diesel-cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[print_schema]
 | 
				
			||||||
 | 
					file = "src/schema.rs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[migrations_directory]
 | 
				
			||||||
 | 
					dir = "migrations"
 | 
				
			||||||
							
								
								
									
										0
									
								
								backend/migrations/.keep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								backend/migrations/.keep
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										3
									
								
								backend/migrations/2022-10-05-145523_init/down.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								backend/migrations/2022-10-05-145523_init/down.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					DROP TABLE inode;
 | 
				
			||||||
 | 
					DROP TABLE user;
 | 
				
			||||||
 | 
					DROP TABLE tokens
 | 
				
			||||||
							
								
								
									
										28
									
								
								backend/migrations/2022-10-05-145523_init/up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								backend/migrations/2022-10-05-145523_init/up.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					CREATE TABLE tokens (
 | 
				
			||||||
 | 
					                          'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
 | 
				
			||||||
 | 
					                          'owner_id' INTEGER NOT NULL,
 | 
				
			||||||
 | 
					                          'exp' INT8 NOT NULL
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE user (
 | 
				
			||||||
 | 
					                        'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
 | 
				
			||||||
 | 
					                        'gitlab' BOOLEAN NOT NULL,
 | 
				
			||||||
 | 
					                        'name' TEXT NOT NULL,
 | 
				
			||||||
 | 
					                        'password' TEXT NOT NULL,
 | 
				
			||||||
 | 
					                        'role' INT2 NOT NULL,
 | 
				
			||||||
 | 
					                        'root_id' INTEGER NOT NULL,
 | 
				
			||||||
 | 
					                        'tfa_type' INT2 NOT NULL,
 | 
				
			||||||
 | 
					                        'tfa_secret' BLOB,
 | 
				
			||||||
 | 
					                        'gitlab_at' TEXT,
 | 
				
			||||||
 | 
					                        'gitlab_rt' TEXT
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE inode (
 | 
				
			||||||
 | 
					                         'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
 | 
				
			||||||
 | 
					                         'is_file' BOOLEAN NOT NULL,
 | 
				
			||||||
 | 
					                         'name' TEXT NOT NULL,
 | 
				
			||||||
 | 
					                         'parent_id' INTEGER,
 | 
				
			||||||
 | 
					                         'owner_id' INTEGER NOT NULL,
 | 
				
			||||||
 | 
					                         'size' INT8,
 | 
				
			||||||
 | 
					                         'has_preview' BOOLEAN NOT NULL
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,295 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 *  Inode.h
 | 
					 | 
				
			||||||
 *  DO NOT EDIT. This file is generated by drogon_ctl
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
#include "drogon/orm/Result.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/Row.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/Field.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/SqlBinder.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/Mapper.h"
 | 
					 | 
				
			||||||
#ifdef __cpp_impl_coroutine
 | 
					 | 
				
			||||||
#include <drogon/orm/CoroMapper.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#include "trantor/utils/Date.h"
 | 
					 | 
				
			||||||
#include "trantor/utils/Logger.h"
 | 
					 | 
				
			||||||
#include "json/json.h"
 | 
					 | 
				
			||||||
#include <string>
 | 
					 | 
				
			||||||
#include <memory>
 | 
					 | 
				
			||||||
#include <vector>
 | 
					 | 
				
			||||||
#include <tuple>
 | 
					 | 
				
			||||||
#include <stdint.h>
 | 
					 | 
				
			||||||
#include <iostream>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace drogon
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
namespace orm
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
class DbClient;
 | 
					 | 
				
			||||||
using DbClientPtr = std::shared_ptr<DbClient>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
namespace drogon_model
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
namespace sqlite3
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Inode
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  public:
 | 
					 | 
				
			||||||
    struct Cols
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        static const std::string _id;
 | 
					 | 
				
			||||||
        static const std::string _is_file;
 | 
					 | 
				
			||||||
        static const std::string _name;
 | 
					 | 
				
			||||||
        static const std::string _parent_id;
 | 
					 | 
				
			||||||
        static const std::string _owner_id;
 | 
					 | 
				
			||||||
        static const std::string _size;
 | 
					 | 
				
			||||||
        static const std::string _has_preview;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const static int primaryKeyNumber;
 | 
					 | 
				
			||||||
    const static std::string tableName;
 | 
					 | 
				
			||||||
    const static bool hasPrimaryKey;
 | 
					 | 
				
			||||||
    const static std::string primaryKeyName;
 | 
					 | 
				
			||||||
    using PrimaryKeyType = uint64_t;
 | 
					 | 
				
			||||||
    const PrimaryKeyType &getPrimaryKey() const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @brief constructor
 | 
					 | 
				
			||||||
     * @param r One row of records in the SQL query result.
 | 
					 | 
				
			||||||
     * @param indexOffset Set the offset to -1 to access all columns by column names,
 | 
					 | 
				
			||||||
     * otherwise access all columns by offsets.
 | 
					 | 
				
			||||||
     * @note If the SQL is not a style of 'select * from table_name ...' (select all
 | 
					 | 
				
			||||||
     * columns by an asterisk), please set the offset to -1.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    explicit Inode(const drogon::orm::Row &r, const ssize_t indexOffset = 0) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @brief constructor
 | 
					 | 
				
			||||||
     * @param pJson The json object to construct a new instance.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    explicit Inode(const Json::Value &pJson) noexcept(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @brief constructor
 | 
					 | 
				
			||||||
     * @param pJson The json object to construct a new instance.
 | 
					 | 
				
			||||||
     * @param pMasqueradingVector The aliases of table columns.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    Inode(const Json::Value &pJson, const std::vector<std::string> &pMasqueradingVector) noexcept(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Inode() = default;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void updateByJson(const Json::Value &pJson) noexcept(false);
 | 
					 | 
				
			||||||
    void updateByMasqueradedJson(const Json::Value &pJson,
 | 
					 | 
				
			||||||
                                 const std::vector<std::string> &pMasqueradingVector) noexcept(false);
 | 
					 | 
				
			||||||
    static bool validateJsonForCreation(const Json::Value &pJson, std::string &err);
 | 
					 | 
				
			||||||
    static bool validateMasqueradedJsonForCreation(const Json::Value &,
 | 
					 | 
				
			||||||
                                                const std::vector<std::string> &pMasqueradingVector,
 | 
					 | 
				
			||||||
                                                    std::string &err);
 | 
					 | 
				
			||||||
    static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err);
 | 
					 | 
				
			||||||
    static bool validateMasqueradedJsonForUpdate(const Json::Value &,
 | 
					 | 
				
			||||||
                                          const std::vector<std::string> &pMasqueradingVector,
 | 
					 | 
				
			||||||
                                          std::string &err);
 | 
					 | 
				
			||||||
    static bool validJsonOfField(size_t index,
 | 
					 | 
				
			||||||
                          const std::string &fieldName,
 | 
					 | 
				
			||||||
                          const Json::Value &pJson,
 | 
					 | 
				
			||||||
                          std::string &err,
 | 
					 | 
				
			||||||
                          bool isForCreation);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column id  */
 | 
					 | 
				
			||||||
    ///Get the value of the column id, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfId() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getId() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column id
 | 
					 | 
				
			||||||
    void setId(const uint64_t &pId) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column is_file  */
 | 
					 | 
				
			||||||
    ///Get the value of the column is_file, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfIsFile() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getIsFile() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column is_file
 | 
					 | 
				
			||||||
    void setIsFile(const uint64_t &pIsFile) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column name  */
 | 
					 | 
				
			||||||
    ///Get the value of the column name, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const std::string &getValueOfName() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<std::string> &getName() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column name
 | 
					 | 
				
			||||||
    void setName(const std::string &pName) noexcept;
 | 
					 | 
				
			||||||
    void setName(std::string &&pName) noexcept;
 | 
					 | 
				
			||||||
    void setNameToNull() noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column parent_id  */
 | 
					 | 
				
			||||||
    ///Get the value of the column parent_id, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfParentId() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getParentId() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column parent_id
 | 
					 | 
				
			||||||
    void setParentId(const uint64_t &pParentId) noexcept;
 | 
					 | 
				
			||||||
    void setParentIdToNull() noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column owner_id  */
 | 
					 | 
				
			||||||
    ///Get the value of the column owner_id, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfOwnerId() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getOwnerId() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column owner_id
 | 
					 | 
				
			||||||
    void setOwnerId(const uint64_t &pOwnerId) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column size  */
 | 
					 | 
				
			||||||
    ///Get the value of the column size, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfSize() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getSize() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column size
 | 
					 | 
				
			||||||
    void setSize(const uint64_t &pSize) noexcept;
 | 
					 | 
				
			||||||
    void setSizeToNull() noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column has_preview  */
 | 
					 | 
				
			||||||
    ///Get the value of the column has_preview, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfHasPreview() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getHasPreview() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column has_preview
 | 
					 | 
				
			||||||
    void setHasPreview(const uint64_t &pHasPreview) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static size_t getColumnNumber() noexcept {  return 7;  }
 | 
					 | 
				
			||||||
    static const std::string &getColumnName(size_t index) noexcept(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Json::Value toJson() const;
 | 
					 | 
				
			||||||
    Json::Value toMasqueradedJson(const std::vector<std::string> &pMasqueradingVector) const;
 | 
					 | 
				
			||||||
    /// Relationship interfaces
 | 
					 | 
				
			||||||
  private:
 | 
					 | 
				
			||||||
    friend drogon::orm::Mapper<Inode>;
 | 
					 | 
				
			||||||
#ifdef __cpp_impl_coroutine
 | 
					 | 
				
			||||||
    friend drogon::orm::CoroMapper<Inode>;
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
    static const std::vector<std::string> &insertColumns() noexcept;
 | 
					 | 
				
			||||||
    void outputArgs(drogon::orm::internal::SqlBinder &binder) const;
 | 
					 | 
				
			||||||
    const std::vector<std::string> updateColumns() const;
 | 
					 | 
				
			||||||
    void updateArgs(drogon::orm::internal::SqlBinder &binder) const;
 | 
					 | 
				
			||||||
    ///For mysql or sqlite3
 | 
					 | 
				
			||||||
    void updateId(const uint64_t id);
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> id_;
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> isFile_;
 | 
					 | 
				
			||||||
    std::shared_ptr<std::string> name_;
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> parentId_;
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> ownerId_;
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> size_;
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> hasPreview_;
 | 
					 | 
				
			||||||
    struct MetaData
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        const std::string colName_;
 | 
					 | 
				
			||||||
        const std::string colType_;
 | 
					 | 
				
			||||||
        const std::string colDatabaseType_;
 | 
					 | 
				
			||||||
        const ssize_t colLength_;
 | 
					 | 
				
			||||||
        const bool isAutoVal_;
 | 
					 | 
				
			||||||
        const bool isPrimaryKey_;
 | 
					 | 
				
			||||||
        const bool notNull_;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    static const std::vector<MetaData> metaData_;
 | 
					 | 
				
			||||||
    bool dirtyFlag_[7]={ false };
 | 
					 | 
				
			||||||
  public:
 | 
					 | 
				
			||||||
    static const std::string &sqlForFindingByPrimaryKey()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        static const std::string sql="select * from " + tableName + " where id = ?";
 | 
					 | 
				
			||||||
        return sql;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static const std::string &sqlForDeletingByPrimaryKey()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        static const std::string sql="delete from " + tableName + " where id = ?";
 | 
					 | 
				
			||||||
        return sql;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    std::string sqlForInserting(bool &needSelection) const
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        std::string sql="insert into " + tableName + " (";
 | 
					 | 
				
			||||||
        size_t parametersCount = 0;
 | 
					 | 
				
			||||||
        needSelection = false;
 | 
					 | 
				
			||||||
        if(dirtyFlag_[1])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "is_file,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[2])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "name,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[3])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "parent_id,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[4])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "owner_id,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[5])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "size,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[6])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "has_preview,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(parametersCount > 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql[sql.length()-1]=')';
 | 
					 | 
				
			||||||
            sql += " values (";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            sql += ") values (";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if(dirtyFlag_[1])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[2])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[3])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[4])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[5])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[6])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(parametersCount > 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.resize(sql.length() - 1);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        sql.append(1, ')');
 | 
					 | 
				
			||||||
        LOG_TRACE << sql;
 | 
					 | 
				
			||||||
        return sql;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
} // namespace sqlite3
 | 
					 | 
				
			||||||
} // namespace drogon_model
 | 
					 | 
				
			||||||
@@ -1,631 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 *  Tokens.cc
 | 
					 | 
				
			||||||
 *  DO NOT EDIT. This file is generated by drogon_ctl
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "Tokens.h"
 | 
					 | 
				
			||||||
#include "drogon/utils/Utilities.h"
 | 
					 | 
				
			||||||
#include <string>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using namespace drogon;
 | 
					 | 
				
			||||||
using namespace drogon::orm;
 | 
					 | 
				
			||||||
using namespace drogon_model::sqlite3;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const std::string Tokens::Cols::_id = "id";
 | 
					 | 
				
			||||||
const std::string Tokens::Cols::_owner_id = "owner_id";
 | 
					 | 
				
			||||||
const std::string Tokens::Cols::_exp = "exp";
 | 
					 | 
				
			||||||
const std::string Tokens::primaryKeyName = "id";
 | 
					 | 
				
			||||||
const bool Tokens::hasPrimaryKey = true;
 | 
					 | 
				
			||||||
const std::string Tokens::tableName = "tokens";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const std::vector<typename Tokens::MetaData> Tokens::metaData_={
 | 
					 | 
				
			||||||
{"id","uint64_t","integer",8,1,1,1},
 | 
					 | 
				
			||||||
{"owner_id","uint64_t","integer",8,0,0,1},
 | 
					 | 
				
			||||||
{"exp","uint64_t","integer",8,0,0,1}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
const std::string &Tokens::getColumnName(size_t index) noexcept(false)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    assert(index < metaData_.size());
 | 
					 | 
				
			||||||
    return metaData_[index].colName_;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
Tokens::Tokens(const Row &r, const ssize_t indexOffset) noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if(indexOffset < 0)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(!r["id"].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            id_=std::make_shared<uint64_t>(r["id"].as<uint64_t>());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(!r["owner_id"].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            ownerId_=std::make_shared<uint64_t>(r["owner_id"].as<uint64_t>());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(!r["exp"].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            exp_=std::make_shared<uint64_t>(r["exp"].as<uint64_t>());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        size_t offset = (size_t)indexOffset;
 | 
					 | 
				
			||||||
        if(offset + 3 > r.size())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            LOG_FATAL << "Invalid SQL result for this model";
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        size_t index;
 | 
					 | 
				
			||||||
        index = offset + 0;
 | 
					 | 
				
			||||||
        if(!r[index].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            id_=std::make_shared<uint64_t>(r[index].as<uint64_t>());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        index = offset + 1;
 | 
					 | 
				
			||||||
        if(!r[index].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            ownerId_=std::make_shared<uint64_t>(r[index].as<uint64_t>());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        index = offset + 2;
 | 
					 | 
				
			||||||
        if(!r[index].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            exp_=std::make_shared<uint64_t>(r[index].as<uint64_t>());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Tokens::Tokens(const Json::Value &pJson, const std::vector<std::string> &pMasqueradingVector) noexcept(false)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if(pMasqueradingVector.size() != 3)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LOG_ERROR << "Bad masquerading vector";
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0]))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        dirtyFlag_[0] = true;
 | 
					 | 
				
			||||||
        if(!pJson[pMasqueradingVector[0]].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            id_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[0]].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        dirtyFlag_[1] = true;
 | 
					 | 
				
			||||||
        if(!pJson[pMasqueradingVector[1]].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            ownerId_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[1]].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2]))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        dirtyFlag_[2] = true;
 | 
					 | 
				
			||||||
        if(!pJson[pMasqueradingVector[2]].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            exp_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[2]].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Tokens::Tokens(const Json::Value &pJson) noexcept(false)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if(pJson.isMember("id"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        dirtyFlag_[0]=true;
 | 
					 | 
				
			||||||
        if(!pJson["id"].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            id_=std::make_shared<uint64_t>((uint64_t)pJson["id"].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(pJson.isMember("owner_id"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        dirtyFlag_[1]=true;
 | 
					 | 
				
			||||||
        if(!pJson["owner_id"].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            ownerId_=std::make_shared<uint64_t>((uint64_t)pJson["owner_id"].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(pJson.isMember("exp"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        dirtyFlag_[2]=true;
 | 
					 | 
				
			||||||
        if(!pJson["exp"].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            exp_=std::make_shared<uint64_t>((uint64_t)pJson["exp"].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Tokens::updateByMasqueradedJson(const Json::Value &pJson,
 | 
					 | 
				
			||||||
                                            const std::vector<std::string> &pMasqueradingVector) noexcept(false)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if(pMasqueradingVector.size() != 3)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LOG_ERROR << "Bad masquerading vector";
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0]))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(!pJson[pMasqueradingVector[0]].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            id_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[0]].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        dirtyFlag_[1] = true;
 | 
					 | 
				
			||||||
        if(!pJson[pMasqueradingVector[1]].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            ownerId_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[1]].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2]))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        dirtyFlag_[2] = true;
 | 
					 | 
				
			||||||
        if(!pJson[pMasqueradingVector[2]].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            exp_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[2]].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Tokens::updateByJson(const Json::Value &pJson) noexcept(false)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if(pJson.isMember("id"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(!pJson["id"].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            id_=std::make_shared<uint64_t>((uint64_t)pJson["id"].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(pJson.isMember("owner_id"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        dirtyFlag_[1] = true;
 | 
					 | 
				
			||||||
        if(!pJson["owner_id"].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            ownerId_=std::make_shared<uint64_t>((uint64_t)pJson["owner_id"].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(pJson.isMember("exp"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        dirtyFlag_[2] = true;
 | 
					 | 
				
			||||||
        if(!pJson["exp"].isNull())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            exp_=std::make_shared<uint64_t>((uint64_t)pJson["exp"].asUInt64());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const uint64_t &Tokens::getValueOfId() const noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    const static uint64_t defaultValue = uint64_t();
 | 
					 | 
				
			||||||
    if(id_)
 | 
					 | 
				
			||||||
        return *id_;
 | 
					 | 
				
			||||||
    return defaultValue;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
const std::shared_ptr<uint64_t> &Tokens::getId() const noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    return id_;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void Tokens::setId(const uint64_t &pId) noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    id_ = std::make_shared<uint64_t>(pId);
 | 
					 | 
				
			||||||
    dirtyFlag_[0] = true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
const typename Tokens::PrimaryKeyType & Tokens::getPrimaryKey() const
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    assert(id_);
 | 
					 | 
				
			||||||
    return *id_;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const uint64_t &Tokens::getValueOfOwnerId() const noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    const static uint64_t defaultValue = uint64_t();
 | 
					 | 
				
			||||||
    if(ownerId_)
 | 
					 | 
				
			||||||
        return *ownerId_;
 | 
					 | 
				
			||||||
    return defaultValue;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
const std::shared_ptr<uint64_t> &Tokens::getOwnerId() const noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    return ownerId_;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void Tokens::setOwnerId(const uint64_t &pOwnerId) noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    ownerId_ = std::make_shared<uint64_t>(pOwnerId);
 | 
					 | 
				
			||||||
    dirtyFlag_[1] = true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const uint64_t &Tokens::getValueOfExp() const noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    const static uint64_t defaultValue = uint64_t();
 | 
					 | 
				
			||||||
    if(exp_)
 | 
					 | 
				
			||||||
        return *exp_;
 | 
					 | 
				
			||||||
    return defaultValue;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
const std::shared_ptr<uint64_t> &Tokens::getExp() const noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    return exp_;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void Tokens::setExp(const uint64_t &pExp) noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    exp_ = std::make_shared<uint64_t>(pExp);
 | 
					 | 
				
			||||||
    dirtyFlag_[2] = true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Tokens::updateId(const uint64_t id)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    id_ = std::make_shared<uint64_t>(id);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const std::vector<std::string> &Tokens::insertColumns() noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    static const std::vector<std::string> inCols={
 | 
					 | 
				
			||||||
        "owner_id",
 | 
					 | 
				
			||||||
        "exp"
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    return inCols;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Tokens::outputArgs(drogon::orm::internal::SqlBinder &binder) const
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if(dirtyFlag_[1])
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(getOwnerId())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            binder << getValueOfOwnerId();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            binder << nullptr;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(dirtyFlag_[2])
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(getExp())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            binder << getValueOfExp();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            binder << nullptr;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const std::vector<std::string> Tokens::updateColumns() const
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    std::vector<std::string> ret;
 | 
					 | 
				
			||||||
    if(dirtyFlag_[1])
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret.push_back(getColumnName(1));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(dirtyFlag_[2])
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret.push_back(getColumnName(2));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return ret;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Tokens::updateArgs(drogon::orm::internal::SqlBinder &binder) const
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if(dirtyFlag_[1])
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(getOwnerId())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            binder << getValueOfOwnerId();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            binder << nullptr;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(dirtyFlag_[2])
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(getExp())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            binder << getValueOfExp();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            binder << nullptr;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
Json::Value Tokens::toJson() const
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    Json::Value ret;
 | 
					 | 
				
			||||||
    if(getId())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["id"]=(Json::UInt64)getValueOfId();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["id"]=Json::Value();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(getOwnerId())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["owner_id"]=(Json::UInt64)getValueOfOwnerId();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["owner_id"]=Json::Value();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(getExp())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["exp"]=(Json::UInt64)getValueOfExp();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["exp"]=Json::Value();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return ret;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Json::Value Tokens::toMasqueradedJson(
 | 
					 | 
				
			||||||
    const std::vector<std::string> &pMasqueradingVector) const
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    Json::Value ret;
 | 
					 | 
				
			||||||
    if(pMasqueradingVector.size() == 3)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(!pMasqueradingVector[0].empty())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if(getId())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ret[pMasqueradingVector[0]]=(Json::UInt64)getValueOfId();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ret[pMasqueradingVector[0]]=Json::Value();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(!pMasqueradingVector[1].empty())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if(getOwnerId())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ret[pMasqueradingVector[1]]=(Json::UInt64)getValueOfOwnerId();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ret[pMasqueradingVector[1]]=Json::Value();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(!pMasqueradingVector[2].empty())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if(getExp())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ret[pMasqueradingVector[2]]=(Json::UInt64)getValueOfExp();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ret[pMasqueradingVector[2]]=Json::Value();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return ret;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    LOG_ERROR << "Masquerade failed";
 | 
					 | 
				
			||||||
    if(getId())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["id"]=(Json::UInt64)getValueOfId();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["id"]=Json::Value();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(getOwnerId())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["owner_id"]=(Json::UInt64)getValueOfOwnerId();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["owner_id"]=Json::Value();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(getExp())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["exp"]=(Json::UInt64)getValueOfExp();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ret["exp"]=Json::Value();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return ret;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool Tokens::validateJsonForCreation(const Json::Value &pJson, std::string &err)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if(pJson.isMember("id"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(!validJsonOfField(0, "id", pJson["id"], err, true))
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(pJson.isMember("owner_id"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(!validJsonOfField(1, "owner_id", pJson["owner_id"], err, true))
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        err="The owner_id column cannot be null";
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(pJson.isMember("exp"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(!validJsonOfField(2, "exp", pJson["exp"], err, true))
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        err="The exp column cannot be null";
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
bool Tokens::validateMasqueradedJsonForCreation(const Json::Value &pJson,
 | 
					 | 
				
			||||||
                                                const std::vector<std::string> &pMasqueradingVector,
 | 
					 | 
				
			||||||
                                                std::string &err)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if(pMasqueradingVector.size() != 3)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        err = "Bad masquerading vector";
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      if(!pMasqueradingVector[0].empty())
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
          if(pJson.isMember(pMasqueradingVector[0]))
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
              if(!validJsonOfField(0, pMasqueradingVector[0], pJson[pMasqueradingVector[0]], err, true))
 | 
					 | 
				
			||||||
                  return false;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if(!pMasqueradingVector[1].empty())
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
          if(pJson.isMember(pMasqueradingVector[1]))
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
              if(!validJsonOfField(1, pMasqueradingVector[1], pJson[pMasqueradingVector[1]], err, true))
 | 
					 | 
				
			||||||
                  return false;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            err="The " + pMasqueradingVector[1] + " column cannot be null";
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if(!pMasqueradingVector[2].empty())
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
          if(pJson.isMember(pMasqueradingVector[2]))
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
              if(!validJsonOfField(2, pMasqueradingVector[2], pJson[pMasqueradingVector[2]], err, true))
 | 
					 | 
				
			||||||
                  return false;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            err="The " + pMasqueradingVector[2] + " column cannot be null";
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    catch(const Json::LogicError &e)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      err = e.what();
 | 
					 | 
				
			||||||
      return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
bool Tokens::validateJsonForUpdate(const Json::Value &pJson, std::string &err)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if(pJson.isMember("id"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(!validJsonOfField(0, "id", pJson["id"], err, false))
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        err = "The value of primary key must be set in the json object for update";
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(pJson.isMember("owner_id"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(!validJsonOfField(1, "owner_id", pJson["owner_id"], err, false))
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(pJson.isMember("exp"))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if(!validJsonOfField(2, "exp", pJson["exp"], err, false))
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
bool Tokens::validateMasqueradedJsonForUpdate(const Json::Value &pJson,
 | 
					 | 
				
			||||||
                                              const std::vector<std::string> &pMasqueradingVector,
 | 
					 | 
				
			||||||
                                              std::string &err)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if(pMasqueradingVector.size() != 3)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        err = "Bad masquerading vector";
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0]))
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
          if(!validJsonOfField(0, pMasqueradingVector[0], pJson[pMasqueradingVector[0]], err, false))
 | 
					 | 
				
			||||||
              return false;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        err = "The value of primary key must be set in the json object for update";
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
      if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
          if(!validJsonOfField(1, pMasqueradingVector[1], pJson[pMasqueradingVector[1]], err, false))
 | 
					 | 
				
			||||||
              return false;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2]))
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
          if(!validJsonOfField(2, pMasqueradingVector[2], pJson[pMasqueradingVector[2]], err, false))
 | 
					 | 
				
			||||||
              return false;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    catch(const Json::LogicError &e)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      err = e.what();
 | 
					 | 
				
			||||||
      return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
bool Tokens::validJsonOfField(size_t index,
 | 
					 | 
				
			||||||
                              const std::string &fieldName,
 | 
					 | 
				
			||||||
                              const Json::Value &pJson,
 | 
					 | 
				
			||||||
                              std::string &err,
 | 
					 | 
				
			||||||
                              bool isForCreation)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    switch(index)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        case 0:
 | 
					 | 
				
			||||||
            if(pJson.isNull())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                err="The " + fieldName + " column cannot be null";
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if(isForCreation)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                err="The automatic primary key cannot be set";
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if(!pJson.isUInt64())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                err="Type error in the "+fieldName+" field";
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        case 1:
 | 
					 | 
				
			||||||
            if(pJson.isNull())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                err="The " + fieldName + " column cannot be null";
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if(!pJson.isUInt64())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                err="Type error in the "+fieldName+" field";
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        case 2:
 | 
					 | 
				
			||||||
            if(pJson.isNull())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                err="The " + fieldName + " column cannot be null";
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if(!pJson.isUInt64())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                err="Type error in the "+fieldName+" field";
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        default:
 | 
					 | 
				
			||||||
            err="Internal error in the server";
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,211 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 *  Tokens.h
 | 
					 | 
				
			||||||
 *  DO NOT EDIT. This file is generated by drogon_ctl
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
#include "drogon/orm/Result.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/Row.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/Field.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/SqlBinder.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/Mapper.h"
 | 
					 | 
				
			||||||
#ifdef __cpp_impl_coroutine
 | 
					 | 
				
			||||||
#include <drogon/orm/CoroMapper.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#include "trantor/utils/Date.h"
 | 
					 | 
				
			||||||
#include "trantor/utils/Logger.h"
 | 
					 | 
				
			||||||
#include "json/json.h"
 | 
					 | 
				
			||||||
#include <string>
 | 
					 | 
				
			||||||
#include <memory>
 | 
					 | 
				
			||||||
#include <vector>
 | 
					 | 
				
			||||||
#include <tuple>
 | 
					 | 
				
			||||||
#include <stdint.h>
 | 
					 | 
				
			||||||
#include <iostream>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace drogon
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
namespace orm
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
class DbClient;
 | 
					 | 
				
			||||||
using DbClientPtr = std::shared_ptr<DbClient>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
namespace drogon_model
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
namespace sqlite3
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Tokens
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  public:
 | 
					 | 
				
			||||||
    struct Cols
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        static const std::string _id;
 | 
					 | 
				
			||||||
        static const std::string _owner_id;
 | 
					 | 
				
			||||||
        static const std::string _exp;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const static int primaryKeyNumber;
 | 
					 | 
				
			||||||
    const static std::string tableName;
 | 
					 | 
				
			||||||
    const static bool hasPrimaryKey;
 | 
					 | 
				
			||||||
    const static std::string primaryKeyName;
 | 
					 | 
				
			||||||
    using PrimaryKeyType = uint64_t;
 | 
					 | 
				
			||||||
    const PrimaryKeyType &getPrimaryKey() const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @brief constructor
 | 
					 | 
				
			||||||
     * @param r One row of records in the SQL query result.
 | 
					 | 
				
			||||||
     * @param indexOffset Set the offset to -1 to access all columns by column names,
 | 
					 | 
				
			||||||
     * otherwise access all columns by offsets.
 | 
					 | 
				
			||||||
     * @note If the SQL is not a style of 'select * from table_name ...' (select all
 | 
					 | 
				
			||||||
     * columns by an asterisk), please set the offset to -1.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    explicit Tokens(const drogon::orm::Row &r, const ssize_t indexOffset = 0) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @brief constructor
 | 
					 | 
				
			||||||
     * @param pJson The json object to construct a new instance.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    explicit Tokens(const Json::Value &pJson) noexcept(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @brief constructor
 | 
					 | 
				
			||||||
     * @param pJson The json object to construct a new instance.
 | 
					 | 
				
			||||||
     * @param pMasqueradingVector The aliases of table columns.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    Tokens(const Json::Value &pJson, const std::vector<std::string> &pMasqueradingVector) noexcept(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Tokens() = default;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void updateByJson(const Json::Value &pJson) noexcept(false);
 | 
					 | 
				
			||||||
    void updateByMasqueradedJson(const Json::Value &pJson,
 | 
					 | 
				
			||||||
                                 const std::vector<std::string> &pMasqueradingVector) noexcept(false);
 | 
					 | 
				
			||||||
    static bool validateJsonForCreation(const Json::Value &pJson, std::string &err);
 | 
					 | 
				
			||||||
    static bool validateMasqueradedJsonForCreation(const Json::Value &,
 | 
					 | 
				
			||||||
                                                const std::vector<std::string> &pMasqueradingVector,
 | 
					 | 
				
			||||||
                                                    std::string &err);
 | 
					 | 
				
			||||||
    static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err);
 | 
					 | 
				
			||||||
    static bool validateMasqueradedJsonForUpdate(const Json::Value &,
 | 
					 | 
				
			||||||
                                          const std::vector<std::string> &pMasqueradingVector,
 | 
					 | 
				
			||||||
                                          std::string &err);
 | 
					 | 
				
			||||||
    static bool validJsonOfField(size_t index,
 | 
					 | 
				
			||||||
                          const std::string &fieldName,
 | 
					 | 
				
			||||||
                          const Json::Value &pJson,
 | 
					 | 
				
			||||||
                          std::string &err,
 | 
					 | 
				
			||||||
                          bool isForCreation);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column id  */
 | 
					 | 
				
			||||||
    ///Get the value of the column id, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfId() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getId() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column id
 | 
					 | 
				
			||||||
    void setId(const uint64_t &pId) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column owner_id  */
 | 
					 | 
				
			||||||
    ///Get the value of the column owner_id, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfOwnerId() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getOwnerId() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column owner_id
 | 
					 | 
				
			||||||
    void setOwnerId(const uint64_t &pOwnerId) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column exp  */
 | 
					 | 
				
			||||||
    ///Get the value of the column exp, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfExp() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getExp() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column exp
 | 
					 | 
				
			||||||
    void setExp(const uint64_t &pExp) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static size_t getColumnNumber() noexcept {  return 3;  }
 | 
					 | 
				
			||||||
    static const std::string &getColumnName(size_t index) noexcept(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Json::Value toJson() const;
 | 
					 | 
				
			||||||
    Json::Value toMasqueradedJson(const std::vector<std::string> &pMasqueradingVector) const;
 | 
					 | 
				
			||||||
    /// Relationship interfaces
 | 
					 | 
				
			||||||
  private:
 | 
					 | 
				
			||||||
    friend drogon::orm::Mapper<Tokens>;
 | 
					 | 
				
			||||||
#ifdef __cpp_impl_coroutine
 | 
					 | 
				
			||||||
    friend drogon::orm::CoroMapper<Tokens>;
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
    static const std::vector<std::string> &insertColumns() noexcept;
 | 
					 | 
				
			||||||
    void outputArgs(drogon::orm::internal::SqlBinder &binder) const;
 | 
					 | 
				
			||||||
    const std::vector<std::string> updateColumns() const;
 | 
					 | 
				
			||||||
    void updateArgs(drogon::orm::internal::SqlBinder &binder) const;
 | 
					 | 
				
			||||||
    ///For mysql or sqlite3
 | 
					 | 
				
			||||||
    void updateId(const uint64_t id);
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> id_;
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> ownerId_;
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> exp_;
 | 
					 | 
				
			||||||
    struct MetaData
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        const std::string colName_;
 | 
					 | 
				
			||||||
        const std::string colType_;
 | 
					 | 
				
			||||||
        const std::string colDatabaseType_;
 | 
					 | 
				
			||||||
        const ssize_t colLength_;
 | 
					 | 
				
			||||||
        const bool isAutoVal_;
 | 
					 | 
				
			||||||
        const bool isPrimaryKey_;
 | 
					 | 
				
			||||||
        const bool notNull_;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    static const std::vector<MetaData> metaData_;
 | 
					 | 
				
			||||||
    bool dirtyFlag_[3]={ false };
 | 
					 | 
				
			||||||
  public:
 | 
					 | 
				
			||||||
    static const std::string &sqlForFindingByPrimaryKey()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        static const std::string sql="select * from " + tableName + " where id = ?";
 | 
					 | 
				
			||||||
        return sql;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static const std::string &sqlForDeletingByPrimaryKey()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        static const std::string sql="delete from " + tableName + " where id = ?";
 | 
					 | 
				
			||||||
        return sql;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    std::string sqlForInserting(bool &needSelection) const
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        std::string sql="insert into " + tableName + " (";
 | 
					 | 
				
			||||||
        size_t parametersCount = 0;
 | 
					 | 
				
			||||||
        needSelection = false;
 | 
					 | 
				
			||||||
        if(dirtyFlag_[1])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "owner_id,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[2])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "exp,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(parametersCount > 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql[sql.length()-1]=')';
 | 
					 | 
				
			||||||
            sql += " values (";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            sql += ") values (";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if(dirtyFlag_[1])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[2])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(parametersCount > 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.resize(sql.length() - 1);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        sql.append(1, ')');
 | 
					 | 
				
			||||||
        LOG_TRACE << sql;
 | 
					 | 
				
			||||||
        return sql;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
} // namespace sqlite3
 | 
					 | 
				
			||||||
} // namespace drogon_model
 | 
					 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,361 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 *  User.h
 | 
					 | 
				
			||||||
 *  DO NOT EDIT. This file is generated by drogon_ctl
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
#include "drogon/orm/Result.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/Row.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/Field.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/SqlBinder.h"
 | 
					 | 
				
			||||||
#include "drogon/orm/Mapper.h"
 | 
					 | 
				
			||||||
#ifdef __cpp_impl_coroutine
 | 
					 | 
				
			||||||
#include <drogon/orm/CoroMapper.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#include "trantor/utils/Date.h"
 | 
					 | 
				
			||||||
#include "trantor/utils/Logger.h"
 | 
					 | 
				
			||||||
#include "json/json.h"
 | 
					 | 
				
			||||||
#include <string>
 | 
					 | 
				
			||||||
#include <memory>
 | 
					 | 
				
			||||||
#include <vector>
 | 
					 | 
				
			||||||
#include <tuple>
 | 
					 | 
				
			||||||
#include <stdint.h>
 | 
					 | 
				
			||||||
#include <iostream>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace drogon
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
namespace orm
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
class DbClient;
 | 
					 | 
				
			||||||
using DbClientPtr = std::shared_ptr<DbClient>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
namespace drogon_model
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
namespace sqlite3
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class User
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  public:
 | 
					 | 
				
			||||||
    struct Cols
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        static const std::string _id;
 | 
					 | 
				
			||||||
        static const std::string _gitlab;
 | 
					 | 
				
			||||||
        static const std::string _name;
 | 
					 | 
				
			||||||
        static const std::string _password;
 | 
					 | 
				
			||||||
        static const std::string _role;
 | 
					 | 
				
			||||||
        static const std::string _root_id;
 | 
					 | 
				
			||||||
        static const std::string _tfa_type;
 | 
					 | 
				
			||||||
        static const std::string _tfa_secret;
 | 
					 | 
				
			||||||
        static const std::string _gitlab_at;
 | 
					 | 
				
			||||||
        static const std::string _gitlab_rt;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const static int primaryKeyNumber;
 | 
					 | 
				
			||||||
    const static std::string tableName;
 | 
					 | 
				
			||||||
    const static bool hasPrimaryKey;
 | 
					 | 
				
			||||||
    const static std::string primaryKeyName;
 | 
					 | 
				
			||||||
    using PrimaryKeyType = uint64_t;
 | 
					 | 
				
			||||||
    const PrimaryKeyType &getPrimaryKey() const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @brief constructor
 | 
					 | 
				
			||||||
     * @param r One row of records in the SQL query result.
 | 
					 | 
				
			||||||
     * @param indexOffset Set the offset to -1 to access all columns by column names,
 | 
					 | 
				
			||||||
     * otherwise access all columns by offsets.
 | 
					 | 
				
			||||||
     * @note If the SQL is not a style of 'select * from table_name ...' (select all
 | 
					 | 
				
			||||||
     * columns by an asterisk), please set the offset to -1.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    explicit User(const drogon::orm::Row &r, const ssize_t indexOffset = 0) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @brief constructor
 | 
					 | 
				
			||||||
     * @param pJson The json object to construct a new instance.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    explicit User(const Json::Value &pJson) noexcept(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @brief constructor
 | 
					 | 
				
			||||||
     * @param pJson The json object to construct a new instance.
 | 
					 | 
				
			||||||
     * @param pMasqueradingVector The aliases of table columns.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    User(const Json::Value &pJson, const std::vector<std::string> &pMasqueradingVector) noexcept(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    User() = default;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void updateByJson(const Json::Value &pJson) noexcept(false);
 | 
					 | 
				
			||||||
    void updateByMasqueradedJson(const Json::Value &pJson,
 | 
					 | 
				
			||||||
                                 const std::vector<std::string> &pMasqueradingVector) noexcept(false);
 | 
					 | 
				
			||||||
    static bool validateJsonForCreation(const Json::Value &pJson, std::string &err);
 | 
					 | 
				
			||||||
    static bool validateMasqueradedJsonForCreation(const Json::Value &,
 | 
					 | 
				
			||||||
                                                const std::vector<std::string> &pMasqueradingVector,
 | 
					 | 
				
			||||||
                                                    std::string &err);
 | 
					 | 
				
			||||||
    static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err);
 | 
					 | 
				
			||||||
    static bool validateMasqueradedJsonForUpdate(const Json::Value &,
 | 
					 | 
				
			||||||
                                          const std::vector<std::string> &pMasqueradingVector,
 | 
					 | 
				
			||||||
                                          std::string &err);
 | 
					 | 
				
			||||||
    static bool validJsonOfField(size_t index,
 | 
					 | 
				
			||||||
                          const std::string &fieldName,
 | 
					 | 
				
			||||||
                          const Json::Value &pJson,
 | 
					 | 
				
			||||||
                          std::string &err,
 | 
					 | 
				
			||||||
                          bool isForCreation);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column id  */
 | 
					 | 
				
			||||||
    ///Get the value of the column id, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfId() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getId() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column id
 | 
					 | 
				
			||||||
    void setId(const uint64_t &pId) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column gitlab  */
 | 
					 | 
				
			||||||
    ///Get the value of the column gitlab, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfGitlab() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getGitlab() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column gitlab
 | 
					 | 
				
			||||||
    void setGitlab(const uint64_t &pGitlab) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column name  */
 | 
					 | 
				
			||||||
    ///Get the value of the column name, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const std::string &getValueOfName() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<std::string> &getName() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column name
 | 
					 | 
				
			||||||
    void setName(const std::string &pName) noexcept;
 | 
					 | 
				
			||||||
    void setName(std::string &&pName) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column password  */
 | 
					 | 
				
			||||||
    ///Get the value of the column password, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const std::string &getValueOfPassword() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<std::string> &getPassword() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column password
 | 
					 | 
				
			||||||
    void setPassword(const std::string &pPassword) noexcept;
 | 
					 | 
				
			||||||
    void setPassword(std::string &&pPassword) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column role  */
 | 
					 | 
				
			||||||
    ///Get the value of the column role, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfRole() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getRole() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column role
 | 
					 | 
				
			||||||
    void setRole(const uint64_t &pRole) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column root_id  */
 | 
					 | 
				
			||||||
    ///Get the value of the column root_id, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfRootId() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getRootId() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column root_id
 | 
					 | 
				
			||||||
    void setRootId(const uint64_t &pRootId) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column tfa_type  */
 | 
					 | 
				
			||||||
    ///Get the value of the column tfa_type, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const uint64_t &getValueOfTfaType() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<uint64_t> &getTfaType() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column tfa_type
 | 
					 | 
				
			||||||
    void setTfaType(const uint64_t &pTfaType) noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column tfa_secret  */
 | 
					 | 
				
			||||||
    ///Get the value of the column tfa_secret, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const std::vector<char> &getValueOfTfaSecret() const noexcept;
 | 
					 | 
				
			||||||
    ///Return the column value by std::string with binary data
 | 
					 | 
				
			||||||
    std::string getValueOfTfaSecretAsString() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<std::vector<char>> &getTfaSecret() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column tfa_secret
 | 
					 | 
				
			||||||
    void setTfaSecret(const std::vector<char> &pTfaSecret) noexcept;
 | 
					 | 
				
			||||||
    void setTfaSecret(const std::string &pTfaSecret) noexcept;
 | 
					 | 
				
			||||||
    void setTfaSecretToNull() noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column gitlab_at  */
 | 
					 | 
				
			||||||
    ///Get the value of the column gitlab_at, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const std::string &getValueOfGitlabAt() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<std::string> &getGitlabAt() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column gitlab_at
 | 
					 | 
				
			||||||
    void setGitlabAt(const std::string &pGitlabAt) noexcept;
 | 
					 | 
				
			||||||
    void setGitlabAt(std::string &&pGitlabAt) noexcept;
 | 
					 | 
				
			||||||
    void setGitlabAtToNull() noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**  For column gitlab_rt  */
 | 
					 | 
				
			||||||
    ///Get the value of the column gitlab_rt, returns the default value if the column is null
 | 
					 | 
				
			||||||
    const std::string &getValueOfGitlabRt() const noexcept;
 | 
					 | 
				
			||||||
    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
 | 
					 | 
				
			||||||
    const std::shared_ptr<std::string> &getGitlabRt() const noexcept;
 | 
					 | 
				
			||||||
    ///Set the value of the column gitlab_rt
 | 
					 | 
				
			||||||
    void setGitlabRt(const std::string &pGitlabRt) noexcept;
 | 
					 | 
				
			||||||
    void setGitlabRt(std::string &&pGitlabRt) noexcept;
 | 
					 | 
				
			||||||
    void setGitlabRtToNull() noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static size_t getColumnNumber() noexcept {  return 10;  }
 | 
					 | 
				
			||||||
    static const std::string &getColumnName(size_t index) noexcept(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Json::Value toJson() const;
 | 
					 | 
				
			||||||
    Json::Value toMasqueradedJson(const std::vector<std::string> &pMasqueradingVector) const;
 | 
					 | 
				
			||||||
    /// Relationship interfaces
 | 
					 | 
				
			||||||
  private:
 | 
					 | 
				
			||||||
    friend drogon::orm::Mapper<User>;
 | 
					 | 
				
			||||||
#ifdef __cpp_impl_coroutine
 | 
					 | 
				
			||||||
    friend drogon::orm::CoroMapper<User>;
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
    static const std::vector<std::string> &insertColumns() noexcept;
 | 
					 | 
				
			||||||
    void outputArgs(drogon::orm::internal::SqlBinder &binder) const;
 | 
					 | 
				
			||||||
    const std::vector<std::string> updateColumns() const;
 | 
					 | 
				
			||||||
    void updateArgs(drogon::orm::internal::SqlBinder &binder) const;
 | 
					 | 
				
			||||||
    ///For mysql or sqlite3
 | 
					 | 
				
			||||||
    void updateId(const uint64_t id);
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> id_;
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> gitlab_;
 | 
					 | 
				
			||||||
    std::shared_ptr<std::string> name_;
 | 
					 | 
				
			||||||
    std::shared_ptr<std::string> password_;
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> role_;
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> rootId_;
 | 
					 | 
				
			||||||
    std::shared_ptr<uint64_t> tfaType_;
 | 
					 | 
				
			||||||
    std::shared_ptr<std::vector<char>> tfaSecret_;
 | 
					 | 
				
			||||||
    std::shared_ptr<std::string> gitlabAt_;
 | 
					 | 
				
			||||||
    std::shared_ptr<std::string> gitlabRt_;
 | 
					 | 
				
			||||||
    struct MetaData
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        const std::string colName_;
 | 
					 | 
				
			||||||
        const std::string colType_;
 | 
					 | 
				
			||||||
        const std::string colDatabaseType_;
 | 
					 | 
				
			||||||
        const ssize_t colLength_;
 | 
					 | 
				
			||||||
        const bool isAutoVal_;
 | 
					 | 
				
			||||||
        const bool isPrimaryKey_;
 | 
					 | 
				
			||||||
        const bool notNull_;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    static const std::vector<MetaData> metaData_;
 | 
					 | 
				
			||||||
    bool dirtyFlag_[10]={ false };
 | 
					 | 
				
			||||||
  public:
 | 
					 | 
				
			||||||
    static const std::string &sqlForFindingByPrimaryKey()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        static const std::string sql="select * from " + tableName + " where id = ?";
 | 
					 | 
				
			||||||
        return sql;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static const std::string &sqlForDeletingByPrimaryKey()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        static const std::string sql="delete from " + tableName + " where id = ?";
 | 
					 | 
				
			||||||
        return sql;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    std::string sqlForInserting(bool &needSelection) const
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        std::string sql="insert into " + tableName + " (";
 | 
					 | 
				
			||||||
        size_t parametersCount = 0;
 | 
					 | 
				
			||||||
        needSelection = false;
 | 
					 | 
				
			||||||
        if(dirtyFlag_[1])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "gitlab,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[2])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "name,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[3])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "password,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[4])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "role,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[5])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "root_id,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[6])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "tfa_type,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[7])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "tfa_secret,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[8])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "gitlab_at,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[9])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql += "gitlab_rt,";
 | 
					 | 
				
			||||||
            ++parametersCount;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(parametersCount > 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql[sql.length()-1]=')';
 | 
					 | 
				
			||||||
            sql += " values (";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            sql += ") values (";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if(dirtyFlag_[1])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[2])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[3])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[4])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[5])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[6])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[7])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[8])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(dirtyFlag_[9])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.append("?,");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(parametersCount > 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            sql.resize(sql.length() - 1);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        sql.append(1, ')');
 | 
					 | 
				
			||||||
        LOG_TRACE << sql;
 | 
					 | 
				
			||||||
        return sql;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
} // namespace sqlite3
 | 
					 | 
				
			||||||
} // namespace drogon_model
 | 
					 | 
				
			||||||
@@ -1,5 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "rdbms":"sqlite3",
 | 
					 | 
				
			||||||
  "filename":"run/sqlite.db",
 | 
					 | 
				
			||||||
  "tables":[]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,68 +0,0 @@
 | 
				
			|||||||
// Copyright (C) 2022 Andrei Avram
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifndef MSD_CHANNEL_BLOCKING_ITERATOR_HPP_
 | 
					 | 
				
			||||||
#define MSD_CHANNEL_BLOCKING_ITERATOR_HPP_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <iterator>
 | 
					 | 
				
			||||||
#include <mutex>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace msd {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @brief An iterator that block the current thread,
 | 
					 | 
				
			||||||
 * waiting to fetch elements from the channel.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Used to implement channel range-based for loop.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @tparam Channel Instance of channel.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
template <typename channel>
 | 
					 | 
				
			||||||
class blocking_iterator {
 | 
					 | 
				
			||||||
   public:
 | 
					 | 
				
			||||||
    using value_type = typename channel::value_type;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    explicit blocking_iterator(channel& ch) : ch_{ch} {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Advances to next element in the channel.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    blocking_iterator<channel> operator++() const noexcept { return *this; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns an element from the channel.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    value_type operator*() const
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        value_type value;
 | 
					 | 
				
			||||||
        value << ch_;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return value;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Makes iteration continue until the channel is closed and empty.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    bool operator!=(blocking_iterator<channel>) const
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        std::unique_lock<std::mutex> lock{ch_.mtx_};
 | 
					 | 
				
			||||||
        ch_.waitBeforeRead(lock);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return !(ch_.closed() && ch_.empty());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   private:
 | 
					 | 
				
			||||||
    channel& ch_;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}  // namespace msd
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @brief Output iterator specialization
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
struct std::iterator_traits<msd::blocking_iterator<T>> {
 | 
					 | 
				
			||||||
    using value_type = typename msd::blocking_iterator<T>::value_type;
 | 
					 | 
				
			||||||
    using iterator_category = std::output_iterator_tag;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // MSD_CHANNEL_BLOCKING_ITERATOR_HPP_
 | 
					 | 
				
			||||||
@@ -1,130 +0,0 @@
 | 
				
			|||||||
// Copyright (C) 2022 Andrei Avram
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifndef MSD_CHANNEL_HPP_
 | 
					 | 
				
			||||||
#define MSD_CHANNEL_HPP_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <atomic>
 | 
					 | 
				
			||||||
#include <condition_variable>
 | 
					 | 
				
			||||||
#include <cstdlib>
 | 
					 | 
				
			||||||
#include <mutex>
 | 
					 | 
				
			||||||
#include <queue>
 | 
					 | 
				
			||||||
#include <stdexcept>
 | 
					 | 
				
			||||||
#include <type_traits>
 | 
					 | 
				
			||||||
#include <utility>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "blocking_iterator.hpp"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace msd {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L))
 | 
					 | 
				
			||||||
#define NODISCARD [[nodiscard]]
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
#define NODISCARD
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace detail {
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
struct remove_cvref {
 | 
					 | 
				
			||||||
    using type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
using remove_cvref_t = typename remove_cvref<T>::type;
 | 
					 | 
				
			||||||
}  // namespace detail
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @brief Exception thrown if trying to write on closed channel.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class closed_channel : public std::runtime_error {
 | 
					 | 
				
			||||||
   public:
 | 
					 | 
				
			||||||
    explicit closed_channel(const char* msg) : std::runtime_error{msg} {}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @brief Thread-safe container for sharing data between threads.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Implements a blocking input iterator.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @tparam T The type of the elements.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
class channel {
 | 
					 | 
				
			||||||
   public:
 | 
					 | 
				
			||||||
    using value_type = T;
 | 
					 | 
				
			||||||
    using iterator = blocking_iterator<channel<T>>;
 | 
					 | 
				
			||||||
    using size_type = std::size_t;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a new channel.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param capacity Number of elements the channel can store before blocking.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    explicit constexpr channel(size_type capacity = 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Pushes an element into the channel.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @throws closed_channel if channel is closed.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    template <typename Type>
 | 
					 | 
				
			||||||
    friend void operator>>(Type&&, channel<detail::remove_cvref_t<Type>>&);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Pops an element from the channel.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @tparam Type The type of the elements
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    template <typename Type>
 | 
					 | 
				
			||||||
    friend void operator<<(Type&, channel<Type>&);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the number of elements in the channel.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    NODISCARD inline size_type constexpr size() const noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns true if there are no elements in channel.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    NODISCARD inline bool constexpr empty() const noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Closes the channel.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    inline void close() noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns true if the channel is closed.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    NODISCARD inline bool closed() const noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Iterator
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    iterator begin() noexcept;
 | 
					 | 
				
			||||||
    iterator end() noexcept;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Channel cannot be copied or moved.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    channel(const channel&) = delete;
 | 
					 | 
				
			||||||
    channel& operator=(const channel&) = delete;
 | 
					 | 
				
			||||||
    channel(channel&&) = delete;
 | 
					 | 
				
			||||||
    channel& operator=(channel&&) = delete;
 | 
					 | 
				
			||||||
    virtual ~channel() = default;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   private:
 | 
					 | 
				
			||||||
    const size_type cap_;
 | 
					 | 
				
			||||||
    std::queue<T> queue_;
 | 
					 | 
				
			||||||
    std::mutex mtx_;
 | 
					 | 
				
			||||||
    std::condition_variable cnd_;
 | 
					 | 
				
			||||||
    std::atomic<bool> is_closed_{false};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    inline void waitBeforeRead(std::unique_lock<std::mutex>&);
 | 
					 | 
				
			||||||
    friend class blocking_iterator<channel>;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "channel_impl.hpp"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}  // namespace msd
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // MSD_CHANNEL_HPP_
 | 
					 | 
				
			||||||
@@ -1,87 +0,0 @@
 | 
				
			|||||||
// Copyright (C) 2022 Andrei Avram
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
constexpr channel<T>::channel(const size_type capacity) : cap_{capacity}
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
void operator>>(T&& in, channel<detail::remove_cvref_t<T>>& ch)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if (ch.closed()) {
 | 
					 | 
				
			||||||
        throw closed_channel{"cannot write on closed channel"};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::unique_lock<std::mutex> lock{ch.mtx_};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (ch.cap_ > 0 && ch.queue_.size() == ch.cap_) {
 | 
					 | 
				
			||||||
        ch.cnd_.wait(lock, [&ch]() { return ch.queue_.size() < ch.cap_; });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ch.queue_.push(std::forward<T>(in));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ch.cnd_.notify_one();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
void operator<<(T& out, channel<T>& ch)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if (ch.closed() && ch.empty()) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        std::unique_lock<std::mutex> lock{ch.mtx_};
 | 
					 | 
				
			||||||
        ch.waitBeforeRead(lock);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (ch.queue_.size() > 0) {
 | 
					 | 
				
			||||||
            out = std::move(ch.queue_.front());
 | 
					 | 
				
			||||||
            ch.queue_.pop();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ch.cnd_.notify_one();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
constexpr typename channel<T>::size_type channel<T>::size() const noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    return queue_.size();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
constexpr bool channel<T>::empty() const noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    return queue_.empty();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
void channel<T>::close() noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    is_closed_.store(true);
 | 
					 | 
				
			||||||
    cnd_.notify_all();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
bool channel<T>::closed() const noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    return is_closed_.load();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
blocking_iterator<channel<T>> channel<T>::begin() noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    return blocking_iterator<channel<T>>{*this};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
blocking_iterator<channel<T>> channel<T>::end() noexcept
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    return blocking_iterator<channel<T>>{*this};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template <typename T>
 | 
					 | 
				
			||||||
void channel<T>::waitBeforeRead(std::unique_lock<std::mutex>& lock)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    cnd_.wait(lock, [this] { return queue_.size() > 0 || closed(); });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										35
									
								
								backend/src/config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								backend/src/config.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					use lazy_static::lazy_static;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					lazy_static! {
 | 
				
			||||||
 | 
					    pub static ref CONFIG: Config = Config::read();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Config {
 | 
				
			||||||
 | 
					    pub gitlab_id: String,
 | 
				
			||||||
 | 
					    pub gitlab_secret: String,
 | 
				
			||||||
 | 
					    pub gitlab_url: String,
 | 
				
			||||||
 | 
					    pub gitlab_api_url: String,
 | 
				
			||||||
 | 
					    pub gitlab_redirect_url: String,
 | 
				
			||||||
 | 
					    pub smtp_server: String,
 | 
				
			||||||
 | 
					    pub smtp_port: u16,
 | 
				
			||||||
 | 
					    pub smtp_user: String,
 | 
				
			||||||
 | 
					    pub smtp_password: String
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Config {
 | 
				
			||||||
 | 
					    fn read() -> Self {
 | 
				
			||||||
 | 
					        let config = std::fs::read_to_string("config.json").expect("Failed to read config.json");
 | 
				
			||||||
 | 
					        let config = json::parse(config.as_str()).expect("Failed to parse config.json");
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            gitlab_id: config["gitlab_id"].as_str().expect("Config is missing 'gitlab_id'").to_string(),
 | 
				
			||||||
 | 
					            gitlab_secret: config["gitlab_secret"].as_str().expect("Config is missing 'gitlab_secret'").to_string(),
 | 
				
			||||||
 | 
					            gitlab_url: config["gitlab_url"].as_str().expect("Config is missing 'gitlab_url'").to_string(),
 | 
				
			||||||
 | 
					            gitlab_api_url: config["gitlab_api_url"].as_str().expect("Config is missing 'gitlab_api_url'").to_string(),
 | 
				
			||||||
 | 
					            gitlab_redirect_url: config["gitlab_redirect_url"].as_str().expect("Config is missing 'gitlab_redirect_url'").to_string(),
 | 
				
			||||||
 | 
					            smtp_server: config["smtp_server"].as_str().expect("Config is missing 'smtp_server'").to_string(),
 | 
				
			||||||
 | 
					            smtp_port: config["smtp_port"].as_u16().expect("Config is missing 'smtp_port'"),
 | 
				
			||||||
 | 
					            smtp_user: config["smtp_user"].as_str().expect("Config is missing 'smtp_user'").to_string(),
 | 
				
			||||||
 | 
					            smtp_password: config["smtp_password"].as_str().expect("Config is missing 'smtp_password'").to_string()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,110 +0,0 @@
 | 
				
			|||||||
#pragma clang diagnostic push
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "performance-unnecessary-value-param"
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "controllers.h"
 | 
					 | 
				
			||||||
#include "dto/dto.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace api {
 | 
					 | 
				
			||||||
    void admin::users(req_type, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
        std::vector<dto::Responses::GetUsersEntry> entries;
 | 
					 | 
				
			||||||
        auto users = user_mapper.findAll();
 | 
					 | 
				
			||||||
        for (const db::User& user : users)
 | 
					 | 
				
			||||||
            entries.emplace_back(
 | 
					 | 
				
			||||||
                    user.getValueOfId(),
 | 
					 | 
				
			||||||
                    user.getValueOfGitlab() != 0,
 | 
					 | 
				
			||||||
                    db::User_getEnumTfaType(user) != db::tfaTypes::NONE,
 | 
					 | 
				
			||||||
                    user.getValueOfName(),
 | 
					 | 
				
			||||||
                    db::User_getEnumRole(user)
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        cbk(dto::Responses::get_admin_users_res(entries));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void admin::set_role(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        Json::Value& json = *req->jsonObject();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            uint64_t user_id = dto::json_get<uint64_t>(json, "user").value();
 | 
					 | 
				
			||||||
            db::UserRole role = (db::UserRole)dto::json_get<int>(json, "role").value();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
            auto user = user_mapper.findByPrimaryKey(user_id);
 | 
					 | 
				
			||||||
            user.setRole(role);
 | 
					 | 
				
			||||||
            user_mapper.update(user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Validation error"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void admin::logout(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        Json::Value& json = *req->jsonObject();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            uint64_t user_id = dto::json_get<uint64_t>(json, "user").value();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
            auto user = user_mapper.findByPrimaryKey(user_id);
 | 
					 | 
				
			||||||
            auth::revoke_all(user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Validation error"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void admin::delete_user(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        Json::Value& json = *req->jsonObject();
 | 
					 | 
				
			||||||
        msd::channel<std::string> chan;
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            uint64_t user_id = dto::json_get<uint64_t>(json, "user").value();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
            auto user = user_mapper.findByPrimaryKey(user_id);
 | 
					 | 
				
			||||||
            auth::revoke_all(user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            std::unique_lock lock(*fs::get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            fs::delete_node(fs::get_node(user.getValueOfRootId()).value(), chan, true);
 | 
					 | 
				
			||||||
            user_mapper.deleteOne(user);
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Validation error"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void admin::disable_2fa(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        Json::Value& json = *req->jsonObject();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            uint64_t user_id = dto::json_get<uint64_t>(json, "user").value();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
            auto user = user_mapper.findByPrimaryKey(user_id);
 | 
					 | 
				
			||||||
            user.setTfaType(db::tfaTypes::NONE);
 | 
					 | 
				
			||||||
            user_mapper.update(user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Validation error"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void admin::is_admin(req_type, cbk_type cbk) {
 | 
					 | 
				
			||||||
        cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void admin::get_token(req_type, cbk_type cbk, uint64_t user) {
 | 
					 | 
				
			||||||
        db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            const auto &db_user = user_mapper.findByPrimaryKey(user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const std::string &token = auth::get_token(db_user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_login_res(token));
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Bad user"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#pragma clang diagnostic pop
 | 
					 | 
				
			||||||
@@ -1,99 +0,0 @@
 | 
				
			|||||||
#pragma clang diagnostic push
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "readability-make-member-function-const"
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <botan/base32.h>
 | 
					 | 
				
			||||||
#include <botan/base64.h>
 | 
					 | 
				
			||||||
#include <qrcodegen.hpp>
 | 
					 | 
				
			||||||
#include <opencv2/opencv.hpp>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "controllers/controllers.h"
 | 
					 | 
				
			||||||
#include "db/db.h"
 | 
					 | 
				
			||||||
#include "dto/dto.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
std::string create_totp_qrcode(const db::User& user, const std::string& b32_secret) {
 | 
					 | 
				
			||||||
    constexpr int qrcode_pixel_size = 4;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::stringstream code_ss;
 | 
					 | 
				
			||||||
    code_ss << "otpauth://totp/MFileserver:"
 | 
					 | 
				
			||||||
            << user.getValueOfName()
 | 
					 | 
				
			||||||
            << "?secret="
 | 
					 | 
				
			||||||
            << b32_secret
 | 
					 | 
				
			||||||
            << "&issuer=MFileserver";
 | 
					 | 
				
			||||||
    auto code = qrcodegen::QrCode::encodeText(code_ss.str().c_str(), qrcodegen::QrCode::Ecc::MEDIUM);
 | 
					 | 
				
			||||||
    const int mod_count = code.getSize();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const int row_size = qrcode_pixel_size * mod_count;
 | 
					 | 
				
			||||||
    cv::Mat image(mod_count, mod_count, CV_8UC1), scaled_image;
 | 
					 | 
				
			||||||
    std::vector<uint8_t> image_encoded;
 | 
					 | 
				
			||||||
    for (int y = 0; y < mod_count; y++) for (int x = 0; x < mod_count; x++)
 | 
					 | 
				
			||||||
        image.at<uint8_t>(x, y) = code.getModule(x, y) ? 0 : 0xff;
 | 
					 | 
				
			||||||
    cv::resize(image, scaled_image, cv::Size(), qrcode_pixel_size, qrcode_pixel_size, cv::INTER_NEAREST);
 | 
					 | 
				
			||||||
    cv::imencode(".png", scaled_image, image_encoded);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return "data:image/png;base64," + Botan::base64_encode(image_encoded);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace api {
 | 
					 | 
				
			||||||
    void auth::tfa_setup(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        Json::Value &json = *req->jsonObject();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            bool mail = dto::json_get<bool>(json, "mail").value();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            auto secret_uchar = rng->random_vec(32);
 | 
					 | 
				
			||||||
            std::vector<char> secret(secret_uchar.data(), secret_uchar.data()+32);
 | 
					 | 
				
			||||||
            user.setTfaSecret(secret);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
            user_mapper.update(user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (mail) {
 | 
					 | 
				
			||||||
                send_mail(user);
 | 
					 | 
				
			||||||
                cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                std::string b32_secret = Botan::base32_encode(secret_uchar);
 | 
					 | 
				
			||||||
                b32_secret.erase(std::remove(b32_secret.begin(), b32_secret.end(), '='), b32_secret.end());
 | 
					 | 
				
			||||||
                std::string code = create_totp_qrcode(user, b32_secret);
 | 
					 | 
				
			||||||
                cbk(dto::Responses::get_tfa_setup_res(b32_secret, code));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Validation error"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void auth::tfa_complete(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        Json::Value &json = *req->jsonObject();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            bool mail = dto::json_get<bool>(json, "mail").value();
 | 
					 | 
				
			||||||
            uint32_t code = std::stoi(dto::json_get<std::string>(json, "code").value());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            user.setTfaType(mail ? db::tfaTypes::EMAIL : db::tfaTypes::TOTP);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!verify2fa(user, code))
 | 
					 | 
				
			||||||
                return cbk(dto::Responses::get_unauth_res("Incorrect 2fa"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
            user_mapper.update(user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            revoke_all(user);
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Validation error"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void auth::tfa_disable(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
        user.setTfaType(db::tfaTypes::NONE);
 | 
					 | 
				
			||||||
        user_mapper.update(user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        revoke_all(user);
 | 
					 | 
				
			||||||
        cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma clang diagnostic pop
 | 
					 | 
				
			||||||
@@ -1,135 +0,0 @@
 | 
				
			|||||||
#pragma clang diagnostic push
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "readability-make-member-function-const"
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <botan/argon2.h>
 | 
					 | 
				
			||||||
#include <botan/totp.h>
 | 
					 | 
				
			||||||
#include <jwt-cpp/traits/kazuho-picojson/traits.h>
 | 
					 | 
				
			||||||
#include <jwt-cpp/jwt.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "controllers/controllers.h"
 | 
					 | 
				
			||||||
#include "db/db.h"
 | 
					 | 
				
			||||||
#include "dto/dto.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace api {
 | 
					 | 
				
			||||||
    void auth::login(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        Json::Value &json = *req->jsonObject();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            std::string username = dto::json_get<std::string>(json, "username").value();
 | 
					 | 
				
			||||||
            std::string password = dto::json_get<std::string>(json, "password").value();
 | 
					 | 
				
			||||||
            std::optional<std::string> otp = dto::json_get<std::string>(json, "otp");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            auto db = drogon::app().getDbClient();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            db::MapperUser user_mapper(db);
 | 
					 | 
				
			||||||
            auto db_users = user_mapper.findBy(
 | 
					 | 
				
			||||||
                    db::Criteria(db::User::Cols::_name, db::CompareOps::EQ, username) &&
 | 
					 | 
				
			||||||
                    db::Criteria(db::User::Cols::_gitlab, db::CompareOps::EQ, 0)
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            if (db_users.empty()) {
 | 
					 | 
				
			||||||
                cbk(dto::Responses::get_unauth_res("Invalid username or password"));
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            db::User &db_user = db_users.at(0);
 | 
					 | 
				
			||||||
            if (!Botan::argon2_check_pwhash(password.c_str(), password.size(), db_user.getValueOfPassword())) {
 | 
					 | 
				
			||||||
                cbk(dto::Responses::get_unauth_res("Invalid username or password"));
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (db::User_getEnumRole(db_user) == db::UserRole::DISABLED) {
 | 
					 | 
				
			||||||
                cbk(dto::Responses::get_unauth_res("Account is disabled"));
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const auto tfa = db::User_getEnumTfaType(db_user);
 | 
					 | 
				
			||||||
            if (tfa != db::tfaTypes::NONE) {
 | 
					 | 
				
			||||||
                if (!otp.has_value()) {
 | 
					 | 
				
			||||||
                    if (tfa == db::tfaTypes::EMAIL) send_mail(db_user);
 | 
					 | 
				
			||||||
                    return cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (!verify2fa(db_user, std::stoi(otp.value())))
 | 
					 | 
				
			||||||
                    return cbk(dto::Responses::get_unauth_res("Incorrect 2fa"));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_login_res(get_token(db_user)));
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Validation error"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void auth::signup(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        Json::Value &json = *req->jsonObject();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            std::string username = dto::json_get<std::string>(json, "username").value();
 | 
					 | 
				
			||||||
            std::string password = dto::json_get<std::string>(json, "password").value();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            auto existing_users = user_mapper.count(
 | 
					 | 
				
			||||||
                    db::Criteria(db::User::Cols::_name, db::CompareOps::EQ, username) &&
 | 
					 | 
				
			||||||
                    db::Criteria(db::User::Cols::_gitlab, db::CompareOps::EQ, 0)
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            if (existing_users != 0) {
 | 
					 | 
				
			||||||
                cbk(dto::Responses::get_badreq_res("Username is already taken"));
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //std::string hash = Botan::argon2_generate_pwhash(password.c_str(), password.size(), *rng, 1, 256*1024, 2);
 | 
					 | 
				
			||||||
            std::string hash = Botan::argon2_generate_pwhash(password.c_str(), password.size(), *rng, 1, 16*1024, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            db::User new_user;
 | 
					 | 
				
			||||||
            new_user.setName(username);
 | 
					 | 
				
			||||||
            new_user.setPassword(hash);
 | 
					 | 
				
			||||||
            new_user.setGitlab(0);
 | 
					 | 
				
			||||||
            new_user.setRole(db::UserRole::DISABLED);
 | 
					 | 
				
			||||||
            new_user.setRootId(0);
 | 
					 | 
				
			||||||
            new_user.setTfaType(db::tfaTypes::NONE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            user_mapper.insert(new_user);
 | 
					 | 
				
			||||||
            generate_root(new_user);
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Validation error"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void auth::refresh(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        db::Token token = dto::get_token(req);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        db::MapperToken token_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
        token_mapper.deleteOne(token);
 | 
					 | 
				
			||||||
        cbk(dto::Responses::get_login_res( get_token(user)));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void auth::logout_all(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        revoke_all(user);
 | 
					 | 
				
			||||||
        cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void auth::change_password(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        Json::Value &json = *req->jsonObject();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            std::string old_pw = dto::json_get<std::string>(json, "oldPassword").value();
 | 
					 | 
				
			||||||
            std::string new_pw = dto::json_get<std::string>(json, "newPassword").value();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            auto db = drogon::app().getDbClient();
 | 
					 | 
				
			||||||
            db::MapperUser user_mapper(db);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!Botan::argon2_check_pwhash(old_pw.c_str(), old_pw.size(), user.getValueOfPassword()))
 | 
					 | 
				
			||||||
                return cbk(dto::Responses::get_unauth_res("Old password is wrong"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            std::string hash = Botan::argon2_generate_pwhash(new_pw.c_str(), new_pw.size(), *rng, 1, 256*1024, 2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            user.setPassword(hash);
 | 
					 | 
				
			||||||
            user_mapper.update(user);
 | 
					 | 
				
			||||||
            revoke_all(user);
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Validation error"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma clang diagnostic pop
 | 
					 | 
				
			||||||
@@ -1,110 +0,0 @@
 | 
				
			|||||||
#pragma clang diagnostic push
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "readability-make-member-function-const"
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <chrono>
 | 
					 | 
				
			||||||
#include <filesystem>
 | 
					 | 
				
			||||||
#include <fstream>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <botan/uuid.h>
 | 
					 | 
				
			||||||
#include <botan/totp.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if defined(BOTAN_HAS_SYSTEM_RNG)
 | 
					 | 
				
			||||||
#include <botan/system_rng.h>
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
#include <botan/auto_rng.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <jwt-cpp/traits/kazuho-picojson/traits.h>
 | 
					 | 
				
			||||||
#include <jwt-cpp/jwt.h>
 | 
					 | 
				
			||||||
#include <SMTPMail.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "controllers/controllers.h"
 | 
					 | 
				
			||||||
#include "db/db.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace api {
 | 
					 | 
				
			||||||
#if defined(BOTAN_HAS_SYSTEM_RNG)
 | 
					 | 
				
			||||||
    std::unique_ptr<Botan::RNG> auth::rng = std::make_unique<Botan::System_RNG>();
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
    std::unique_ptr<Botan::RNG> auth::rng = std::make_unique<Botan::AutoSeeded_RNG>();
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool auth::verify2fa(const db::User& user, uint32_t totp) {
 | 
					 | 
				
			||||||
        size_t allowed_skew = db::User_getEnumTfaType(user) == db::tfaTypes::TOTP ? 0 : 10;
 | 
					 | 
				
			||||||
        const auto& totp_secret = (const std::vector<uint8_t>&) user.getValueOfTfaSecret();
 | 
					 | 
				
			||||||
        return Botan::TOTP(Botan::OctetString(totp_secret)).verify_totp(totp, std::chrono::system_clock::now(), allowed_skew);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void auth::send_mail(const db::User& user) {
 | 
					 | 
				
			||||||
        std::time_t t = std::time(nullptr);
 | 
					 | 
				
			||||||
        const auto& totp_secret = (const std::vector<uint8_t>&) user.getValueOfTfaSecret();
 | 
					 | 
				
			||||||
        char totp[16];
 | 
					 | 
				
			||||||
        std::snprintf(totp, 16, "%06d", Botan::TOTP(Botan::OctetString(totp_secret)).generate_totp(t));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto config = drogon::app().getCustomConfig();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        drogon::app().getPlugin<SMTPMail>()->sendEmail(
 | 
					 | 
				
			||||||
                config["smtp_server"].asString(),
 | 
					 | 
				
			||||||
                (uint16_t)config["smtp_port"].asUInt64(),
 | 
					 | 
				
			||||||
                "fileserver@mattv.de",
 | 
					 | 
				
			||||||
                user.getValueOfName(),
 | 
					 | 
				
			||||||
                "MFileserver - Email 2fa code",
 | 
					 | 
				
			||||||
                "Your code is: " + std::string(totp) +"\r\nIt is valid for 5 Minutes",
 | 
					 | 
				
			||||||
                config["smtp_user"].asString(),
 | 
					 | 
				
			||||||
                config["smtp_password"].asString(),
 | 
					 | 
				
			||||||
                false
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::string auth::get_token(const db::User& user) {
 | 
					 | 
				
			||||||
        auto db = drogon::app().getDbClient();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        db::MapperToken token_mapper(db);
 | 
					 | 
				
			||||||
        const auto iat = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch());
 | 
					 | 
				
			||||||
        const auto exp = iat + std::chrono::hours{24};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        db::Token new_token;
 | 
					 | 
				
			||||||
        new_token.setOwnerId(user.getValueOfId());
 | 
					 | 
				
			||||||
        new_token.setExp(exp.count());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        token_mapper.insert(new_token);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return jwt::create<jwt::traits::kazuho_picojson>()
 | 
					 | 
				
			||||||
                .set_type("JWT")
 | 
					 | 
				
			||||||
                .set_payload_claim("sub", picojson::value((int64_t)user.getValueOfId()))
 | 
					 | 
				
			||||||
                .set_payload_claim("jti", picojson::value((int64_t)new_token.getValueOfId()))
 | 
					 | 
				
			||||||
                .set_issued_at(std::chrono::system_clock::from_time_t(iat.count()))
 | 
					 | 
				
			||||||
                .set_expires_at(std::chrono::system_clock::from_time_t(exp.count()))
 | 
					 | 
				
			||||||
                .sign(jwt::algorithm::hs256{get_jwt_secret()});
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void auth::generate_root(db::User& user) {
 | 
					 | 
				
			||||||
        db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto node = fs::create_node("", user, false, std::nullopt, true);
 | 
					 | 
				
			||||||
        user.setRootId(std::get<db::INode>(node).getValueOfId());
 | 
					 | 
				
			||||||
        user_mapper.update(user);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void auth::revoke_all(const db::User& user) {
 | 
					 | 
				
			||||||
        db::MapperToken token_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
         token_mapper.deleteBy(db::Criteria(db::Token::Cols::_owner_id, db::CompareOps::EQ, user.getValueOfId()));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::string auth::get_jwt_secret() {
 | 
					 | 
				
			||||||
        static std::string token;
 | 
					 | 
				
			||||||
        if (token.empty()) {
 | 
					 | 
				
			||||||
            if (!std::filesystem::exists("jwt.secret")) {
 | 
					 | 
				
			||||||
                auto new_token = rng->random_vec(128);
 | 
					 | 
				
			||||||
                std::ofstream file("jwt.secret", std::ofstream::binary);
 | 
					 | 
				
			||||||
                file.write((const char*)new_token.data(), (std::streamsize)new_token.size());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            std::ifstream file("jwt.secret", std::ifstream::binary);
 | 
					 | 
				
			||||||
            token = {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return token;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma clang diagnostic pop
 | 
					 | 
				
			||||||
@@ -1,135 +0,0 @@
 | 
				
			|||||||
#pragma clang diagnostic push
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "performance-unnecessary-value-param"
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "readability-make-member-function-const"
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "controllers/controllers.h"
 | 
					 | 
				
			||||||
#include "dto/dto.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace config {
 | 
					 | 
				
			||||||
    std::string get_id() {
 | 
					 | 
				
			||||||
        static std::string val = drogon::app().getCustomConfig()["gitlab_id"].asString();
 | 
					 | 
				
			||||||
        return val;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    std::string get_secret() {
 | 
					 | 
				
			||||||
        static std::string val = drogon::app().getCustomConfig()["gitlab_secret"].asString();
 | 
					 | 
				
			||||||
        return val;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    std::string get_url() {
 | 
					 | 
				
			||||||
        static std::string val = drogon::app().getCustomConfig()["gitlab_url"].asString();
 | 
					 | 
				
			||||||
        return val;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    std::string get_api_url() {
 | 
					 | 
				
			||||||
        static std::string val = drogon::app().getCustomConfig()["gitlab_api_url"].asString();
 | 
					 | 
				
			||||||
        return val;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    std::string get_redirect_url() {
 | 
					 | 
				
			||||||
        static std::string val = drogon::app().getCustomConfig()["gitlab_redirect_url"].asString();
 | 
					 | 
				
			||||||
        return val;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
std::string get_redirect_uri() {
 | 
					 | 
				
			||||||
    std::stringstream ss;
 | 
					 | 
				
			||||||
    ss << config::get_redirect_url()
 | 
					 | 
				
			||||||
       << "/api/auth/gitlab_callback";
 | 
					 | 
				
			||||||
    return drogon::utils::urlEncode(ss.str());
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const drogon::HttpClientPtr& get_gitlab_client() {
 | 
					 | 
				
			||||||
    static drogon::HttpClientPtr client = drogon::HttpClient::newHttpClient(config::get_api_url(), drogon::app().getLoop(), false, false);
 | 
					 | 
				
			||||||
    return client;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace api {
 | 
					 | 
				
			||||||
    std::optional<auth::gitlab_tokens> auth::get_gitlab_tokens(const std::string& code_or_token, bool token) {
 | 
					 | 
				
			||||||
        std::stringstream ss;
 | 
					 | 
				
			||||||
        ss << "/oauth/token"
 | 
					 | 
				
			||||||
           << "?redirect_uri=" << get_redirect_uri()
 | 
					 | 
				
			||||||
           << "&client_id=" << config::get_id()
 | 
					 | 
				
			||||||
           << "&client_secret=" << config::get_secret()
 | 
					 | 
				
			||||||
           << (token ? "&refresh_token=" : "&code=") << code_or_token
 | 
					 | 
				
			||||||
           << "&grant_type=" << (token ? "refresh_token" : "authorization_code");
 | 
					 | 
				
			||||||
        auto gitlab_req = drogon::HttpRequest::newHttpRequest();
 | 
					 | 
				
			||||||
        gitlab_req->setPathEncode(false);
 | 
					 | 
				
			||||||
        gitlab_req->setPath(ss.str());
 | 
					 | 
				
			||||||
        gitlab_req->setMethod(drogon::HttpMethod::Post);
 | 
					 | 
				
			||||||
        auto res_tuple = get_gitlab_client()->sendRequest(gitlab_req);
 | 
					 | 
				
			||||||
        auto res = res_tuple.second;
 | 
					 | 
				
			||||||
        if ((res->statusCode() != drogon::HttpStatusCode::k200OK) && (res->statusCode() != drogon::HttpStatusCode::k201Created))
 | 
					 | 
				
			||||||
            return std::nullopt;
 | 
					 | 
				
			||||||
        auto json = *res->jsonObject();
 | 
					 | 
				
			||||||
        return std::make_optional<gitlab_tokens>(
 | 
					 | 
				
			||||||
                json["access_token"].as<std::string>(),
 | 
					 | 
				
			||||||
                json["refresh_token"].as<std::string>()
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::optional<auth::gitlab_user> auth::get_gitlab_user(const std::string& at) {
 | 
					 | 
				
			||||||
        auto gitlab_req = drogon::HttpRequest::newHttpRequest();
 | 
					 | 
				
			||||||
        gitlab_req->setPath("/api/v4/user");
 | 
					 | 
				
			||||||
        gitlab_req->addHeader("Authorization", "Bearer " + at);
 | 
					 | 
				
			||||||
        gitlab_req->setMethod(drogon::HttpMethod::Get);
 | 
					 | 
				
			||||||
        auto res_tuple = get_gitlab_client()->sendRequest(gitlab_req);
 | 
					 | 
				
			||||||
        auto res = res_tuple.second;
 | 
					 | 
				
			||||||
        if (res->statusCode() != drogon::HttpStatusCode::k200OK)
 | 
					 | 
				
			||||||
            return std::nullopt;
 | 
					 | 
				
			||||||
        auto json = *res->jsonObject();
 | 
					 | 
				
			||||||
        return std::make_optional<gitlab_user>(
 | 
					 | 
				
			||||||
                json["username"].as<std::string>(),
 | 
					 | 
				
			||||||
                json.get("is_admin", false).as<bool>()
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void auth::gitlab(req_type, cbk_type cbk) {
 | 
					 | 
				
			||||||
        std::stringstream ss;
 | 
					 | 
				
			||||||
        ss << config::get_url() << "/oauth/authorize"
 | 
					 | 
				
			||||||
           << "?redirect_uri=" << get_redirect_uri()
 | 
					 | 
				
			||||||
           << "&client_id=" << config::get_id()
 | 
					 | 
				
			||||||
           << "&scope=read_user&response_type=code";
 | 
					 | 
				
			||||||
        cbk(drogon::HttpResponse::newRedirectionResponse(ss.str()));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::string disabled_resp = "<!DOCTYPE html><html><h2>Your account is disabled, please contact an admin. <a href=\"/login\">Go to login page</a></h2></html>";
 | 
					 | 
				
			||||||
    void auth::gitlab_callback(req_type, cbk_type cbk, std::string code) {
 | 
					 | 
				
			||||||
        auto tokens =  get_gitlab_tokens(code, false);
 | 
					 | 
				
			||||||
        if (!tokens.has_value())
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_unauth_res("Invalid code"));
 | 
					 | 
				
			||||||
        auto info =  get_gitlab_user(tokens->at);
 | 
					 | 
				
			||||||
        if (!info.has_value())
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_unauth_res("Invalid code"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
        auto db_users =  user_mapper.findBy(
 | 
					 | 
				
			||||||
                db::Criteria(db::User::Cols::_name, db::CompareOps::EQ, info->name) &&
 | 
					 | 
				
			||||||
                db::Criteria(db::User::Cols::_gitlab, db::CompareOps::EQ, 1)
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (db_users.empty()) {
 | 
					 | 
				
			||||||
            db::User new_user;
 | 
					 | 
				
			||||||
            new_user.setName(info->name);
 | 
					 | 
				
			||||||
            new_user.setPassword("");
 | 
					 | 
				
			||||||
            new_user.setGitlab(1);
 | 
					 | 
				
			||||||
            new_user.setRole(info->is_admin ? db::UserRole::ADMIN : db::UserRole::DISABLED);
 | 
					 | 
				
			||||||
            new_user.setRootId(0);
 | 
					 | 
				
			||||||
            new_user.setTfaType(db::tfaTypes::NONE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            user_mapper.insert(new_user);
 | 
					 | 
				
			||||||
            generate_root(new_user);
 | 
					 | 
				
			||||||
            db_users.push_back(new_user);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        db::User& db_user = db_users.at(0);
 | 
					 | 
				
			||||||
        db_user.setGitlabAt(tokens->at);
 | 
					 | 
				
			||||||
        db_user.setGitlabRt(tokens->rt);
 | 
					 | 
				
			||||||
        user_mapper.update(db_user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (db::User_getEnumRole(db_user) == db::UserRole::DISABLED)
 | 
					 | 
				
			||||||
            return cbk(drogon::HttpResponse::newFileResponse((unsigned char*)disabled_resp.data(), disabled_resp.size(), "", drogon::ContentType::CT_TEXT_HTML));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const std::string& token = get_token(db_user);
 | 
					 | 
				
			||||||
        cbk(drogon::HttpResponse::newRedirectionResponse("/set_token?token="+token));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma clang diagnostic pop
 | 
					 | 
				
			||||||
@@ -1,161 +0,0 @@
 | 
				
			|||||||
#ifndef BACKEND_CONTROLLERS_H
 | 
					 | 
				
			||||||
#define BACKEND_CONTROLLERS_H
 | 
					 | 
				
			||||||
#include <variant>
 | 
					 | 
				
			||||||
#include <unordered_map>
 | 
					 | 
				
			||||||
#include <shared_mutex>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <drogon/drogon.h>
 | 
					 | 
				
			||||||
#include <botan/rng.h>
 | 
					 | 
				
			||||||
#include <msd/channel.hpp>
 | 
					 | 
				
			||||||
#include <trantor/net/EventLoopThread.h>
 | 
					 | 
				
			||||||
#include <kubazip/zip/zip.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "db/db.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using req_type = const drogon::HttpRequestPtr&;
 | 
					 | 
				
			||||||
using cbk_type = std::function<void(const drogon::HttpResponsePtr &)>&&;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace api {
 | 
					 | 
				
			||||||
class admin : public drogon::HttpController<admin> {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    METHOD_LIST_BEGIN
 | 
					 | 
				
			||||||
        METHOD_ADD(admin::users, "/users", drogon::Get, "Login", "Admin");
 | 
					 | 
				
			||||||
        METHOD_ADD(admin::set_role, "/set_role", drogon::Post, "Login", "Admin");
 | 
					 | 
				
			||||||
        METHOD_ADD(admin::logout, "/logout", drogon::Post, "Login", "Admin");
 | 
					 | 
				
			||||||
        METHOD_ADD(admin::delete_user, "/delete", drogon::Post, "Login", "Admin");
 | 
					 | 
				
			||||||
        METHOD_ADD(admin::disable_2fa, "/disable_2fa", drogon::Post, "Login", "Admin");
 | 
					 | 
				
			||||||
        METHOD_ADD(admin::is_admin, "/is_admin", drogon::Get, "Login", "Admin");
 | 
					 | 
				
			||||||
        METHOD_ADD(admin::get_token, "/get_token/{}", drogon::Get, "Login", "Admin");
 | 
					 | 
				
			||||||
    METHOD_LIST_END
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void users(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void set_role(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void logout(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void delete_user(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void disable_2fa(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void is_admin(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void get_token(req_type, cbk_type, uint64_t user);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class auth : public drogon::HttpController<auth> {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    METHOD_LIST_BEGIN
 | 
					 | 
				
			||||||
        METHOD_ADD(auth::gitlab, "/gitlab", drogon::Get);
 | 
					 | 
				
			||||||
        METHOD_ADD(auth::gitlab_callback, "/gitlab_callback?code={}", drogon::Get);
 | 
					 | 
				
			||||||
        METHOD_ADD(auth::signup, "/signup", drogon::Post);
 | 
					 | 
				
			||||||
        METHOD_ADD(auth::login, "/login", drogon::Post);
 | 
					 | 
				
			||||||
        METHOD_ADD(auth::refresh, "/refresh", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(auth::tfa_setup, "/2fa/setup", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(auth::tfa_complete, "/2fa/complete", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(auth::tfa_disable, "/2fa/disable", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(auth::change_password, "/change_password", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(auth::logout_all, "/logout_all", drogon::Post, "Login");
 | 
					 | 
				
			||||||
    METHOD_LIST_END
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    struct gitlab_tokens {
 | 
					 | 
				
			||||||
        gitlab_tokens(std::string at, std::string rt) : at(std::move(at)), rt(std::move(rt)) {}
 | 
					 | 
				
			||||||
        std::string at, rt;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    struct gitlab_user {
 | 
					 | 
				
			||||||
        gitlab_user(std::string name, bool isAdmin) : name(std::move(name)), is_admin(isAdmin) {}
 | 
					 | 
				
			||||||
        std::string name;
 | 
					 | 
				
			||||||
        bool is_admin;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static std::unique_ptr<Botan::RNG> rng;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static std::optional<gitlab_tokens> get_gitlab_tokens(const std::string&, bool token);
 | 
					 | 
				
			||||||
    static std::optional<gitlab_user> get_gitlab_user(const std::string&);
 | 
					 | 
				
			||||||
    static bool verify2fa(const db::User&, uint32_t totp);
 | 
					 | 
				
			||||||
    static void send_mail(const db::User&);
 | 
					 | 
				
			||||||
    static std::string get_token(const db::User&);
 | 
					 | 
				
			||||||
    static void generate_root(db::User&);
 | 
					 | 
				
			||||||
    static void revoke_all(const db::User&);
 | 
					 | 
				
			||||||
    static std::string get_jwt_secret();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void gitlab(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void gitlab_callback(req_type, cbk_type, std::string code);
 | 
					 | 
				
			||||||
    void signup(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void login(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void refresh(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void tfa_setup(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void tfa_complete(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void tfa_disable(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void change_password(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void logout_all(req_type, cbk_type);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class fs : public drogon::HttpController<fs> {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    METHOD_LIST_BEGIN
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::root, "/root", drogon::Get, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::node, "/node/{}", drogon::Get, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::path, "/path/{}", drogon::Get, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::create_node_req<false>, "/createFolder", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::create_node_req<true>, "/createFile", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::delete_node_req, "/delete/{}", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::upload, "/upload/{}", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::create_zip, "/create_zip", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::download, "/download", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::download_multi, "/download_multi", drogon::Post, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::download_preview, "/download_preview/{}", drogon::Get, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(fs::get_type, "/get_type/{}", drogon::Get, "Login");
 | 
					 | 
				
			||||||
    METHOD_LIST_END
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    enum class create_node_error {
 | 
					 | 
				
			||||||
        INVALID_NAME,
 | 
					 | 
				
			||||||
        INVALID_PARENT,
 | 
					 | 
				
			||||||
        FILE_PARENT
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    struct mutex_stream {
 | 
					 | 
				
			||||||
        std::stringstream ss;
 | 
					 | 
				
			||||||
        std::mutex mutex;
 | 
					 | 
				
			||||||
        bool done = false;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static std::optional<db::INode> get_node(uint64_t node);
 | 
					 | 
				
			||||||
    static std::optional<db::INode> get_node_and_validate(const db::User& user, uint64_t node);
 | 
					 | 
				
			||||||
    static std::vector<db::INode> get_children(const db::INode& parent);
 | 
					 | 
				
			||||||
    static std::variant<db::INode, fs::create_node_error, std::tuple<bool, uint64_t>>
 | 
					 | 
				
			||||||
        create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &parent, bool force = false);
 | 
					 | 
				
			||||||
    static void delete_node(db::INode node, msd::channel<std::string>& chan, bool allow_root = false);
 | 
					 | 
				
			||||||
    static std::shared_ptr<std::shared_mutex> get_user_mutex(uint64_t user_id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void root(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void node(req_type, cbk_type, uint64_t node);
 | 
					 | 
				
			||||||
    void path(req_type, cbk_type, uint64_t node);
 | 
					 | 
				
			||||||
    template<bool file> void create_node_req(req_type req, cbk_type cbk);
 | 
					 | 
				
			||||||
    void delete_node_req(req_type, cbk_type, uint64_t node);
 | 
					 | 
				
			||||||
    void upload(req_type, cbk_type, uint64_t node);
 | 
					 | 
				
			||||||
    void create_zip(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void download(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void download_multi(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void download_preview(req_type, cbk_type, uint64_t node);
 | 
					 | 
				
			||||||
    void get_type(req_type, cbk_type, uint64_t node);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
    static trantor::EventLoop* get_zip_loop();
 | 
					 | 
				
			||||||
    static trantor::EventLoop* get_delete_loop();
 | 
					 | 
				
			||||||
    static void generate_path(db::INode node, std::string& str);
 | 
					 | 
				
			||||||
    static Json::Value generate_path(db::INode node);
 | 
					 | 
				
			||||||
    static uint64_t calc_total_size(const db::INode& base);
 | 
					 | 
				
			||||||
    static void add_to_zip(struct zip_t* zip, const std::string& key, const db::INode& node, const std::string& path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static uint64_t next_temp_id;
 | 
					 | 
				
			||||||
    static std::unordered_map<std::string, std::string> zip_to_temp_map;
 | 
					 | 
				
			||||||
    static std::unordered_map<std::string, std::tuple<std::string, uint64_t, uint64_t>> in_progress_zips;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class user : public drogon::HttpController<user> {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    METHOD_LIST_BEGIN
 | 
					 | 
				
			||||||
        METHOD_ADD(user::info, "/info", drogon::Get, "Login");
 | 
					 | 
				
			||||||
        METHOD_ADD(user::delete_user, "/delete", drogon::Post, "Login");
 | 
					 | 
				
			||||||
    METHOD_LIST_END
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void info(req_type, cbk_type);
 | 
					 | 
				
			||||||
    void delete_user(req_type, cbk_type);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif //BACKEND_CONTROLLERS_H
 | 
					 | 
				
			||||||
@@ -1,257 +0,0 @@
 | 
				
			|||||||
#include <filesystem>
 | 
					 | 
				
			||||||
#include <fstream>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "controllers/controllers.h"
 | 
					 | 
				
			||||||
#include "dto/dto.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
char windows_invalid_chars[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F<>:\"/\\|";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace api {
 | 
					 | 
				
			||||||
    uint64_t fs::next_temp_id = 0;
 | 
					 | 
				
			||||||
    std::unordered_map<std::string, std::string> fs::zip_to_temp_map;
 | 
					 | 
				
			||||||
    std::unordered_map<std::string, std::tuple<std::string, uint64_t, uint64_t>> fs::in_progress_zips;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::optional<db::INode> fs::get_node(uint64_t node) {
 | 
					 | 
				
			||||||
        db::MapperInode inode_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            return inode_mapper.findByPrimaryKey(node);
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            return std::nullopt;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::optional<db::INode> fs::get_node_and_validate(const db::User &user, uint64_t node) {
 | 
					 | 
				
			||||||
        auto inode = get_node(node);
 | 
					 | 
				
			||||||
        if (!inode.has_value()) return std::nullopt;
 | 
					 | 
				
			||||||
        if (inode->getValueOfOwnerId() != user.getValueOfId()) return std::nullopt;
 | 
					 | 
				
			||||||
        return inode;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::vector<db::INode> fs::get_children(const db::INode& parent) {
 | 
					 | 
				
			||||||
        db::MapperInode inode_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
        return inode_mapper.findBy(db::Criteria(db::INode::Cols::_parent_id, db::CompareOps::EQ, parent.getValueOfId()));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::variant<db::INode, fs::create_node_error, std::tuple<bool, uint64_t>>
 | 
					 | 
				
			||||||
    fs::create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &parent, bool force) {
 | 
					 | 
				
			||||||
        // Stolen from https://github.com/boostorg/filesystem/blob/develop/src/portability.cpp
 | 
					 | 
				
			||||||
        if (!force)
 | 
					 | 
				
			||||||
            if (name.empty() || name[0] == ' ' || name.find_first_of(windows_invalid_chars, 0, sizeof(windows_invalid_chars)) != std::string::npos || *(name.end() - 1) == ' ' || *(name.end() - 1) == '.' || name == "." || name == "..")
 | 
					 | 
				
			||||||
                return {create_node_error::INVALID_NAME};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        db::INode node;
 | 
					 | 
				
			||||||
        node.setIsFile(file ? 1 : 0);
 | 
					 | 
				
			||||||
        node.setName(name);
 | 
					 | 
				
			||||||
        node.setOwnerId(owner.getValueOfId());
 | 
					 | 
				
			||||||
        node.setHasPreview(0);
 | 
					 | 
				
			||||||
        if (parent.has_value()) {
 | 
					 | 
				
			||||||
            auto parent_node =  get_node_and_validate(owner, *parent);
 | 
					 | 
				
			||||||
            if (!parent_node.has_value())
 | 
					 | 
				
			||||||
                return {create_node_error::INVALID_PARENT};
 | 
					 | 
				
			||||||
            if (parent_node->getValueOfIsFile() != 0)
 | 
					 | 
				
			||||||
                return {create_node_error::FILE_PARENT};
 | 
					 | 
				
			||||||
            auto children = get_children(*parent_node);
 | 
					 | 
				
			||||||
            for (const auto& child : children)
 | 
					 | 
				
			||||||
                if (child.getValueOfName() == name)
 | 
					 | 
				
			||||||
                    return {std::make_tuple(
 | 
					 | 
				
			||||||
                                child.getValueOfIsFile() != 0,
 | 
					 | 
				
			||||||
                                child.getValueOfId()
 | 
					 | 
				
			||||||
                            )};
 | 
					 | 
				
			||||||
            node.setParentId(*parent);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        db::MapperInode inode_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
        inode_mapper.insert(node);
 | 
					 | 
				
			||||||
        return {node};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::delete_node(db::INode node, msd::channel<std::string>& chan, bool allow_root) {
 | 
					 | 
				
			||||||
        if (node.getValueOfParentId() == 0 && (!allow_root)) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        db::MapperInode inode_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const auto delete_file = [&chan, &inode_mapper](const db::INode& node) {
 | 
					 | 
				
			||||||
            std::string entry = "Deleting ";
 | 
					 | 
				
			||||||
            generate_path(node, entry);
 | 
					 | 
				
			||||||
            entry >> chan;
 | 
					 | 
				
			||||||
            std::filesystem::path p("./files");
 | 
					 | 
				
			||||||
            p /= std::to_string(node.getValueOfId());
 | 
					 | 
				
			||||||
            std::filesystem::remove(p);
 | 
					 | 
				
			||||||
            if (node.getValueOfHasPreview() != 0)
 | 
					 | 
				
			||||||
                std::filesystem::remove(p.string() + "_preview.png");
 | 
					 | 
				
			||||||
            inode_mapper.deleteOne(node);
 | 
					 | 
				
			||||||
            std::string(" Done\n") >> chan;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::stack<db::INode> queue, files, folders;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (node.getValueOfIsFile() == 0) queue.push(node);
 | 
					 | 
				
			||||||
        else files.push(node);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while (!queue.empty()) {
 | 
					 | 
				
			||||||
            while (!files.empty()) {
 | 
					 | 
				
			||||||
                delete_file(files.top());
 | 
					 | 
				
			||||||
                files.pop();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            std::string entry = "Deleting ";
 | 
					 | 
				
			||||||
            generate_path(queue.top(), entry);
 | 
					 | 
				
			||||||
            entry += "\n";
 | 
					 | 
				
			||||||
            entry >> chan;
 | 
					 | 
				
			||||||
            auto children = get_children(queue.top());
 | 
					 | 
				
			||||||
            folders.push(queue.top());
 | 
					 | 
				
			||||||
            queue.pop();
 | 
					 | 
				
			||||||
            for (const auto& child : children) {
 | 
					 | 
				
			||||||
                if (child.getValueOfIsFile() == 0) queue.push(child);
 | 
					 | 
				
			||||||
                else files.push(child);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while (!files.empty()) {
 | 
					 | 
				
			||||||
            delete_file(files.top());
 | 
					 | 
				
			||||||
            files.pop();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while (!folders.empty()) {
 | 
					 | 
				
			||||||
            inode_mapper.deleteOne(folders.top());
 | 
					 | 
				
			||||||
            folders.pop();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::shared_ptr<std::shared_mutex> fs::get_user_mutex(uint64_t user_id) {
 | 
					 | 
				
			||||||
        static std::unordered_map<uint64_t, std::shared_ptr<std::shared_mutex>> mutexes;
 | 
					 | 
				
			||||||
        static std::mutex mutexes_mutex;
 | 
					 | 
				
			||||||
        std::lock_guard guard(mutexes_mutex);
 | 
					 | 
				
			||||||
        return (*mutexes.try_emplace(user_id, std::make_shared<std::shared_mutex>()).first).second;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    trantor::EventLoop* fs::get_zip_loop() {
 | 
					 | 
				
			||||||
        static bool init_done = false;
 | 
					 | 
				
			||||||
        static trantor::EventLoopThread loop("ZipEventLoop");
 | 
					 | 
				
			||||||
        if (!init_done) {
 | 
					 | 
				
			||||||
            init_done = true;
 | 
					 | 
				
			||||||
            loop.run();
 | 
					 | 
				
			||||||
            loop.getLoop()->runEvery(30*60, []{
 | 
					 | 
				
			||||||
                for (const auto& entry : std::filesystem::directory_iterator("./temp")) {
 | 
					 | 
				
			||||||
                    if (!entry.is_regular_file()) continue;
 | 
					 | 
				
			||||||
                    const std::string file_name = "./temp/" + entry.path().filename().string();
 | 
					 | 
				
			||||||
                    const auto& progress_pos = std::find_if(in_progress_zips.begin(), in_progress_zips.end(),
 | 
					 | 
				
			||||||
                                                            [&file_name](const std::pair<std::string, std::tuple<std::string, uint64_t, uint64_t>>& entry) {
 | 
					 | 
				
			||||||
                                                                return std::get<0>(entry.second) == file_name;
 | 
					 | 
				
			||||||
                                                            }
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                    if (progress_pos != in_progress_zips.end()) return;
 | 
					 | 
				
			||||||
                    const auto& zip_map_pos = std::find_if(zip_to_temp_map.begin(), zip_to_temp_map.end(),
 | 
					 | 
				
			||||||
                                                           [&file_name](const std::pair<std::string, std::string>& entry){
 | 
					 | 
				
			||||||
                                                               return entry.second == file_name;
 | 
					 | 
				
			||||||
                                                           }
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                    if (zip_map_pos != zip_to_temp_map.end()) return;
 | 
					 | 
				
			||||||
                    std::filesystem::remove(entry.path());
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return loop.getLoop();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    trantor::EventLoop* fs::get_delete_loop() {
 | 
					 | 
				
			||||||
        static bool init_done = false;
 | 
					 | 
				
			||||||
        static trantor::EventLoopThread loop("DeleteEventLoop");
 | 
					 | 
				
			||||||
        if (!init_done) {
 | 
					 | 
				
			||||||
            init_done = true;
 | 
					 | 
				
			||||||
            loop.run();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return loop.getLoop();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::generate_path(db::INode node, std::string& str) {
 | 
					 | 
				
			||||||
        db::MapperInode inode_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
        std::stack<db::INode> path;
 | 
					 | 
				
			||||||
        path.push(node);
 | 
					 | 
				
			||||||
        while (node.getParentId() != nullptr) {
 | 
					 | 
				
			||||||
            node = inode_mapper.findByPrimaryKey(node.getValueOfParentId());
 | 
					 | 
				
			||||||
            path.push(node);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        while (!path.empty()) {
 | 
					 | 
				
			||||||
            const db::INode& seg = path.top();
 | 
					 | 
				
			||||||
            str += seg.getValueOfName();
 | 
					 | 
				
			||||||
            if (seg.getValueOfIsFile() == 0) str += "/";
 | 
					 | 
				
			||||||
            path.pop();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Json::Value fs::generate_path(db::INode node) {
 | 
					 | 
				
			||||||
        Json::Value segments = Json::Value(Json::ValueType::arrayValue);
 | 
					 | 
				
			||||||
        db::MapperInode inode_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
        std::stack<db::INode> path;
 | 
					 | 
				
			||||||
        path.push(node);
 | 
					 | 
				
			||||||
        while (node.getParentId() != nullptr) {
 | 
					 | 
				
			||||||
            node = inode_mapper.findByPrimaryKey(node.getValueOfParentId());
 | 
					 | 
				
			||||||
            path.push(node);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        while (!path.empty()) {
 | 
					 | 
				
			||||||
            const db::INode& seg = path.top();
 | 
					 | 
				
			||||||
            if (seg.getParentId() == nullptr) {
 | 
					 | 
				
			||||||
                Json::Value json_seg;
 | 
					 | 
				
			||||||
                json_seg["path"] = "/";
 | 
					 | 
				
			||||||
                json_seg["node"] = seg.getValueOfId();
 | 
					 | 
				
			||||||
                segments.append(json_seg);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                Json::Value json_seg;
 | 
					 | 
				
			||||||
                json_seg["path"] = seg.getValueOfName();
 | 
					 | 
				
			||||||
                json_seg["node"] = seg.getValueOfId();
 | 
					 | 
				
			||||||
                segments.append(json_seg);
 | 
					 | 
				
			||||||
                if (seg.getValueOfIsFile() == 0) {
 | 
					 | 
				
			||||||
                    json_seg.removeMember("node");
 | 
					 | 
				
			||||||
                    json_seg["path"] = "/";
 | 
					 | 
				
			||||||
                    segments.append(json_seg);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            path.pop();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Json::Value resp;
 | 
					 | 
				
			||||||
        resp["segments"] = segments;
 | 
					 | 
				
			||||||
        return resp;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    uint64_t fs::calc_total_size(const db::INode& base) {
 | 
					 | 
				
			||||||
        uint64_t size = 0;
 | 
					 | 
				
			||||||
        std::stack<db::INode> queue;
 | 
					 | 
				
			||||||
        queue.push(base);
 | 
					 | 
				
			||||||
        while (!queue.empty()) {
 | 
					 | 
				
			||||||
            const db::INode& node = queue.top();
 | 
					 | 
				
			||||||
            if (node.getValueOfIsFile() == 0) {
 | 
					 | 
				
			||||||
                auto children = api::fs::get_children(node);
 | 
					 | 
				
			||||||
                queue.pop();
 | 
					 | 
				
			||||||
                for (const auto& child : children) {
 | 
					 | 
				
			||||||
                    if (child.getValueOfIsFile() == 0) queue.push(child);
 | 
					 | 
				
			||||||
                    else if (child.getSize()) size += child.getValueOfSize();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                size += node.getValueOfSize();
 | 
					 | 
				
			||||||
                queue.pop();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return size;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::add_to_zip(struct zip_t* zip, const std::string& key, const db::INode& node, const std::string& path) {
 | 
					 | 
				
			||||||
        if (node.getValueOfIsFile() == 0) {
 | 
					 | 
				
			||||||
            std::string new_path = path + node.getValueOfName() + "/";
 | 
					 | 
				
			||||||
            zip_entry_opencasesensitive(zip, new_path.c_str());
 | 
					 | 
				
			||||||
            zip_entry_close(zip);
 | 
					 | 
				
			||||||
            auto children = api::fs::get_children(node);
 | 
					 | 
				
			||||||
            for (const auto& child : children)
 | 
					 | 
				
			||||||
                add_to_zip(zip, key, child, new_path);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            zip_entry_opencasesensitive(zip, (path + node.getValueOfName()).c_str());
 | 
					 | 
				
			||||||
            std::ifstream file("./files/" + std::to_string(node.getValueOfId()), std::ifstream::binary);
 | 
					 | 
				
			||||||
            std::vector<char> buffer(64*1024);
 | 
					 | 
				
			||||||
            while (!file.eof()) {
 | 
					 | 
				
			||||||
                file.read(buffer.data(), (std::streamsize)buffer.size());
 | 
					 | 
				
			||||||
                auto read = file.gcount();
 | 
					 | 
				
			||||||
                zip_entry_write(zip, buffer.data(), read);
 | 
					 | 
				
			||||||
                std::get<1>(in_progress_zips[key]) += read;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            zip_entry_close(zip);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,349 +0,0 @@
 | 
				
			|||||||
#include <filesystem>
 | 
					 | 
				
			||||||
#include <fstream>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <opencv2/opencv.hpp>
 | 
					 | 
				
			||||||
#include <botan/base64.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "controllers/controllers.h"
 | 
					 | 
				
			||||||
#include "dto/dto.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types#common_image_file_types
 | 
					 | 
				
			||||||
const std::unordered_map<std::string, std::string> mime_type_map = {
 | 
					 | 
				
			||||||
        { ".apng" , "image/apng" },
 | 
					 | 
				
			||||||
        { ".avif" , "image/avif" },
 | 
					 | 
				
			||||||
        { ".bmp"  , "image/bmp"  },
 | 
					 | 
				
			||||||
        { ".gif"  , "image/gif"  },
 | 
					 | 
				
			||||||
        { ".jpg"  , "image/jpeg" },
 | 
					 | 
				
			||||||
        { ".jpeg" , "image/jpeg" },
 | 
					 | 
				
			||||||
        { ".jfif" , "image/jpeg" },
 | 
					 | 
				
			||||||
        { ".pjpeg", "image/jpeg" },
 | 
					 | 
				
			||||||
        { ".pjp"  , "image/jpeg" },
 | 
					 | 
				
			||||||
        { ".png"  , "image/png"  },
 | 
					 | 
				
			||||||
        { ".svg"  , "image/svg"  },
 | 
					 | 
				
			||||||
        { ".webp" , "image/webp" },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        { ".aac"  , "audio/aac"  },
 | 
					 | 
				
			||||||
        { ".flac" , "audio/flac" },
 | 
					 | 
				
			||||||
        { ".mp3"  , "audio/mp3"  },
 | 
					 | 
				
			||||||
        { ".m4a"  , "audio/mp4"  },
 | 
					 | 
				
			||||||
        { ".oga"  , "audio/ogg"  },
 | 
					 | 
				
			||||||
        { ".ogg"  , "audio/ogg"  },
 | 
					 | 
				
			||||||
        { ".wav"  , "audio/wav"  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        { ".3gp"  , "video/3gpp" },
 | 
					 | 
				
			||||||
        { ".mpg"  , "video/mpeg" },
 | 
					 | 
				
			||||||
        { ".mpeg" , "video/mpeg" },
 | 
					 | 
				
			||||||
        { ".mp4"  , "video/mp4"  },
 | 
					 | 
				
			||||||
        { ".m4v"  , "video/mp4"  },
 | 
					 | 
				
			||||||
        { ".m4p"  , "video/mp4"  },
 | 
					 | 
				
			||||||
        { ".ogv"  , "video/ogg"  },
 | 
					 | 
				
			||||||
        { ".mov"  , "video/quicktime" },
 | 
					 | 
				
			||||||
        { ".webm" , "video/webm" },
 | 
					 | 
				
			||||||
        { ".mkv"  , "video/x-matroska" },
 | 
					 | 
				
			||||||
        { ".mk3d" , "video/x-matroska" },
 | 
					 | 
				
			||||||
        { ".mks"  , "video/x-matroska" },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        { ".pdf"  , "application/pdf" }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template<typename InputIt>
 | 
					 | 
				
			||||||
std::string join_string(InputIt first, InputIt last, const std::string& separator = ",") {
 | 
					 | 
				
			||||||
    std::ostringstream result;
 | 
					 | 
				
			||||||
    if (first != last) {
 | 
					 | 
				
			||||||
        result << *first;
 | 
					 | 
				
			||||||
        while (++first != last) {
 | 
					 | 
				
			||||||
            result << separator << *first;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return result.str();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace api {
 | 
					 | 
				
			||||||
    void fs::root(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        cbk(dto::Responses::get_root_res(user.getValueOfRootId()));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::node(req_type req, cbk_type cbk, uint64_t node) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
        auto inode =  get_node_and_validate(user, node);
 | 
					 | 
				
			||||||
        if (!inode.has_value())
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_badreq_res("Unknown node"));
 | 
					 | 
				
			||||||
        auto dto_node = dto::Responses::GetNodeEntry(*inode);
 | 
					 | 
				
			||||||
        std::vector<dto::Responses::GetNodeEntry> children;
 | 
					 | 
				
			||||||
        if (!dto_node.is_file) for (const db::INode& child : get_children(*inode)) children.emplace_back(child);
 | 
					 | 
				
			||||||
        cbk(dto::Responses::get_node_res(dto_node, children));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::path(req_type req, cbk_type cbk, uint64_t node) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
        auto inode = get_node_and_validate(user, node);
 | 
					 | 
				
			||||||
        if (!inode.has_value())
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Unknown node"));
 | 
					 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
            auto path = generate_path(*inode);
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_success_res(path));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    template<bool file>
 | 
					 | 
				
			||||||
    void fs::create_node_req(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        Json::Value& json = *req->jsonObject();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            uint64_t parent = dto::json_get<uint64_t>(json, "parent").value();
 | 
					 | 
				
			||||||
            std::string name = dto::json_get<std::string>(json, "name").value();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            auto new_node = create_node(name, user, file, std::make_optional(parent));
 | 
					 | 
				
			||||||
            if (std::holds_alternative<db::INode>(new_node))
 | 
					 | 
				
			||||||
                cbk(dto::Responses::get_new_node_res(std::get<db::INode>(new_node).getValueOfId()));
 | 
					 | 
				
			||||||
            else if (std::holds_alternative<create_node_error>(new_node))
 | 
					 | 
				
			||||||
                switch (std::get<create_node_error>(new_node)) {
 | 
					 | 
				
			||||||
                    case create_node_error::INVALID_NAME: return cbk(dto::Responses::get_badreq_res("Invalid name"));
 | 
					 | 
				
			||||||
                    case create_node_error::INVALID_PARENT: return cbk(dto::Responses::get_badreq_res("Invalid parent"));
 | 
					 | 
				
			||||||
                    case create_node_error::FILE_PARENT: return cbk(dto::Responses::get_badreq_res("Parent is file"));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            else {
 | 
					 | 
				
			||||||
                auto tuple = std::get<std::tuple<bool, uint64_t>>(new_node);
 | 
					 | 
				
			||||||
                cbk(dto::Responses::get_node_exists_res(std::get<1>(tuple), std::get<0>(tuple)));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Validation error"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::delete_node_req(req_type req, cbk_type cbk, uint64_t node) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        std::unique_lock lock(*get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
        auto inode = get_node_and_validate(user, node);
 | 
					 | 
				
			||||||
        if (!inode.has_value())
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Unknown node"));
 | 
					 | 
				
			||||||
        else if (inode->getValueOfParentId() == 0)
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Can't delete root"));
 | 
					 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
            auto chan = std::make_shared<msd::channel<std::string>>();
 | 
					 | 
				
			||||||
            std::string("Waiting in queue...\n") >> (*chan);
 | 
					 | 
				
			||||||
            get_delete_loop()->queueInLoop([chan, inode=*inode, user=user.getValueOfId()]{
 | 
					 | 
				
			||||||
                std::unique_lock lock(*get_user_mutex(user));
 | 
					 | 
				
			||||||
                delete_node(inode, *chan);
 | 
					 | 
				
			||||||
                chan->close();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            cbk(drogon::HttpResponse::newStreamResponse([chan](char* buf, std::size_t size) -> std::size_t{
 | 
					 | 
				
			||||||
                if (buf == nullptr) return 0;
 | 
					 | 
				
			||||||
                if (chan->closed() && chan->empty()) return 0;
 | 
					 | 
				
			||||||
                std::string buffer;
 | 
					 | 
				
			||||||
                buffer << *chan;
 | 
					 | 
				
			||||||
                if (buffer.empty()) return 0;
 | 
					 | 
				
			||||||
                std::size_t read = std::min(size, buffer.size());
 | 
					 | 
				
			||||||
                std::memcpy(buf, buffer.data(), read); // NOLINT(bugprone-not-null-terminated-result)
 | 
					 | 
				
			||||||
                return read;
 | 
					 | 
				
			||||||
            }));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::upload(req_type req, cbk_type cbk, uint64_t node) {
 | 
					 | 
				
			||||||
        constexpr int image_height = 256;
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto inode = get_node_and_validate(user, node);
 | 
					 | 
				
			||||||
        if (!inode.has_value())
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_badreq_res("Unknown node"));
 | 
					 | 
				
			||||||
        if (inode->getValueOfIsFile() == 0)
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_badreq_res("Can't upload to a directory"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        drogon::MultiPartParser mpp;
 | 
					 | 
				
			||||||
        if (mpp.parse(req) != 0)
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_badreq_res("Failed to parse files"));
 | 
					 | 
				
			||||||
        if (mpp.getFiles().size() != 1)
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_badreq_res("Exactly 1 file needed"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const drogon::HttpFile& file = mpp.getFiles().at(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::filesystem::path p("./files");
 | 
					 | 
				
			||||||
        p /= std::to_string(inode->getValueOfId());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        file.saveAs(p.string());
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            if (file.fileLength() > 100 * 1024 * 1024) throw std::exception();
 | 
					 | 
				
			||||||
            std::filesystem::path filename(inode->getValueOfName());
 | 
					 | 
				
			||||||
            std::string ext = filename.extension().string();
 | 
					 | 
				
			||||||
            std::transform(ext.begin(), ext.end(), ext.begin(), tolower);
 | 
					 | 
				
			||||||
            const std::string& mime = mime_type_map.at(ext);
 | 
					 | 
				
			||||||
            if (!mime.starts_with("image")) throw std::exception();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cv::_InputArray image_arr(file.fileData(), (int) file.fileLength());
 | 
					 | 
				
			||||||
            cv::Mat image = cv::imdecode(image_arr, cv::IMREAD_COLOR);
 | 
					 | 
				
			||||||
            if (!image.empty()) {
 | 
					 | 
				
			||||||
                float h_ration = ((float) image_height) / ((float) image.rows);
 | 
					 | 
				
			||||||
                cv::Mat preview;
 | 
					 | 
				
			||||||
                cv::resize(image, preview, cv::Size((int) (((float) image.cols) * h_ration), image_height), 0, 0, cv::INTER_AREA);
 | 
					 | 
				
			||||||
                cv::imwrite(p.string() + "_preview.png", preview);
 | 
					 | 
				
			||||||
                inode->setHasPreview(1);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {}
 | 
					 | 
				
			||||||
        inode->setSize(file.fileLength());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        db::MapperInode inode_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
        inode_mapper.update(*inode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::create_zip(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Json::Value& json = *req->jsonObject();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            if (!json.isMember("nodes")) throw std::exception();
 | 
					 | 
				
			||||||
            Json::Value node_arr = json["nodes"];
 | 
					 | 
				
			||||||
            if (!node_arr.isArray()) throw std::exception();
 | 
					 | 
				
			||||||
            std::vector<uint64_t> node_ids;
 | 
					 | 
				
			||||||
            for (const auto& node : node_arr)
 | 
					 | 
				
			||||||
                node_ids.push_back(node.asUInt64());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            std::vector<db::INode> nodes;
 | 
					 | 
				
			||||||
            std::transform(node_ids.begin(), node_ids.end(), std::back_inserter(nodes), [&user](uint64_t node) {
 | 
					 | 
				
			||||||
                return api::fs::get_node_and_validate(user, node).value();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            std::string key = join_string(node_ids.begin(), node_ids.end());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (zip_to_temp_map.contains(key)) return cbk(dto::Responses::get_create_zip_done_res());
 | 
					 | 
				
			||||||
            if (in_progress_zips.contains(key)) {
 | 
					 | 
				
			||||||
                auto progress = in_progress_zips.at(key);
 | 
					 | 
				
			||||||
                return cbk(dto::Responses::get_create_zip_done_res(std::get<1>(progress), std::get<2>(progress)));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            std::string file_name = "./temp/fs_" + std::to_string(next_temp_id++) + ".zip";
 | 
					 | 
				
			||||||
            in_progress_zips.emplace(key, std::make_tuple(file_name, 0, 1));
 | 
					 | 
				
			||||||
            get_zip_loop()->queueInLoop([key = std::move(key), nodes = std::move(nodes), file_name = std::move(file_name), user=user.getValueOfId()]{
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    std::shared_lock lock(*get_user_mutex(user));
 | 
					 | 
				
			||||||
                    uint64_t size = 0;
 | 
					 | 
				
			||||||
                    for (const auto& node : nodes) size += calc_total_size(node);
 | 
					 | 
				
			||||||
                    std::get<2>(in_progress_zips.at(key)) = size;
 | 
					 | 
				
			||||||
                    struct zip_t* zip = zip_open(file_name.c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
 | 
					 | 
				
			||||||
                    for (const db::INode& node : nodes)
 | 
					 | 
				
			||||||
                        add_to_zip(zip, key, node, "");
 | 
					 | 
				
			||||||
                    zip_close(zip);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                zip_to_temp_map.emplace(key, file_name);
 | 
					 | 
				
			||||||
                in_progress_zips.erase(key);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_create_zip_done_res(0, 1));
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Validation error"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::download(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto node_id = req->getOptionalParameter<uint64_t>("id");
 | 
					 | 
				
			||||||
        if (!node_id.has_value()) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Invalid node"));
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        auto inode = get_node_and_validate(user, *node_id);
 | 
					 | 
				
			||||||
        if (!inode.has_value()) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Invalid node"));
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (inode->getValueOfIsFile() != 0) {
 | 
					 | 
				
			||||||
            std::filesystem::path p("./files");
 | 
					 | 
				
			||||||
            p /= std::to_string(inode->getValueOfId());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cbk(drogon::HttpResponse::newFileResponse(
 | 
					 | 
				
			||||||
                    p.string(),
 | 
					 | 
				
			||||||
                    inode->getValueOfName()
 | 
					 | 
				
			||||||
            ));
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
                std::string key = std::to_string(inode->getValueOfId());
 | 
					 | 
				
			||||||
                std::string file = zip_to_temp_map.at(key);
 | 
					 | 
				
			||||||
                zip_to_temp_map.erase(key);
 | 
					 | 
				
			||||||
                cbk(drogon::HttpResponse::newFileResponse(
 | 
					 | 
				
			||||||
                        file,
 | 
					 | 
				
			||||||
                        inode->getValueOfName() + ".zip"
 | 
					 | 
				
			||||||
                ));
 | 
					 | 
				
			||||||
            } catch (const std::exception&) {
 | 
					 | 
				
			||||||
                cbk(dto::Responses::get_badreq_res("Invalid node"));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::download_multi(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto node_ids_str = req->getOptionalParameter<std::string>("id");
 | 
					 | 
				
			||||||
        if (!node_ids_str.has_value())
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_badreq_res("No nodes"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::stringstream node_ids_ss(*node_ids_str);
 | 
					 | 
				
			||||||
        std::string temp;
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            while (std::getline(node_ids_ss, temp, ','))
 | 
					 | 
				
			||||||
                if (!get_node_and_validate(user, std::stoull(temp)).has_value()) throw std::exception();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            std::string file = zip_to_temp_map.at(*node_ids_str);
 | 
					 | 
				
			||||||
            zip_to_temp_map.erase(*node_ids_str);
 | 
					 | 
				
			||||||
            cbk(drogon::HttpResponse::newFileResponse(
 | 
					 | 
				
			||||||
                    file,
 | 
					 | 
				
			||||||
                    "files.zip"
 | 
					 | 
				
			||||||
            ));
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Invalid nodes"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::download_preview(req_type req, cbk_type cbk, uint64_t node) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto inode = get_node_and_validate(user, node);
 | 
					 | 
				
			||||||
        if (!inode.has_value())
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_badreq_res("Unknown node"));
 | 
					 | 
				
			||||||
        if (inode->getValueOfHasPreview() == 0)
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_badreq_res("No preview"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::filesystem::path p("./files");
 | 
					 | 
				
			||||||
        p /= std::to_string(inode->getValueOfId()) + "_preview.png";
 | 
					 | 
				
			||||||
        std::ifstream file(p, std::ios::in | std::ios::binary);
 | 
					 | 
				
			||||||
        std::vector<uint8_t> image((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cbk(dto::Responses::get_download_base64_res("data:image/png;base64," + Botan::base64_encode(image)));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void fs::get_type(req_type req, cbk_type cbk, uint64_t node){
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto inode = get_node_and_validate(user, node);
 | 
					 | 
				
			||||||
        if (!inode.has_value())
 | 
					 | 
				
			||||||
            return cbk(dto::Responses::get_badreq_res("Unknown node"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::filesystem::path name(inode->getValueOfName());
 | 
					 | 
				
			||||||
        std::string ext = name.extension().string();
 | 
					 | 
				
			||||||
        std::transform(ext.begin(), ext.end(), ext.begin(), tolower);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_type_res(mime_type_map.at(ext)));
 | 
					 | 
				
			||||||
        } catch (const std::exception&) {
 | 
					 | 
				
			||||||
            cbk(dto::Responses::get_badreq_res("Invalid file type"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,33 +0,0 @@
 | 
				
			|||||||
#pragma clang diagnostic push
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "performance-unnecessary-value-param"
 | 
					 | 
				
			||||||
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "controllers.h"
 | 
					 | 
				
			||||||
#include "dto/dto.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace api {
 | 
					 | 
				
			||||||
    void user::info(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        cbk(dto::Responses::get_user_info_res(
 | 
					 | 
				
			||||||
                user.getValueOfName(),
 | 
					 | 
				
			||||||
                user.getValueOfGitlab() != 0,
 | 
					 | 
				
			||||||
                db::User_getEnumTfaType(user) != db::tfaTypes::NONE)
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void user::delete_user(req_type req, cbk_type cbk) {
 | 
					 | 
				
			||||||
        db::MapperUser user_mapper(drogon::app().getDbClient());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        msd::channel<std::string> chan;
 | 
					 | 
				
			||||||
        db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
        auth::revoke_all(user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::unique_lock lock(*fs::get_user_mutex(user.getValueOfId()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fs::delete_node((fs::get_node(user.getValueOfRootId())).value(), chan, true);
 | 
					 | 
				
			||||||
        user_mapper.deleteOne(user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cbk(dto::Responses::get_success_res());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#pragma clang diagnostic pop
 | 
					 | 
				
			||||||
							
								
								
									
										157
									
								
								backend/src/db/connection.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								backend/src/db/connection.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
				
			|||||||
 | 
					use diesel::prelude::*;
 | 
				
			||||||
 | 
					use crate::db::manager::DB_MANAGER;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct DBConnection {
 | 
				
			||||||
 | 
					    db: super::RawDBConnection
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<super::RawDBConnection> for DBConnection {
 | 
				
			||||||
 | 
					    fn from(conn: super::RawDBConnection) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            db: conn
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl DBConnection {
 | 
				
			||||||
 | 
					    // Users
 | 
				
			||||||
 | 
					    pub fn create_user_password(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        name: String,
 | 
				
			||||||
 | 
					        password: String
 | 
				
			||||||
 | 
					    ) -> super::User {
 | 
				
			||||||
 | 
					        let mut new_user: super::User = diesel::insert_into(crate::schema::user::table)
 | 
				
			||||||
 | 
					            .values(super::user::NewUser {
 | 
				
			||||||
 | 
					                name,
 | 
				
			||||||
 | 
					                password,
 | 
				
			||||||
 | 
					                gitlab: false,
 | 
				
			||||||
 | 
					                role: super::UserRole::Disabled,
 | 
				
			||||||
 | 
					                root_id: 0,
 | 
				
			||||||
 | 
					                tfa_type: super::TfaTypes::None,
 | 
				
			||||||
 | 
					                tfa_secret: None,
 | 
				
			||||||
 | 
					                gitlab_at: None,
 | 
				
			||||||
 | 
					                gitlab_rt: None
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .get_result(&mut self.db)
 | 
				
			||||||
 | 
					            .expect("Failed to insert new user");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let root_node = crate::routes::fs::create_node("".to_owned(), &new_user, false, None, true, self).expect("Couldn't create root node");
 | 
				
			||||||
 | 
					        new_user.root_id = root_node.id;
 | 
				
			||||||
 | 
					        self.save_user(&new_user);
 | 
				
			||||||
 | 
					        new_user
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn create_user_gitlab(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        name: String,
 | 
				
			||||||
 | 
					        role: super::UserRole,
 | 
				
			||||||
 | 
					        gitlab_at: String,
 | 
				
			||||||
 | 
					        gitlab_rt: String
 | 
				
			||||||
 | 
					    ) -> super::User {
 | 
				
			||||||
 | 
					        let mut new_user: super::User = diesel::insert_into(crate::schema::user::table)
 | 
				
			||||||
 | 
					            .values(super::user::NewUser {
 | 
				
			||||||
 | 
					                name,
 | 
				
			||||||
 | 
					                password: "".to_owned(),
 | 
				
			||||||
 | 
					                gitlab: true,
 | 
				
			||||||
 | 
					                role,
 | 
				
			||||||
 | 
					                root_id: 0,
 | 
				
			||||||
 | 
					                tfa_type: super::TfaTypes::None,
 | 
				
			||||||
 | 
					                tfa_secret: None,
 | 
				
			||||||
 | 
					                gitlab_at: Some(gitlab_at),
 | 
				
			||||||
 | 
					                gitlab_rt: Some(gitlab_rt)
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .get_result(&mut self.db)
 | 
				
			||||||
 | 
					            .expect("Failed to insert new user");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let root_node = crate::routes::fs::create_node("".to_owned(), &new_user, false, None, true, self).expect("Couldn't create root node");
 | 
				
			||||||
 | 
					        new_user.root_id = root_node.id;
 | 
				
			||||||
 | 
					        self.save_user(&new_user);
 | 
				
			||||||
 | 
					        new_user
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_user(&mut self, _id: i32) -> Option<super::User> {
 | 
				
			||||||
 | 
					        use crate::schema::user::dsl::*;
 | 
				
			||||||
 | 
					        user.find(_id).first(&mut self.db).ok()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn find_user(&mut self, _name: &str, _gitlab: bool) -> Option<super::User> {
 | 
				
			||||||
 | 
					        use crate::schema::user::dsl::*;
 | 
				
			||||||
 | 
					        user.filter(name.eq(name)).filter(gitlab.eq(_gitlab)).first(&mut self.db).ok()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_users(&mut self) -> Vec<super::User> {
 | 
				
			||||||
 | 
					        crate::schema::user::table.load(&mut self.db).expect("Could not load users")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn save_user(&mut self, user: &super::User) {
 | 
				
			||||||
 | 
					        diesel::update(user)
 | 
				
			||||||
 | 
					            .set(user.clone())
 | 
				
			||||||
 | 
					            .execute(&mut self.db)
 | 
				
			||||||
 | 
					            .expect("Failed to save user");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn delete_user(&mut self, user: &super::User) {
 | 
				
			||||||
 | 
					        diesel::delete(user).execute(&mut self.db).expect("Failed to delete user");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Tokens
 | 
				
			||||||
 | 
					    pub fn create_token(&mut self, _owner: i32, _exp: i64) -> super::Token {
 | 
				
			||||||
 | 
					        diesel::insert_into(crate::schema::tokens::table)
 | 
				
			||||||
 | 
					            .values(&super::token::NewToken {
 | 
				
			||||||
 | 
					                owner_id: _owner,
 | 
				
			||||||
 | 
					                exp: _exp
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .get_result(&mut self.db)
 | 
				
			||||||
 | 
					            .expect("Failed to save new token to database")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_token(&mut self, _id: i32) -> Option<super::Token> {
 | 
				
			||||||
 | 
					        use crate::schema::tokens::dsl::*;
 | 
				
			||||||
 | 
					        tokens.find(_id).first(&mut self.db).ok()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn delete_token(&mut self, _id: i32) {
 | 
				
			||||||
 | 
					        use crate::schema::tokens::dsl::*;
 | 
				
			||||||
 | 
					        diesel::delete(tokens.find(_id))
 | 
				
			||||||
 | 
					            .execute(&mut self.db)
 | 
				
			||||||
 | 
					            .expect("Failed to delete token");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn delete_all_tokens(&mut self, _owner: i32) {
 | 
				
			||||||
 | 
					        use crate::schema::tokens::dsl::*;
 | 
				
			||||||
 | 
					        diesel::delete(tokens.filter(owner_id.eq(_owner)))
 | 
				
			||||||
 | 
					            .execute(&mut self.db)
 | 
				
			||||||
 | 
					            .expect("Failed to delete token");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn cleanup_tokens(&mut self) {
 | 
				
			||||||
 | 
					        use crate::schema::tokens::dsl::*;
 | 
				
			||||||
 | 
					        let current_time = chrono::Utc::now().timestamp();
 | 
				
			||||||
 | 
					        diesel::delete(tokens.filter(exp.le(current_time))).execute(&mut self.db).expect("Failed to cleanup tokens");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Nodes
 | 
				
			||||||
 | 
					    pub async fn get_lock(user: i32) -> std::sync::Arc<tokio::sync::RwLock<()>> {
 | 
				
			||||||
 | 
					        DB_MANAGER.get_lock(user).await
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn create_node(&mut self, file: bool, name: String, parent: Option<i32>, owner: i32) -> super::Inode {
 | 
				
			||||||
 | 
					        DB_MANAGER.create_node(&mut self.db, file, name, parent, owner)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_node(&mut self, id: i32) -> Option<super::Inode> {
 | 
				
			||||||
 | 
					        DB_MANAGER.get_node(&mut self.db, id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_children(&mut self, id: i32) -> Vec<super::Inode> {
 | 
				
			||||||
 | 
					        DB_MANAGER.get_children(&mut self.db, id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn save_node(&mut self, node: &super::Inode) {
 | 
				
			||||||
 | 
					        DB_MANAGER.save_node(&mut self.db, node);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn delete_node(&mut self, node: &super::Inode) {
 | 
				
			||||||
 | 
					        DB_MANAGER.delete_node(&mut self.db, node);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,11 +0,0 @@
 | 
				
			|||||||
#include "db.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace db {
 | 
					 | 
				
			||||||
    UserRole User_getEnumRole(const User& user) noexcept {
 | 
					 | 
				
			||||||
        return (UserRole)user.getValueOfRole();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tfaTypes User_getEnumTfaType(const User& user) noexcept {
 | 
					 | 
				
			||||||
        return (tfaTypes)user.getValueOfTfaType();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,41 +0,0 @@
 | 
				
			|||||||
#ifndef BACKEND_DB_H
 | 
					 | 
				
			||||||
#define BACKEND_DB_H
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <utility>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <drogon/utils/coroutine.h>
 | 
					 | 
				
			||||||
#include <drogon/drogon.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "Inode.h"
 | 
					 | 
				
			||||||
#include "Tokens.h"
 | 
					 | 
				
			||||||
#include "User.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace db {
 | 
					 | 
				
			||||||
    enum UserRole : int {
 | 
					 | 
				
			||||||
        ADMIN = 2,
 | 
					 | 
				
			||||||
        USER = 1,
 | 
					 | 
				
			||||||
        DISABLED = 0
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    enum tfaTypes : int {
 | 
					 | 
				
			||||||
        NONE = 0,
 | 
					 | 
				
			||||||
        EMAIL = 1,
 | 
					 | 
				
			||||||
        TOTP = 2
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    using INode = drogon_model::sqlite3::Inode;
 | 
					 | 
				
			||||||
    using Token = drogon_model::sqlite3::Tokens;
 | 
					 | 
				
			||||||
    using User = drogon_model::sqlite3::User;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    using MapperInode = drogon::orm::Mapper<INode>;
 | 
					 | 
				
			||||||
    using MapperToken = drogon::orm::Mapper<Token>;
 | 
					 | 
				
			||||||
    using MapperUser = drogon::orm::Mapper<User>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    using Criteria = drogon::orm::Criteria;
 | 
					 | 
				
			||||||
    using CompareOps = drogon::orm::CompareOperator;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    UserRole User_getEnumRole(const User&) noexcept;
 | 
					 | 
				
			||||||
    tfaTypes User_getEnumTfaType(const User&) noexcept;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif //BACKEND_DB_H
 | 
					 | 
				
			||||||
							
								
								
									
										26
									
								
								backend/src/db/inode.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								backend/src/db/inode.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					use diesel::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Queryable, Identifiable, Eq, PartialEq, Debug, Associations, AsChangeset, Clone)]
 | 
				
			||||||
 | 
					#[diesel(belongs_to(crate::db::User, foreign_key = owner_id))]
 | 
				
			||||||
 | 
					#[diesel(table_name = crate::schema::inode)]
 | 
				
			||||||
 | 
					#[diesel(treat_none_as_null = true)]
 | 
				
			||||||
 | 
					pub struct Inode {
 | 
				
			||||||
 | 
					    pub id: i32,
 | 
				
			||||||
 | 
					    pub is_file: bool,
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub parent_id: Option<i32>,
 | 
				
			||||||
 | 
					    pub owner_id: i32,
 | 
				
			||||||
 | 
					    pub size: Option<i64>,
 | 
				
			||||||
 | 
					    pub has_preview: bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Insertable, Debug)]
 | 
				
			||||||
 | 
					#[diesel(table_name = crate::schema::inode)]
 | 
				
			||||||
 | 
					pub struct NewInode {
 | 
				
			||||||
 | 
					    pub is_file: bool,
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub parent_id: Option<i32>,
 | 
				
			||||||
 | 
					    pub owner_id: i32,
 | 
				
			||||||
 | 
					    pub size: Option<i64>,
 | 
				
			||||||
 | 
					    pub has_preview: bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										109
									
								
								backend/src/db/manager.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								backend/src/db/manager.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					use lazy_static::lazy_static;
 | 
				
			||||||
 | 
					use stretto::Cache;
 | 
				
			||||||
 | 
					use tokio::sync::{Mutex, RwLock};
 | 
				
			||||||
 | 
					use diesel::prelude::*;
 | 
				
			||||||
 | 
					use crate::db::Inode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					lazy_static! {
 | 
				
			||||||
 | 
					    pub(super) static ref DB_MANAGER: DBManager = DBManager::new();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(super) struct DBManager {
 | 
				
			||||||
 | 
					    locks: Mutex<HashMap<i32, Arc<RwLock<()>>>>,
 | 
				
			||||||
 | 
					    node_cache: Cache<i32, Inode>,
 | 
				
			||||||
 | 
					    children_cache: Cache<i32, Vec<Inode>>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl DBManager {
 | 
				
			||||||
 | 
					    fn new() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            locks: Mutex::new(HashMap::new()),
 | 
				
			||||||
 | 
					            node_cache: Cache::new(10000, 1000).expect("Failed to create node cache"),
 | 
				
			||||||
 | 
					            children_cache: Cache::new(1000, 100).expect("Failed to create child cache")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn create_node(&self, db: &mut super::RawDBConnection, file: bool, _name: String, parent: Option<i32>, owner: i32) -> Inode {
 | 
				
			||||||
 | 
					        use crate::schema::inode::dsl::*;
 | 
				
			||||||
 | 
					        let node: Inode = diesel::insert_into(inode)
 | 
				
			||||||
 | 
					            .values(crate::db::inode::NewInode {
 | 
				
			||||||
 | 
					                is_file: file,
 | 
				
			||||||
 | 
					                name: _name,
 | 
				
			||||||
 | 
					                parent_id: parent,
 | 
				
			||||||
 | 
					                owner_id: owner,
 | 
				
			||||||
 | 
					                size: None,
 | 
				
			||||||
 | 
					                has_preview: false
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .get_result(db)
 | 
				
			||||||
 | 
					            .expect("Failed to insert new inode");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.node_cache.insert(node.id, node.clone(), 1);
 | 
				
			||||||
 | 
					        if let Some(parent) = parent {
 | 
				
			||||||
 | 
					            self.children_cache.remove(&parent);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        node
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_node(&self, db: &mut super::RawDBConnection, node_id: i32) -> Option<Inode> {
 | 
				
			||||||
 | 
					        use crate::schema::inode::dsl::*;
 | 
				
			||||||
 | 
					        let node = self.node_cache.get(&node_id);
 | 
				
			||||||
 | 
					        match node {
 | 
				
			||||||
 | 
					            Some(v) => Some(v.value().clone()),
 | 
				
			||||||
 | 
					            None => {
 | 
				
			||||||
 | 
					                let v: Inode = inode.find(node_id).first(db).ok()?;
 | 
				
			||||||
 | 
					                self.node_cache.insert(node_id, v.clone(), 1);
 | 
				
			||||||
 | 
					                Some(v)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_children(&self, db: &mut super::RawDBConnection, node_id: i32) -> Vec<Inode> {
 | 
				
			||||||
 | 
					        use crate::schema::inode::dsl::*;
 | 
				
			||||||
 | 
					        let children = self.children_cache.get(&node_id);
 | 
				
			||||||
 | 
					        match children {
 | 
				
			||||||
 | 
					            Some(v) => v.value().clone(),
 | 
				
			||||||
 | 
					            None => {
 | 
				
			||||||
 | 
					                let v = inode.filter(parent_id.eq(node_id)).load(db).expect("Failed to get children of node");
 | 
				
			||||||
 | 
					                self.children_cache.insert(node_id, v.clone(), 1);
 | 
				
			||||||
 | 
					                v
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn save_node(&self, db: &mut super::RawDBConnection, node: &Inode) {
 | 
				
			||||||
 | 
					        self.node_cache.insert(node.id, node.clone(), 1);
 | 
				
			||||||
 | 
					        diesel::update(node)
 | 
				
			||||||
 | 
					            .set(node.clone())
 | 
				
			||||||
 | 
					            .execute(db)
 | 
				
			||||||
 | 
					            .expect("Failed to save node");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn delete_node(&self, db: &mut super::RawDBConnection, node: &Inode) {
 | 
				
			||||||
 | 
					        if node.is_file {
 | 
				
			||||||
 | 
					            let file_name = format!("./files/{}", node.id);
 | 
				
			||||||
 | 
					            let file = std::path::Path::new(&file_name);
 | 
				
			||||||
 | 
					            let preview_name = format!("./files/{}_preview.jpg", node.id);
 | 
				
			||||||
 | 
					            let preview = std::path::Path::new(&preview_name);
 | 
				
			||||||
 | 
					            if file.exists() {
 | 
				
			||||||
 | 
					                std::fs::remove_file(file).expect("Failed to delete file");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if preview.exists() {
 | 
				
			||||||
 | 
					                std::fs::remove_file(preview).expect("Failed to delete preview");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        diesel::delete(node).execute(db).expect("Failed to delete node");
 | 
				
			||||||
 | 
					        self.node_cache.remove(&node.id);
 | 
				
			||||||
 | 
					        self.children_cache.remove(&node.id);
 | 
				
			||||||
 | 
					        if let Some(p) = node.parent_id { self.children_cache.remove(&p); }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn get_lock(&self, user: i32) -> Arc<RwLock<()>> {
 | 
				
			||||||
 | 
					        self.locks.lock().await
 | 
				
			||||||
 | 
					            .entry(user)
 | 
				
			||||||
 | 
					            .or_insert_with(|| Arc::new(RwLock::new(())))
 | 
				
			||||||
 | 
					            .clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								backend/src/db/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								backend/src/db/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					mod inode;
 | 
				
			||||||
 | 
					mod token;
 | 
				
			||||||
 | 
					mod user;
 | 
				
			||||||
 | 
					pub mod manager;
 | 
				
			||||||
 | 
					mod connection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use diesel::connection::SimpleConnection;
 | 
				
			||||||
 | 
					use diesel::sqlite::SqliteConnection;
 | 
				
			||||||
 | 
					use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
 | 
				
			||||||
 | 
					use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
 | 
				
			||||||
 | 
					use warp::Filter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use inode::Inode;
 | 
				
			||||||
 | 
					pub use token::Token;
 | 
				
			||||||
 | 
					pub use user::{User, TfaTypes, UserRole};
 | 
				
			||||||
 | 
					use crate::routes::AppError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type RawDBConnection = PooledConnection<ConnectionManager<SqliteConnection>>;
 | 
				
			||||||
 | 
					pub type DBPool = Pool<ConnectionManager<SqliteConnection>>;
 | 
				
			||||||
 | 
					pub use connection::DBConnection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct ConnectionOptions {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error> for ConnectionOptions {
 | 
				
			||||||
 | 
					    fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<(), diesel::r2d2::Error> {
 | 
				
			||||||
 | 
					        conn.batch_execute("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 15000;")
 | 
				
			||||||
 | 
					            .map_err(diesel::r2d2::Error::QueryError)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_pool() -> Pool<ConnectionManager<SqliteConnection>> {
 | 
				
			||||||
 | 
					    Pool::builder()
 | 
				
			||||||
 | 
					        .connection_customizer(Box::new(ConnectionOptions {}))
 | 
				
			||||||
 | 
					        .build(ConnectionManager::<SqliteConnection>::new("sqlite.db"))
 | 
				
			||||||
 | 
					        .expect("Failed to open db")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn run_migrations(db: &mut RawDBConnection) {
 | 
				
			||||||
 | 
					    db.run_pending_migrations(MIGRATIONS).expect("Failed to run migrations");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn with_db(pool: DBPool) -> impl Filter<Extract=(DBConnection, ), Error=warp::reject::Rejection> + Clone {
 | 
				
			||||||
 | 
					    warp::any()
 | 
				
			||||||
 | 
					        .map(move || pool.clone())
 | 
				
			||||||
 | 
					        .and_then(|pool: DBPool| async move {
 | 
				
			||||||
 | 
					            match pool.get() {
 | 
				
			||||||
 | 
					                Ok(v) => Ok(DBConnection::from(v)),
 | 
				
			||||||
 | 
					                Err(_) => AppError::InternalError("Failed to get a database connection").err()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								backend/src/db/token.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								backend/src/db/token.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					use diesel::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Queryable, Identifiable, Eq, PartialEq, Debug, Associations, AsChangeset)]
 | 
				
			||||||
 | 
					#[diesel(belongs_to(crate::db::User, foreign_key = owner_id))]
 | 
				
			||||||
 | 
					#[diesel(table_name = crate::schema::tokens)]
 | 
				
			||||||
 | 
					#[diesel(treat_none_as_null = true)]
 | 
				
			||||||
 | 
					pub struct Token {
 | 
				
			||||||
 | 
					    pub id: i32,
 | 
				
			||||||
 | 
					    pub owner_id: i32,
 | 
				
			||||||
 | 
					    pub exp: i64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Insertable, Debug)]
 | 
				
			||||||
 | 
					#[diesel(table_name = crate::schema::tokens)]
 | 
				
			||||||
 | 
					pub struct NewToken {
 | 
				
			||||||
 | 
					    pub owner_id: i32,
 | 
				
			||||||
 | 
					    pub exp: i64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										113
									
								
								backend/src/db/user.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								backend/src/db/user.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					use diesel::backend::RawValue;
 | 
				
			||||||
 | 
					use diesel::deserialize::{FromSql, FromSqlRow};
 | 
				
			||||||
 | 
					use diesel::prelude::*;
 | 
				
			||||||
 | 
					use diesel::serialize::{IsNull, Output, ToSql};
 | 
				
			||||||
 | 
					use diesel::sql_types::SmallInt;
 | 
				
			||||||
 | 
					use diesel::sqlite::Sqlite;
 | 
				
			||||||
 | 
					use serde_repr::{Deserialize_repr, Serialize_repr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[repr(i16)]
 | 
				
			||||||
 | 
					#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize_repr, Serialize_repr, FromSqlRow)]
 | 
				
			||||||
 | 
					pub enum UserRole {
 | 
				
			||||||
 | 
					    Disabled = 0,
 | 
				
			||||||
 | 
					    User = 1,
 | 
				
			||||||
 | 
					    Admin = 2
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FromSql<SmallInt, Sqlite> for UserRole {
 | 
				
			||||||
 | 
					    fn from_sql(bytes: RawValue<'_, Sqlite>) -> diesel::deserialize::Result<Self> {
 | 
				
			||||||
 | 
					        match i16::from_sql(bytes)? {
 | 
				
			||||||
 | 
					            1 => Ok(UserRole::User),
 | 
				
			||||||
 | 
					            2 => Ok(UserRole::Admin),
 | 
				
			||||||
 | 
					            _ => Ok(UserRole::Disabled)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ToSql<SmallInt, Sqlite> for UserRole {
 | 
				
			||||||
 | 
					    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> diesel::serialize::Result {
 | 
				
			||||||
 | 
					        let val: i16 = (*self).into();
 | 
				
			||||||
 | 
					        out.set_value(val as i32);
 | 
				
			||||||
 | 
					        Ok(IsNull::No)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<UserRole> for i16 {
 | 
				
			||||||
 | 
					    fn from(v: UserRole) -> Self {
 | 
				
			||||||
 | 
					        match v {
 | 
				
			||||||
 | 
					            UserRole::Disabled => 0,
 | 
				
			||||||
 | 
					            UserRole::User => 1,
 | 
				
			||||||
 | 
					            UserRole::Admin => 2
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[repr(i16)]
 | 
				
			||||||
 | 
					#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize_repr, Serialize_repr, FromSqlRow)]
 | 
				
			||||||
 | 
					pub enum TfaTypes {
 | 
				
			||||||
 | 
					    None = 0,
 | 
				
			||||||
 | 
					    Email = 1,
 | 
				
			||||||
 | 
					    Totp = 2
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FromSql<SmallInt, Sqlite> for TfaTypes {
 | 
				
			||||||
 | 
					    fn from_sql(bytes: RawValue<'_, Sqlite>) -> diesel::deserialize::Result<Self> {
 | 
				
			||||||
 | 
					        match i16::from_sql(bytes)? {
 | 
				
			||||||
 | 
					            1 => Ok(TfaTypes::Email),
 | 
				
			||||||
 | 
					            2 => Ok(TfaTypes::Totp),
 | 
				
			||||||
 | 
					            _ => Ok(TfaTypes::None)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ToSql<SmallInt, Sqlite> for TfaTypes {
 | 
				
			||||||
 | 
					    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> diesel::serialize::Result {
 | 
				
			||||||
 | 
					        let val: i16 = (*self).into();
 | 
				
			||||||
 | 
					        out.set_value(val as i32);
 | 
				
			||||||
 | 
					        Ok(IsNull::No)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<TfaTypes> for i16 {
 | 
				
			||||||
 | 
					    fn from(v: TfaTypes) -> Self {
 | 
				
			||||||
 | 
					        match v {
 | 
				
			||||||
 | 
					            TfaTypes::None => 0,
 | 
				
			||||||
 | 
					            TfaTypes::Email => 1,
 | 
				
			||||||
 | 
					            TfaTypes::Totp => 2
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Queryable, Identifiable, Eq, PartialEq, Debug, AsChangeset, Clone)]
 | 
				
			||||||
 | 
					#[diesel(table_name = crate::schema::user)]
 | 
				
			||||||
 | 
					#[diesel(treat_none_as_null = true)]
 | 
				
			||||||
 | 
					pub struct User {
 | 
				
			||||||
 | 
					    pub id: i32,
 | 
				
			||||||
 | 
					    pub gitlab: bool,
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub password: String,
 | 
				
			||||||
 | 
					    #[diesel(serialize_as = i16)]
 | 
				
			||||||
 | 
					    pub role: UserRole,
 | 
				
			||||||
 | 
					    pub root_id: i32,
 | 
				
			||||||
 | 
					    #[diesel(serialize_as = i16)]
 | 
				
			||||||
 | 
					    pub tfa_type: TfaTypes,
 | 
				
			||||||
 | 
					    pub tfa_secret: Option<Vec<u8>>,
 | 
				
			||||||
 | 
					    pub gitlab_at: Option<String>,
 | 
				
			||||||
 | 
					    pub gitlab_rt: Option<String>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Insertable, Debug)]
 | 
				
			||||||
 | 
					#[diesel(table_name = crate::schema::user)]
 | 
				
			||||||
 | 
					pub struct NewUser {
 | 
				
			||||||
 | 
					    pub gitlab: bool,
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub password: String,
 | 
				
			||||||
 | 
					    #[diesel(serialize_as = i16)]
 | 
				
			||||||
 | 
					    pub role: UserRole,
 | 
				
			||||||
 | 
					    pub root_id: i32,
 | 
				
			||||||
 | 
					    #[diesel(serialize_as = i16)]
 | 
				
			||||||
 | 
					    pub tfa_type: TfaTypes,
 | 
				
			||||||
 | 
					    pub tfa_secret: Option<Vec<u8>>,
 | 
				
			||||||
 | 
					    pub gitlab_at: Option<String>,
 | 
				
			||||||
 | 
					    pub gitlab_rt: Option<String>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										194
									
								
								backend/src/dto.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								backend/src/dto.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,194 @@
 | 
				
			|||||||
 | 
					#[allow(non_snake_case)]
 | 
				
			||||||
 | 
					pub mod responses {
 | 
				
			||||||
 | 
					    use serde::{self, Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct Error {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        pub message: String
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct Success {
 | 
				
			||||||
 | 
					        pub statusCode: i32
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct Login {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        pub jwt: String
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct TfaSetup {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        pub secret: String,
 | 
				
			||||||
 | 
					        pub qrCode: String
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct UserInfo {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        pub name: String,
 | 
				
			||||||
 | 
					        pub gitlab: bool,
 | 
				
			||||||
 | 
					        pub tfaEnabled: bool
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct AdminUsersEntry {
 | 
				
			||||||
 | 
					        pub id: i32,
 | 
				
			||||||
 | 
					        pub gitlab: bool,
 | 
				
			||||||
 | 
					        pub name: String,
 | 
				
			||||||
 | 
					        pub role: crate::db::UserRole,
 | 
				
			||||||
 | 
					        pub tfaEnabled: bool
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct AdminUsers {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        pub users: Vec<AdminUsersEntry>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct Root {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        pub rootId: i32
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct GetNodeEntry {
 | 
				
			||||||
 | 
					        pub id: i32,
 | 
				
			||||||
 | 
					        pub name: String,
 | 
				
			||||||
 | 
					        pub isFile: bool,
 | 
				
			||||||
 | 
					        pub preview: bool,
 | 
				
			||||||
 | 
					        pub parent: Option<i32>,
 | 
				
			||||||
 | 
					        pub size: Option<i64>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct GetNode {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        pub id: i32,
 | 
				
			||||||
 | 
					        pub name: String,
 | 
				
			||||||
 | 
					        pub isFile: bool,
 | 
				
			||||||
 | 
					        pub preview: bool,
 | 
				
			||||||
 | 
					        pub parent: Option<i32>,
 | 
				
			||||||
 | 
					        pub size: Option<i64>,
 | 
				
			||||||
 | 
					        pub children: Option<Vec<GetNodeEntry>>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct NewNode {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        pub id: i32
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct NodeExists {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        pub id: i32,
 | 
				
			||||||
 | 
					        pub exists: bool,
 | 
				
			||||||
 | 
					        pub isFile: bool
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct DownloadBase64 {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        pub data: String
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct Type {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        #[serde(rename = "type")]
 | 
				
			||||||
 | 
					        pub _type: String
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct CreateZipDone {
 | 
				
			||||||
 | 
					        pub statusCode: i32,
 | 
				
			||||||
 | 
					        pub done: bool,
 | 
				
			||||||
 | 
					        pub progress: Option<u64>,
 | 
				
			||||||
 | 
					        pub total: Option<u64>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct GetPathSegment {
 | 
				
			||||||
 | 
					        pub path: String,
 | 
				
			||||||
 | 
					        pub node: Option<i32>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct GetPath {
 | 
				
			||||||
 | 
					        pub segments: Vec<GetPathSegment>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[allow(non_snake_case)]
 | 
				
			||||||
 | 
					pub mod requests {
 | 
				
			||||||
 | 
					    use serde::{self, Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct Admin {
 | 
				
			||||||
 | 
					        pub user: i32
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct AdminSetRole {
 | 
				
			||||||
 | 
					        pub user: i32,
 | 
				
			||||||
 | 
					        pub role: crate::db::UserRole
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct SignUp {
 | 
				
			||||||
 | 
					        pub username: String,
 | 
				
			||||||
 | 
					        pub password: String
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct Login {
 | 
				
			||||||
 | 
					        pub username: String,
 | 
				
			||||||
 | 
					        pub password: String,
 | 
				
			||||||
 | 
					        pub otp: Option<String>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct TfaSetup {
 | 
				
			||||||
 | 
					        pub mail: bool
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct TfaComplete {
 | 
				
			||||||
 | 
					        pub mail: bool,
 | 
				
			||||||
 | 
					        pub code: String
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct ChangePassword {
 | 
				
			||||||
 | 
					        pub oldPassword: String,
 | 
				
			||||||
 | 
					        pub newPassword: String
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct CreateNode {
 | 
				
			||||||
 | 
					        pub parent: i32,
 | 
				
			||||||
 | 
					        pub name: String
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct CreateZip {
 | 
				
			||||||
 | 
					        pub nodes: Vec<i32>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct Download {
 | 
				
			||||||
 | 
					        pub jwtToken: String,
 | 
				
			||||||
 | 
					        pub id: i32
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Deserialize, Serialize, Clone)]
 | 
				
			||||||
 | 
					    pub struct DownloadMulti {
 | 
				
			||||||
 | 
					        pub jwtToken: String,
 | 
				
			||||||
 | 
					        pub id: String
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,69 +0,0 @@
 | 
				
			|||||||
#ifndef BACKEND_DTO_H
 | 
					 | 
				
			||||||
#define BACKEND_DTO_H
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <drogon/HttpResponse.h>
 | 
					 | 
				
			||||||
#include "db/db.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace dto {
 | 
					 | 
				
			||||||
    template<typename T>
 | 
					 | 
				
			||||||
    std::optional<T> json_get(const Json::Value& j, const std::string& key) {
 | 
					 | 
				
			||||||
        return j.isMember(key)
 | 
					 | 
				
			||||||
            ? std::make_optional(j[key].as<T>())
 | 
					 | 
				
			||||||
            : std::nullopt;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    inline db::User get_user(const drogon::HttpRequestPtr& req) {
 | 
					 | 
				
			||||||
        return req->attributes()->get<db::User>("user");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    inline db::Token get_token(const drogon::HttpRequestPtr& req) {
 | 
					 | 
				
			||||||
        return req->attributes()->get<db::Token>("token");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    namespace Responses {
 | 
					 | 
				
			||||||
        struct GetUsersEntry {
 | 
					 | 
				
			||||||
            GetUsersEntry(uint64_t id, bool gitlab, bool tfa, std::string name, db::UserRole role)
 | 
					 | 
				
			||||||
                : id(id), gitlab(gitlab), tfa(tfa), name(std::move(name)), role(role) {}
 | 
					 | 
				
			||||||
            uint64_t id;
 | 
					 | 
				
			||||||
            bool gitlab, tfa;
 | 
					 | 
				
			||||||
            std::string name;
 | 
					 | 
				
			||||||
            db::UserRole role;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        struct GetNodeEntry {
 | 
					 | 
				
			||||||
            explicit GetNodeEntry(const db::INode& node) : id(node.getValueOfId()), name(node.getValueOfName()), is_file(node.getValueOfIsFile() != 0), has_preview(node.getValueOfHasPreview() != 0), parent(node.getParentId()) {
 | 
					 | 
				
			||||||
                if (node.getValueOfIsFile() != 0) size = node.getValueOfSize();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            uint64_t id, size;
 | 
					 | 
				
			||||||
            std::string name;
 | 
					 | 
				
			||||||
            bool is_file, has_preview;
 | 
					 | 
				
			||||||
            std::shared_ptr<uint64_t> parent;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_error_res(drogon::HttpStatusCode, const std::string &msg);
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_success_res();
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_success_res(Json::Value &);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        inline drogon::HttpResponsePtr get_badreq_res(const std::string &msg) { return get_error_res(drogon::HttpStatusCode::k400BadRequest, msg); }
 | 
					 | 
				
			||||||
        inline drogon::HttpResponsePtr get_unauth_res(const std::string &msg) { return get_error_res(drogon::HttpStatusCode::k401Unauthorized, msg); }
 | 
					 | 
				
			||||||
        inline drogon::HttpResponsePtr get_forbdn_res(const std::string &msg) { return get_error_res(drogon::HttpStatusCode::k403Forbidden, msg); }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_login_res(const std::string &jwt);
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_tfa_setup_res(const std::string& secret, const std::string& qrcode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_user_info_res(const std::string& name, bool gitlab, bool tfa);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_admin_users_res(const std::vector<GetUsersEntry>& users);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_root_res(uint64_t root);
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_node_res(const GetNodeEntry& node, const std::vector<GetNodeEntry>& children);
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_new_node_res(uint64_t id);
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_node_exists_res(uint64_t id, bool file);
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_download_base64_res(const std::string& data);
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_type_res(const std::string& type);
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_create_zip_done_res();
 | 
					 | 
				
			||||||
        drogon::HttpResponsePtr get_create_zip_done_res(uint64_t progress, uint64_t total);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif //BACKEND_DTO_H
 | 
					 | 
				
			||||||
@@ -1,126 +0,0 @@
 | 
				
			|||||||
#include "dto.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace dto::Responses {
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_error_res(drogon::HttpStatusCode code, const std::string& msg) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["statusCode"] = static_cast<int>(code);
 | 
					 | 
				
			||||||
        json["message"] = msg;
 | 
					 | 
				
			||||||
        auto res = drogon::HttpResponse::newHttpJsonResponse(json);
 | 
					 | 
				
			||||||
        res->setStatusCode(code);
 | 
					 | 
				
			||||||
        return res;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_success_res() {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_success_res(Json::Value& json) {
 | 
					 | 
				
			||||||
        json["statusCode"] = 200;
 | 
					 | 
				
			||||||
        auto res = drogon::HttpResponse::newHttpJsonResponse(json);
 | 
					 | 
				
			||||||
        res->setStatusCode(drogon::HttpStatusCode::k200OK);
 | 
					 | 
				
			||||||
        return res;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_login_res(const std::string &jwt) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["jwt"] = jwt;
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_tfa_setup_res(const std::string& secret, const std::string& qrcode) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["secret"] = secret;
 | 
					 | 
				
			||||||
        json["qrCode"] = qrcode;
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_user_info_res(const std::string &name, bool gitlab, bool tfa) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["name"] = name;
 | 
					 | 
				
			||||||
        json["gitlab"] = gitlab;
 | 
					 | 
				
			||||||
        json["tfaEnabled"] = tfa;
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_admin_users_res(const std::vector<GetUsersEntry>& users) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        for (const GetUsersEntry& user : users) {
 | 
					 | 
				
			||||||
            Json::Value entry;
 | 
					 | 
				
			||||||
            entry["id"] = user.id;
 | 
					 | 
				
			||||||
            entry["gitlab"] = user.gitlab;
 | 
					 | 
				
			||||||
            entry["name"] = user.name;
 | 
					 | 
				
			||||||
            entry["role"] = user.role;
 | 
					 | 
				
			||||||
            entry["tfaEnabled"] = user.tfa;
 | 
					 | 
				
			||||||
            json["users"].append(entry);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_root_res(uint64_t root) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["rootId"] = root;
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Json::Value parse_node(const GetNodeEntry& node) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["id"] = node.id;
 | 
					 | 
				
			||||||
        json["name"] = node.name;
 | 
					 | 
				
			||||||
        json["isFile"] = node.is_file;
 | 
					 | 
				
			||||||
        json["preview"] = node.has_preview;
 | 
					 | 
				
			||||||
        json["parent"] = (node.parent != nullptr) ? *node.parent : Json::Value::nullSingleton();
 | 
					 | 
				
			||||||
        if (node.is_file) json["size"] = node.size;
 | 
					 | 
				
			||||||
        return json;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_node_res(const GetNodeEntry& node, const std::vector<GetNodeEntry>& children) {
 | 
					 | 
				
			||||||
        Json::Value json = parse_node(node);
 | 
					 | 
				
			||||||
        if (!node.is_file) {
 | 
					 | 
				
			||||||
            json["children"] = Json::Value(Json::arrayValue);
 | 
					 | 
				
			||||||
            for (const GetNodeEntry& child : children)
 | 
					 | 
				
			||||||
                json["children"].append(parse_node(child));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_new_node_res(uint64_t id) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["id"] = id;
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_node_exists_res(uint64_t id, bool file) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["id"] = id;
 | 
					 | 
				
			||||||
        json["exists"] = true;
 | 
					 | 
				
			||||||
        json["isFile"] = file;
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_download_base64_res(const std::string &data) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["data"] = data;
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_type_res(const std::string &type) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["type"] = type;
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_create_zip_done_res() {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["done"] = true;
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::HttpResponsePtr get_create_zip_done_res(uint64_t progress, uint64_t total) {
 | 
					 | 
				
			||||||
        Json::Value json;
 | 
					 | 
				
			||||||
        json["done"] = false;
 | 
					 | 
				
			||||||
        json["progress"] = progress;
 | 
					 | 
				
			||||||
        json["total"] = total;
 | 
					 | 
				
			||||||
        return get_success_res(json);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,82 +0,0 @@
 | 
				
			|||||||
#include "filters.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <drogon/utils/coroutine.h>
 | 
					 | 
				
			||||||
#include <jwt-cpp/traits/kazuho-picojson/traits.h>
 | 
					 | 
				
			||||||
#include <jwt-cpp/jwt.h>
 | 
					 | 
				
			||||||
#include "db/db.h"
 | 
					 | 
				
			||||||
#include "dto/dto.h"
 | 
					 | 
				
			||||||
#include "controllers/controllers.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void cleanup_tokens(db::MapperToken& mapper) {
 | 
					 | 
				
			||||||
    const uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
 | 
					 | 
				
			||||||
    mapper.deleteBy(
 | 
					 | 
				
			||||||
            db::Criteria(db::Token::Cols::_exp, db::CompareOps::LE, now)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Login::doFilter(const drogon::HttpRequestPtr& req, drogon::FilterCallback&& cb, drogon::FilterChainCallback&& ccb) {
 | 
					 | 
				
			||||||
    std::string token_str;
 | 
					 | 
				
			||||||
    if (req->path() == "/api/fs/download" || req->path() == "/api/fs/download_multi") {
 | 
					 | 
				
			||||||
        token_str = req->getParameter("jwtToken");
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        std::string auth_header = req->getHeader("Authorization");
 | 
					 | 
				
			||||||
        if (auth_header.empty() || (!auth_header.starts_with("Bearer ")))
 | 
					 | 
				
			||||||
            return cb(dto::Responses::get_unauth_res("Unauthorized"));
 | 
					 | 
				
			||||||
        token_str = auth_header.substr(7);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
        auto token = jwt::decode<jwt::traits::kazuho_picojson>(token_str);
 | 
					 | 
				
			||||||
        jwt::verify<jwt::traits::kazuho_picojson>()
 | 
					 | 
				
			||||||
                .allow_algorithm(jwt::algorithm::hs256{api::auth::get_jwt_secret()})
 | 
					 | 
				
			||||||
                .verify(token);
 | 
					 | 
				
			||||||
        uint64_t token_id = token.get_payload_claim("jti").as_int();
 | 
					 | 
				
			||||||
        uint64_t user_id = token.get_payload_claim("sub").as_int();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto db = drogon::app().getDbClient();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        db::MapperUser user_mapper(db);
 | 
					 | 
				
			||||||
        db::MapperToken token_mapper(db);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cleanup_tokens(token_mapper);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        db::Token db_token = token_mapper.findByPrimaryKey(token_id);
 | 
					 | 
				
			||||||
        db::User db_user = user_mapper.findByPrimaryKey(db_token.getValueOfOwnerId());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (db_user.getValueOfId() != user_id) throw std::exception();
 | 
					 | 
				
			||||||
        if (db::User_getEnumRole(db_user) == db::UserRole::DISABLED) throw std::exception();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (db_user.getValueOfGitlab() != 0) {
 | 
					 | 
				
			||||||
            auto info = api::auth::get_gitlab_user(db_user.getValueOfGitlabAt());
 | 
					 | 
				
			||||||
            if (!info.has_value()) {
 | 
					 | 
				
			||||||
                auto tokens = api::auth::get_gitlab_tokens(db_user.getValueOfGitlabRt(), true);
 | 
					 | 
				
			||||||
                info = api::auth::get_gitlab_user(tokens->at);
 | 
					 | 
				
			||||||
                if (!tokens.has_value() || !info.has_value()) {
 | 
					 | 
				
			||||||
                    api::auth::revoke_all(db_user);
 | 
					 | 
				
			||||||
                    throw std::exception();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                db_user.setGitlabAt(tokens->at);
 | 
					 | 
				
			||||||
                db_user.setGitlabRt(tokens->rt);
 | 
					 | 
				
			||||||
                user_mapper.update(db_user);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (info->name != db_user.getValueOfName()) {
 | 
					 | 
				
			||||||
                api::auth::revoke_all(db_user);
 | 
					 | 
				
			||||||
                throw std::exception();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        req->attributes()->insert("token", db_token);
 | 
					 | 
				
			||||||
        req->attributes()->insert("user", db_user);
 | 
					 | 
				
			||||||
        ccb();
 | 
					 | 
				
			||||||
    } catch (const std::exception&) {
 | 
					 | 
				
			||||||
        cb(dto::Responses::get_unauth_res("Unauthorized"));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Admin::doFilter(const drogon::HttpRequestPtr& req, drogon::FilterCallback&& cb, drogon::FilterChainCallback&& ccb) {
 | 
					 | 
				
			||||||
    db::User user = dto::get_user(req);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (db::User_getEnumRole(user) != db::UserRole::ADMIN)
 | 
					 | 
				
			||||||
        cb(dto::Responses::get_forbdn_res("Forbidden"));
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
        ccb();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,14 +0,0 @@
 | 
				
			|||||||
#ifndef BACKEND_FILTERS_H
 | 
					 | 
				
			||||||
#define BACKEND_FILTERS_H
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <drogon/HttpFilter.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct Login : public drogon::HttpFilter<Login> {
 | 
					 | 
				
			||||||
    void doFilter(const drogon::HttpRequestPtr&, drogon::FilterCallback&&, drogon::FilterChainCallback&&) override;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct Admin : public drogon::HttpFilter<Admin> {
 | 
					 | 
				
			||||||
    void doFilter(const drogon::HttpRequestPtr&, drogon::FilterCallback&&, drogon::FilterChainCallback&&) override;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif //BACKEND_FILTERS_H
 | 
					 | 
				
			||||||
@@ -1,197 +0,0 @@
 | 
				
			|||||||
#include <filesystem>
 | 
					 | 
				
			||||||
#include <fstream>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <drogon/drogon.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "dto/dto.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool dev_mode = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void cleanup() {
 | 
					 | 
				
			||||||
    std::cout << "Stopping..." << std::endl;
 | 
					 | 
				
			||||||
    drogon::app().quit();
 | 
					 | 
				
			||||||
    std::cout << "Cleanup up uploads...";
 | 
					 | 
				
			||||||
    std::filesystem::remove_all("uploads");
 | 
					 | 
				
			||||||
    std::cout << " [Done]" << std::endl;
 | 
					 | 
				
			||||||
    std::cout << "Removing temp folder..." << std::flush;
 | 
					 | 
				
			||||||
    std::filesystem::remove_all("temp");
 | 
					 | 
				
			||||||
    std::cout << " [Done]" << std::endl;
 | 
					 | 
				
			||||||
    std::cout << "Goodbye!" << std::endl;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
std::string get_index_content() {
 | 
					 | 
				
			||||||
    std::ifstream file("./static/index.html");
 | 
					 | 
				
			||||||
    return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void default_handler(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr&)>&& cbk) {
 | 
					 | 
				
			||||||
    static std::string index_html = get_index_content();
 | 
					 | 
				
			||||||
    if (req->path().starts_with("/api")) {
 | 
					 | 
				
			||||||
        std::cout << "Unknown api request: " << req->getMethodString() << " " << req->path() << std::endl;
 | 
					 | 
				
			||||||
        cbk(drogon::HttpResponse::newNotFoundResponse());
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        if (dev_mode) cbk(drogon::HttpResponse::newFileResponse("./static/index.html"));
 | 
					 | 
				
			||||||
        else cbk(drogon::HttpResponse::newFileResponse((unsigned char*)index_html.data(), index_html.size(), "", drogon::CT_TEXT_HTML));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int main(int argc, char* argv[]) {
 | 
					 | 
				
			||||||
    std::vector<std::string> args(argv+1, argv+argc);
 | 
					 | 
				
			||||||
    if (std::find(args.begin(), args.end(), "--dev") != args.end()) dev_mode = true;
 | 
					 | 
				
			||||||
    if (dev_mode) std::cout << "Starting in development mode" << std::endl;
 | 
					 | 
				
			||||||
    std::cout << "Setting up..." << std::endl;
 | 
					 | 
				
			||||||
    if (!std::filesystem::exists("files")) {
 | 
					 | 
				
			||||||
        std::cout << "Creating files..." << std::flush;
 | 
					 | 
				
			||||||
        std::filesystem::create_directory("files");
 | 
					 | 
				
			||||||
        std::cout << " [Done]" << std::endl;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!std::filesystem::exists("logs")) {
 | 
					 | 
				
			||||||
        std::cout << "Creating logs..." << std::flush;
 | 
					 | 
				
			||||||
        std::filesystem::create_directory("logs");
 | 
					 | 
				
			||||||
        std::cout << " [Done]" << std::endl;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (std::filesystem::exists("temp")) {
 | 
					 | 
				
			||||||
        std::cout << "Removing existing temp folder..." << std::flush;
 | 
					 | 
				
			||||||
        std::filesystem::remove_all("temp");
 | 
					 | 
				
			||||||
        std::cout << " [Done]" << std::endl;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    std::cout << "Creating temp folder..." << std::flush;
 | 
					 | 
				
			||||||
    std::filesystem::create_directory("temp");
 | 
					 | 
				
			||||||
    std::cout << " [Done]" << std::endl;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto* loop = drogon::app().getLoop();
 | 
					 | 
				
			||||||
    loop->queueInLoop([]{
 | 
					 | 
				
			||||||
        std::cout << "Starting..." << std::endl;
 | 
					 | 
				
			||||||
        std::cout << "Creating db tables..." << std::flush;
 | 
					 | 
				
			||||||
        auto db = drogon::app().getDbClient();
 | 
					 | 
				
			||||||
        db->execSqlSync("CREATE TABLE IF NOT EXISTS 'tokens' (\n"
 | 
					 | 
				
			||||||
                        "  'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'owner_id' INTEGER NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'exp' INTEGER NOT NULL\n"
 | 
					 | 
				
			||||||
                        ")");
 | 
					 | 
				
			||||||
        db->execSqlSync("CREATE TABLE IF NOT EXISTS 'user' (\n"
 | 
					 | 
				
			||||||
                        "  'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'gitlab' INTEGER NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'name' TEXT NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'password' TEXT NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'role' INTEGER NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'root_id' INTEGER NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'tfa_type' INTEGER NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'tfa_secret' BLOB,\n"
 | 
					 | 
				
			||||||
                        "  'gitlab_at' TEXT,\n"
 | 
					 | 
				
			||||||
                        "  'gitlab_rt' TEXT\n"
 | 
					 | 
				
			||||||
                        ")");
 | 
					 | 
				
			||||||
        db->execSqlSync("CREATE TABLE IF NOT EXISTS 'inode' (\n"
 | 
					 | 
				
			||||||
                        "  'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'is_file' INTEGER NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'name' TEXT,\n"
 | 
					 | 
				
			||||||
                        "  'parent_id' INTEGER,\n"
 | 
					 | 
				
			||||||
                        "  'owner_id' INTEGER NOT NULL,\n"
 | 
					 | 
				
			||||||
                        "  'size' INTEGER,\n"
 | 
					 | 
				
			||||||
                        "  'has_preview' INTEGER NOT NULL\n"
 | 
					 | 
				
			||||||
                        ")");
 | 
					 | 
				
			||||||
        std::cout << " [Done]" << std::endl;
 | 
					 | 
				
			||||||
        std::cout << "Started!" << std::endl;
 | 
					 | 
				
			||||||
        std::cout << "Registered paths: " << std::endl;
 | 
					 | 
				
			||||||
        auto handlers = drogon::app().getHandlersInfo();
 | 
					 | 
				
			||||||
        for (const auto& handler : handlers) {
 | 
					 | 
				
			||||||
            std::cout << "  ";
 | 
					 | 
				
			||||||
            if (std::get<1>(handler) == drogon::HttpMethod::Post) std::cout << "POST ";
 | 
					 | 
				
			||||||
            else std::cout << "GET  ";
 | 
					 | 
				
			||||||
            std::string func = std::get<2>(handler).substr(16);
 | 
					 | 
				
			||||||
            func.resize(30, ' ');
 | 
					 | 
				
			||||||
            std::cout << '[' << func << "] ";
 | 
					 | 
				
			||||||
            std::cout << std::get<0>(handler) << std::endl;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        std::cout << "Listening on:" << std::endl;
 | 
					 | 
				
			||||||
        auto listeners = drogon::app().getListeners();
 | 
					 | 
				
			||||||
        for (const auto& listener : listeners) {
 | 
					 | 
				
			||||||
            std::cout << "  " << listener.toIpPort() << std::endl;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Json::Value access_logger;
 | 
					 | 
				
			||||||
    access_logger["name"] = "drogon::plugin::AccessLogger";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Json::Value smtp_mail;
 | 
					 | 
				
			||||||
    smtp_mail["name"] = "SMTPMail";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Json::Value config;
 | 
					 | 
				
			||||||
    config["plugins"].append(access_logger);
 | 
					 | 
				
			||||||
    config["plugins"].append(smtp_mail);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!std::filesystem::exists("config.json")) {
 | 
					 | 
				
			||||||
        std::cerr << "config.json missing" << std::endl;
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::ifstream config_file("config.json");
 | 
					 | 
				
			||||||
    config_file >> config["custom_config"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!config["custom_config"].isObject()) {
 | 
					 | 
				
			||||||
        std::cerr << "config.json must be an object" << std::endl;
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!config["custom_config"].isMember("gitlab_id")) {
 | 
					 | 
				
			||||||
        std::cerr << "config.json missing gitlab_id" << std::endl;
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!config["custom_config"].isMember("gitlab_secret")) {
 | 
					 | 
				
			||||||
        std::cerr << "config.json missing gitlab_secret" << std::endl;
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!config["custom_config"].isMember("gitlab_url")) {
 | 
					 | 
				
			||||||
        std::cerr << "config.json missing gitlab_url" << std::endl;
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!config["custom_config"].isMember("gitlab_api_url")) {
 | 
					 | 
				
			||||||
        std::cerr << "config.json missing gitlab_api_url" << std::endl;
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!config["custom_config"].isMember("gitlab_redirect_url")) {
 | 
					 | 
				
			||||||
        std::cerr << "config.json missing gitlab_redirect_url" << std::endl;
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!config["custom_config"].isMember("smtp_server")) {
 | 
					 | 
				
			||||||
        std::cerr << "config.json missing smtp_server" << std::endl;
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!config["custom_config"].isMember("smtp_port")) {
 | 
					 | 
				
			||||||
        std::cerr << "config.json missing smtp_port" << std::endl;
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!config["custom_config"].isMember("smtp_user")) {
 | 
					 | 
				
			||||||
        std::cerr << "config.json missing smtp_user" << std::endl;
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!config["custom_config"].isMember("smtp_password")) {
 | 
					 | 
				
			||||||
        std::cerr << "config.json missing smtp_password" << std::endl;
 | 
					 | 
				
			||||||
        return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::app()
 | 
					 | 
				
			||||||
            .setClientMaxBodySize(std::numeric_limits<size_t>::max())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .loadConfigJson(config)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .createDbClient("sqlite3", "", 0, "", "", "", 1, "sqlite.db")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .setDefaultHandler(default_handler)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .setDocumentRoot("./static")
 | 
					 | 
				
			||||||
            .setBrStatic(true)
 | 
					 | 
				
			||||||
            .setStaticFilesCacheTime(dev_mode ? -1 : 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .setLogPath("./logs")
 | 
					 | 
				
			||||||
            .setLogLevel(trantor::Logger::LogLevel::kDebug)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .setIntSignalHandler(cleanup)
 | 
					 | 
				
			||||||
            .setTermSignalHandler(cleanup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .addListener("0.0.0.0", 2345)
 | 
					 | 
				
			||||||
            .setThreadNum(8);
 | 
					 | 
				
			||||||
    std::cout << "Setup done!" << std::endl;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    drogon::app().run();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										34
									
								
								backend/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								backend/src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					mod db;
 | 
				
			||||||
 | 
					mod schema;
 | 
				
			||||||
 | 
					mod dto;
 | 
				
			||||||
 | 
					mod routes;
 | 
				
			||||||
 | 
					mod config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[tokio::main]
 | 
				
			||||||
 | 
					async fn main() {
 | 
				
			||||||
 | 
					    console_subscriber::init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pretty_env_logger::formatted_builder().filter_level(log::LevelFilter::Info).init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let _ = config::CONFIG;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let pool: db::DBPool = db::build_pool();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    db::run_migrations(&mut pool.get().unwrap());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !std::path::Path::new("files").exists() {
 | 
				
			||||||
 | 
					        std::fs::create_dir("files").expect("Failed to create files directory");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let (_addr, server) = warp::serve(routes::build_routes(pool.clone())).bind_with_graceful_shutdown(([0, 0, 0, 0], 2345), async {
 | 
				
			||||||
 | 
					        shutdown_rx.await.ok();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tokio::task::spawn(server);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tokio::signal::ctrl_c().await.expect("Failed to wait for ctrl-c");
 | 
				
			||||||
 | 
					    println!("Quitting");
 | 
				
			||||||
 | 
					    shutdown_tx.send(()).expect("Failed to shutdown server");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										129
									
								
								backend/src/routes/admin.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								backend/src/routes/admin.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					use warp::{Filter, Reply};
 | 
				
			||||||
 | 
					use crate::db::{DBConnection, DBPool, with_db};
 | 
				
			||||||
 | 
					use crate::dto;
 | 
				
			||||||
 | 
					use crate::routes::{AppError, get_reply};
 | 
				
			||||||
 | 
					use crate::routes::filters::{admin, UserInfo};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_routes(db: DBPool) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
 | 
				
			||||||
 | 
					    let users = warp::path!("admin" / "users")
 | 
				
			||||||
 | 
					        .and(warp::get())
 | 
				
			||||||
 | 
					        .and(admin(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(users);
 | 
				
			||||||
 | 
					    let set_role = warp::path!("admin" / "set_role")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(admin(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(set_role);
 | 
				
			||||||
 | 
					    let logout = warp::path!("admin" / "logout")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(admin(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(logout);
 | 
				
			||||||
 | 
					    let delete_user = warp::path!("admin" / "delete")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(admin(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(delete_user);
 | 
				
			||||||
 | 
					    let disable_2fa = warp::path!("admin" / "disable_2fa")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(admin(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(disable_2fa);
 | 
				
			||||||
 | 
					    let is_admin = warp::path!("admin" / "is_admin")
 | 
				
			||||||
 | 
					        .and(warp::get())
 | 
				
			||||||
 | 
					        .and(admin(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(|_| async { get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					            statusCode: 200
 | 
				
			||||||
 | 
					        }) });
 | 
				
			||||||
 | 
					    let get_token = warp::path!("admin" / "get_token" / i32)
 | 
				
			||||||
 | 
					        .and(warp::get())
 | 
				
			||||||
 | 
					        .and(admin(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db))
 | 
				
			||||||
 | 
					        .and_then(get_token);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    users.or(set_role).or(logout).or(delete_user).or(disable_2fa).or(is_admin).or(get_token)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn users(_: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let users = db.get_users();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut res = dto::responses::AdminUsers {
 | 
				
			||||||
 | 
					        statusCode: 200,
 | 
				
			||||||
 | 
					        users: Vec::new()
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for user in users {
 | 
				
			||||||
 | 
					        res.users.push(dto::responses::AdminUsersEntry {
 | 
				
			||||||
 | 
					            id: user.id,
 | 
				
			||||||
 | 
					            gitlab: user.gitlab,
 | 
				
			||||||
 | 
					            name: user.name,
 | 
				
			||||||
 | 
					            role: user.role,
 | 
				
			||||||
 | 
					            tfaEnabled: user.tfa_type != crate::db::TfaTypes::None
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&res)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn set_role(data: dto::requests::AdminSetRole, _: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let mut user = db.get_user(data.user)
 | 
				
			||||||
 | 
					        .ok_or(AppError::Forbidden("Invalid user"))?;
 | 
				
			||||||
 | 
					    user.role = data.role;
 | 
				
			||||||
 | 
					    db.save_user(&user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					        statusCode: 200
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn logout(data: dto::requests::Admin, _: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    db.delete_all_tokens(data.user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					        statusCode: 200
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn delete_user(data: dto::requests::Admin, _: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let user = db.get_user(data.user)
 | 
				
			||||||
 | 
					        .ok_or(AppError::Forbidden("Invalid user"))?;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    db.delete_all_tokens(data.user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let root_node = super::fs::get_node_and_validate(&user, user.root_id, &mut db).expect("Failed to get root node for deleting");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    super::fs::delete_node_root(&root_node, &mut db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    db.delete_user(&user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					        statusCode: 200
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn disable_2fa(data: dto::requests::Admin, _: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let mut user = db.get_user(data.user)
 | 
				
			||||||
 | 
					        .ok_or(AppError::Forbidden("Invalid user"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user.tfa_type = crate::db::TfaTypes::None;
 | 
				
			||||||
 | 
					    db.save_user(&user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					        statusCode: 200
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn get_token(user: i32, _: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let user = db.get_user(user)
 | 
				
			||||||
 | 
					        .ok_or(AppError::Forbidden("Invalid user"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Login {
 | 
				
			||||||
 | 
					        statusCode: 200,
 | 
				
			||||||
 | 
					        jwt: super::auth::get_token(&user, &mut db)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										115
									
								
								backend/src/routes/auth/basic.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								backend/src/routes/auth/basic.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
				
			|||||||
 | 
					use warp::Filter;
 | 
				
			||||||
 | 
					use crate::db::{DBConnection, DBPool, with_db};
 | 
				
			||||||
 | 
					use crate::db::{TfaTypes, UserRole};
 | 
				
			||||||
 | 
					use crate::dto;
 | 
				
			||||||
 | 
					use crate::dto::requests::ChangePassword;
 | 
				
			||||||
 | 
					use crate::routes::{AppError, get_reply};
 | 
				
			||||||
 | 
					use crate::routes::filters::{authenticated, UserInfo};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_routes(db: DBPool) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
 | 
				
			||||||
 | 
					    let login = warp::path!("auth" / "login")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(login);
 | 
				
			||||||
 | 
					    let signup = warp::path!("auth" / "signup")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(signup);
 | 
				
			||||||
 | 
					    let refresh = warp::path!("auth" / "refresh")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(refresh);
 | 
				
			||||||
 | 
					    let logout_all = warp::path!("auth" / "logout_all")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(logout_all);
 | 
				
			||||||
 | 
					    let change_password = warp::path!("auth" / "change_password")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db))
 | 
				
			||||||
 | 
					        .and_then(change_password);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    login.or(signup).or(refresh).or(logout_all).or(change_password)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn login(data: dto::requests::Login, mut db: DBConnection)
 | 
				
			||||||
 | 
					    -> Result<impl warp::Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let user = db.find_user(&data.username, false)
 | 
				
			||||||
 | 
					        .ok_or(AppError::Unauthorized("Invalid username or password"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !argon2::verify_encoded(user.password.as_str(), data.password.as_bytes()).unwrap_or(false) {
 | 
				
			||||||
 | 
					        return AppError::Unauthorized("Invalid username or password").err();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if user.role == UserRole::Disabled {
 | 
				
			||||||
 | 
					        return AppError::Unauthorized("Account is disabled").err();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if user.tfa_type != TfaTypes::None {
 | 
				
			||||||
 | 
					        if let Some(otp) = data.otp {
 | 
				
			||||||
 | 
					            if !super::tfa::verify2fa(&user, otp) {
 | 
				
			||||||
 | 
					                return AppError::Unauthorized("Incorrect 2fa").err();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            if user.tfa_type == TfaTypes::Email { super::tfa::send_2fa_mail(&user); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					                statusCode: 200
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Login {
 | 
				
			||||||
 | 
					        statusCode: 200,
 | 
				
			||||||
 | 
					        jwt: super::get_token(&user, &mut db)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn signup(data: dto::requests::SignUp, mut db: DBConnection)
 | 
				
			||||||
 | 
					    -> Result<impl warp::Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    if db.find_user(&data.username, false).is_some() {
 | 
				
			||||||
 | 
					        return AppError::BadRequest("Username is already taken").err();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    db.create_user_password(data.username, super::hash_password(&data.password));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					        statusCode: 200
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn refresh(info: UserInfo, mut db: DBConnection) -> Result<impl warp::Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    db.delete_token(info.1.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Login {
 | 
				
			||||||
 | 
					        statusCode: 200,
 | 
				
			||||||
 | 
					        jwt: super::get_token(&info.0, &mut db)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn logout_all(info: UserInfo, mut db: DBConnection) -> Result<impl warp::Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    db.delete_all_tokens(info.0.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					        statusCode: 200
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn change_password(data: ChangePassword, mut info: UserInfo, mut db: DBConnection) -> Result<impl warp::Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    if !argon2::verify_encoded(info.0.password.as_str(), data.oldPassword.as_bytes()).unwrap_or(false) {
 | 
				
			||||||
 | 
					        return AppError::Unauthorized("Old password is wrong").err();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    info.0.password = super::hash_password(&data.newPassword);
 | 
				
			||||||
 | 
					    db.save_user(&info.0);
 | 
				
			||||||
 | 
					    db.delete_all_tokens(info.0.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					        statusCode: 200
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										108
									
								
								backend/src/routes/auth/gitlab.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								backend/src/routes/auth/gitlab.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
				
			|||||||
 | 
					use cached::proc_macro::cached;
 | 
				
			||||||
 | 
					use lazy_static::lazy_static;
 | 
				
			||||||
 | 
					use warp::{Filter, Reply};
 | 
				
			||||||
 | 
					use crate::config::CONFIG;
 | 
				
			||||||
 | 
					use crate::db::{DBConnection, DBPool, with_db};
 | 
				
			||||||
 | 
					use crate::routes::AppError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Deserialize, Clone, Debug)]
 | 
				
			||||||
 | 
					pub struct GitlabTokens {
 | 
				
			||||||
 | 
					    pub access_token: String,
 | 
				
			||||||
 | 
					    pub refresh_token: String
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Deserialize, Clone, Debug)]
 | 
				
			||||||
 | 
					pub struct GitlabUser {
 | 
				
			||||||
 | 
					    pub username: String,
 | 
				
			||||||
 | 
					    pub is_admin: bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
 | 
				
			||||||
 | 
					pub struct GitlabCallbackQuery {
 | 
				
			||||||
 | 
					    pub code: String
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					lazy_static! {
 | 
				
			||||||
 | 
					    static ref REDIRECT_URL: String = CONFIG.gitlab_redirect_url.clone() + "/api/auth/gitlab_callback";
 | 
				
			||||||
 | 
					    static ref TOKEN_URL: String = format!("{}/oauth/token", CONFIG.gitlab_api_url.clone());
 | 
				
			||||||
 | 
					    static ref USER_URL: String = format!("{}/api/v4/user", CONFIG.gitlab_api_url.clone());
 | 
				
			||||||
 | 
					    static ref AUTHORIZE_URL: String = format!("{}/oauth/authorize", CONFIG.gitlab_url.clone());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn get_gitlab_token(code_or_token: String, token: bool) -> Option<GitlabTokens> {
 | 
				
			||||||
 | 
					    let mut req = ureq::post(&TOKEN_URL)
 | 
				
			||||||
 | 
					        .query("redirect_uri", &REDIRECT_URL)
 | 
				
			||||||
 | 
					        .query("client_id", &CONFIG.gitlab_id)
 | 
				
			||||||
 | 
					        .query("client_secret", &CONFIG.gitlab_secret);
 | 
				
			||||||
 | 
					    if token {
 | 
				
			||||||
 | 
					        req = req
 | 
				
			||||||
 | 
					            .query("refresh_token", &code_or_token)
 | 
				
			||||||
 | 
					            .query("grant_type", "refresh_token");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        req = req
 | 
				
			||||||
 | 
					            .query("code", &code_or_token)
 | 
				
			||||||
 | 
					            .query("grant_type", "authorization_code");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    req.call().ok()?.into_json().ok()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cached(time=300, time_refresh=false, option=true)]
 | 
				
			||||||
 | 
					pub fn get_gitlab_user(token: String) -> Option<GitlabUser> {
 | 
				
			||||||
 | 
					    ureq::get(&USER_URL)
 | 
				
			||||||
 | 
					        .set("Authorization", &format!("Bearer {}", token))
 | 
				
			||||||
 | 
					        .call()
 | 
				
			||||||
 | 
					        .ok()?
 | 
				
			||||||
 | 
					        .into_json().ok()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_routes(db: DBPool) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
 | 
				
			||||||
 | 
					    let gitlab = warp::path!("auth" / "gitlab")
 | 
				
			||||||
 | 
					        .and(warp::get())
 | 
				
			||||||
 | 
					        .and_then(gitlab);
 | 
				
			||||||
 | 
					    let gitlab_callback = warp::path!("auth" / "gitlab_callback")
 | 
				
			||||||
 | 
					        .and(warp::get())
 | 
				
			||||||
 | 
					        .and(warp::query::query::<GitlabCallbackQuery>())
 | 
				
			||||||
 | 
					        .and(with_db(db))
 | 
				
			||||||
 | 
					        .and_then(gitlab_callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gitlab.or(gitlab_callback)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn gitlab() -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let uri = format!("{}?redirect_uri={}&client_id={}&scope=read_user&response_type=code", AUTHORIZE_URL.as_str(), REDIRECT_URL.as_str(), CONFIG.gitlab_id);
 | 
				
			||||||
 | 
					    Ok(warp::redirect::found(uri.parse::<warp::http::Uri>().expect("Failed to parse gitlab auth uri")))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn gitlab_callback(code: GitlabCallbackQuery, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    use crate::db::UserRole;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let tokens = get_gitlab_token(code.code, false).ok_or(AppError::Unauthorized("Invalid code"))?;
 | 
				
			||||||
 | 
					    let gitlab_user = get_gitlab_user(tokens.access_token.clone()).ok_or(AppError::Unauthorized("Invalid code"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let user = db.find_user(&gitlab_user.username, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let user = match user {
 | 
				
			||||||
 | 
					        Some(mut v) => {
 | 
				
			||||||
 | 
					            v.gitlab_at = Some(tokens.access_token);
 | 
				
			||||||
 | 
					            v.gitlab_rt = Some(tokens.refresh_token);
 | 
				
			||||||
 | 
					            db.save_user(&v);
 | 
				
			||||||
 | 
					            v
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        None => {
 | 
				
			||||||
 | 
					            db.create_user_gitlab(
 | 
				
			||||||
 | 
					                gitlab_user.username,
 | 
				
			||||||
 | 
					                if gitlab_user.is_admin { UserRole::Admin } else { UserRole::Disabled },
 | 
				
			||||||
 | 
					                tokens.access_token,
 | 
				
			||||||
 | 
					                tokens.refresh_token
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if user.role == UserRole::Disabled {
 | 
				
			||||||
 | 
					        Ok(warp::reply::html("<!DOCTYPE html><html><h2>Your account is disabled, please contact an admin.<br/><a href=\"/login\">Go to login page</a></h2></html>").into_response())
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        let uri = format!("/set_token?token={}", super::get_token(&user, &mut db));
 | 
				
			||||||
 | 
					        Ok(warp::redirect::found(uri.parse::<warp::http::Uri>().expect("Failed to parse set_token uri")).into_response())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										75
									
								
								backend/src/routes/auth/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								backend/src/routes/auth/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					mod basic;
 | 
				
			||||||
 | 
					mod tfa;
 | 
				
			||||||
 | 
					pub mod gitlab;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::ops::Add;
 | 
				
			||||||
 | 
					use lazy_static::lazy_static;
 | 
				
			||||||
 | 
					use ring::rand;
 | 
				
			||||||
 | 
					use ring::rand::SecureRandom;
 | 
				
			||||||
 | 
					use warp::Filter;
 | 
				
			||||||
 | 
					use crate::db::DBPool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_routes(db: DBPool) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
 | 
				
			||||||
 | 
					    SEC_RANDOM.fill(&mut [0; 1]).expect("Failed to init secure random");
 | 
				
			||||||
 | 
					    basic::build_routes(db.clone())
 | 
				
			||||||
 | 
					        .or(tfa::build_routes(db.clone()))
 | 
				
			||||||
 | 
					        .or(gitlab::build_routes(db))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, serde::Deserialize, serde::Serialize)]
 | 
				
			||||||
 | 
					pub struct JWTClaims {
 | 
				
			||||||
 | 
					    pub exp: i64,
 | 
				
			||||||
 | 
					    pub iat: i64,
 | 
				
			||||||
 | 
					    pub jti: i32,
 | 
				
			||||||
 | 
					    pub sub: i32
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub static JWT_ALGORITHM: jsonwebtoken::Algorithm = jsonwebtoken::Algorithm::HS512;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					lazy_static! {
 | 
				
			||||||
 | 
					    pub static ref SEC_RANDOM: rand::SystemRandom = rand::SystemRandom::new();
 | 
				
			||||||
 | 
					    pub static ref JWT_SECRET: Vec<u8> = get_jwt_secret();
 | 
				
			||||||
 | 
					    pub static ref JWT_DECODE_KEY: jsonwebtoken::DecodingKey = jsonwebtoken::DecodingKey::from_secret(JWT_SECRET.as_slice());
 | 
				
			||||||
 | 
					    pub static ref JWT_ENCODE_KEY: jsonwebtoken::EncodingKey = jsonwebtoken::EncodingKey::from_secret(JWT_SECRET.as_slice());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_jwt_secret() -> Vec<u8> {
 | 
				
			||||||
 | 
					    let secret = std::fs::read("jwt.secret");
 | 
				
			||||||
 | 
					    if let Ok(secret) = secret {
 | 
				
			||||||
 | 
					        secret
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        let mut secret: [u8; 128] = [0; 128];
 | 
				
			||||||
 | 
					        SEC_RANDOM.fill(&mut secret).expect("Failed to generate jwt secret");
 | 
				
			||||||
 | 
					        std::fs::write("jwt.secret", secret).expect("Failed to write jwt secret");
 | 
				
			||||||
 | 
					        Vec::from(secret)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn get_token(user: &crate::db::User, db: &mut crate::db::DBConnection) -> String {
 | 
				
			||||||
 | 
					    let iat = chrono::Utc::now();
 | 
				
			||||||
 | 
					    let exp = iat.add(chrono::Duration::hours(24)).timestamp();
 | 
				
			||||||
 | 
					    let iat = iat.timestamp();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let token = db.create_token(user.id, exp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let claims = JWTClaims {
 | 
				
			||||||
 | 
					        exp,
 | 
				
			||||||
 | 
					        iat,
 | 
				
			||||||
 | 
					        jti: token.id,
 | 
				
			||||||
 | 
					        sub: user.id
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    jsonwebtoken::encode(&jsonwebtoken::Header::new(JWT_ALGORITHM), &claims, &JWT_ENCODE_KEY)
 | 
				
			||||||
 | 
					        .expect("Failed to create JWT token")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn hash_password(password: &String) -> String {
 | 
				
			||||||
 | 
					    let mut salt = [0_u8; 16];
 | 
				
			||||||
 | 
					    SEC_RANDOM.fill(&mut salt).expect("Failed to generate salt");
 | 
				
			||||||
 | 
					    let config = argon2::Config {
 | 
				
			||||||
 | 
					        mem_cost: 64 * 1024,
 | 
				
			||||||
 | 
					        variant: argon2::Variant::Argon2id,
 | 
				
			||||||
 | 
					        ..Default::default()
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    argon2::hash_encoded(password.as_bytes(), &salt, &config).expect("Failed to hash password")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										136
									
								
								backend/src/routes/auth/tfa.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								backend/src/routes/auth/tfa.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
				
			|||||||
 | 
					use lazy_static::lazy_static;
 | 
				
			||||||
 | 
					use lettre::Transport;
 | 
				
			||||||
 | 
					use ring::rand::SecureRandom;
 | 
				
			||||||
 | 
					use warp::Filter;
 | 
				
			||||||
 | 
					use crate::config::CONFIG;
 | 
				
			||||||
 | 
					use crate::db::{DBConnection, DBPool, with_db, TfaTypes};
 | 
				
			||||||
 | 
					use crate::dto;
 | 
				
			||||||
 | 
					use crate::routes::{AppError, get_reply};
 | 
				
			||||||
 | 
					use crate::routes::filters::{authenticated, UserInfo};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn build_mail_sender() -> lettre::SmtpTransport {
 | 
				
			||||||
 | 
					    lettre::SmtpTransport::builder_dangerous(CONFIG.smtp_server.clone())
 | 
				
			||||||
 | 
					        .port(CONFIG.smtp_port)
 | 
				
			||||||
 | 
					        .tls(
 | 
				
			||||||
 | 
					            lettre::transport::smtp::client::Tls::Required(
 | 
				
			||||||
 | 
					                lettre::transport::smtp::client::TlsParameters::new(
 | 
				
			||||||
 | 
					                    CONFIG.smtp_server.clone()
 | 
				
			||||||
 | 
					                ).unwrap()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .credentials(lettre::transport::smtp::authentication::Credentials::new(CONFIG.smtp_user.clone(), CONFIG.smtp_password.clone()))
 | 
				
			||||||
 | 
					        .build()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					lazy_static! {
 | 
				
			||||||
 | 
					    static ref MAIL_SENDER: lettre::SmtpTransport = build_mail_sender();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_totp(user: &crate::db::User) -> totp_rs::TOTP {
 | 
				
			||||||
 | 
					    totp_rs::TOTP::from_rfc6238(
 | 
				
			||||||
 | 
					        totp_rs::Rfc6238::new(
 | 
				
			||||||
 | 
					            6,
 | 
				
			||||||
 | 
					            user.tfa_secret.clone().unwrap(),
 | 
				
			||||||
 | 
					            Some("MFileserver".to_owned()),
 | 
				
			||||||
 | 
					            user.name.clone()
 | 
				
			||||||
 | 
					        ).unwrap()
 | 
				
			||||||
 | 
					    ).unwrap()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn verify2fa(user: &crate::db::User, code: String) -> bool {
 | 
				
			||||||
 | 
					    let allowed_skew = if user.tfa_type == TfaTypes::Totp {0} else {10};
 | 
				
			||||||
 | 
					    let totp = get_totp(user);
 | 
				
			||||||
 | 
					    let time = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
 | 
				
			||||||
 | 
					    let base_step = time / totp.step - allowed_skew;
 | 
				
			||||||
 | 
					    for i in 0..allowed_skew + 1 {
 | 
				
			||||||
 | 
					        let step = (base_step + i) * totp.step;
 | 
				
			||||||
 | 
					        if totp.generate(step).eq(&code) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn send_2fa_mail(user: &crate::db::User) {
 | 
				
			||||||
 | 
					    let totp = get_totp(user);
 | 
				
			||||||
 | 
					    let code = totp.generate_current().unwrap();
 | 
				
			||||||
 | 
					    let mail = lettre::Message::builder()
 | 
				
			||||||
 | 
					        .from("fileserver@mattv.de".parse().unwrap())
 | 
				
			||||||
 | 
					        .to(user.name.parse().unwrap())
 | 
				
			||||||
 | 
					        .subject("MFileserver - Email 2fa code")
 | 
				
			||||||
 | 
					        .body(format!("Your code is: {}\r\nIt is valid for 5 minutes", code))
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MAIL_SENDER.send(&mail).expect("Failed to send mail");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_routes(db: DBPool) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
 | 
				
			||||||
 | 
					    let tfa_setup = warp::path!("auth" / "2fa" / "setup")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(tfa_setup);
 | 
				
			||||||
 | 
					    let tfa_complete = warp::path!("auth" / "2fa" / "complete")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(tfa_complete);
 | 
				
			||||||
 | 
					    let tfa_disable = warp::path!("auth" / "2fa" / "disable")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db))
 | 
				
			||||||
 | 
					        .and_then(tfa_disable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tfa_setup.or(tfa_complete).or(tfa_disable)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn tfa_setup(data: dto::requests::TfaSetup, mut info: UserInfo, mut db: DBConnection)
 | 
				
			||||||
 | 
					    -> Result<impl warp::Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let mut secret: [u8; 32] = [0; 32];
 | 
				
			||||||
 | 
					    super::SEC_RANDOM.fill(&mut secret).expect("Failed to generate secret");
 | 
				
			||||||
 | 
					    let secret = Vec::from(secret);
 | 
				
			||||||
 | 
					    info.0.tfa_secret = Some(secret);
 | 
				
			||||||
 | 
					    db.save_user(&info.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if data.mail {
 | 
				
			||||||
 | 
					        send_2fa_mail(&info.0);
 | 
				
			||||||
 | 
					        get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					            statusCode: 200
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        let totp = get_totp(&info.0);
 | 
				
			||||||
 | 
					        get_reply(&dto::responses::TfaSetup {
 | 
				
			||||||
 | 
					            statusCode: 200,
 | 
				
			||||||
 | 
					            secret: totp.get_secret_base32(),
 | 
				
			||||||
 | 
					            qrCode: "data:image/png;base64,".to_owned() + &totp.get_qr().expect("Failed to generate qr code")
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn tfa_complete(data: dto::requests::TfaComplete, mut info: UserInfo, mut db: DBConnection)
 | 
				
			||||||
 | 
					    -> Result<impl warp::Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    info.0.tfa_type = if data.mail { TfaTypes::Email } else { TfaTypes::Totp };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if verify2fa(&info.0, data.code) {
 | 
				
			||||||
 | 
					        db.save_user(&info.0);
 | 
				
			||||||
 | 
					        db.delete_all_tokens(info.0.id);
 | 
				
			||||||
 | 
					        get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					            statusCode: 200
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        AppError::BadRequest("Incorrect 2fa code").err()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn tfa_disable(mut info: UserInfo, mut db: DBConnection)
 | 
				
			||||||
 | 
					    -> Result<impl warp::Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    info.0.tfa_secret = None;
 | 
				
			||||||
 | 
					    info.0.tfa_type = TfaTypes::None;
 | 
				
			||||||
 | 
					    db.save_user(&info.0);
 | 
				
			||||||
 | 
					    db.delete_all_tokens(info.0.id);
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					        statusCode: 200
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										90
									
								
								backend/src/routes/filters.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								backend/src/routes/filters.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					use warp::Filter;
 | 
				
			||||||
 | 
					use warp::http::{HeaderMap, HeaderValue};
 | 
				
			||||||
 | 
					use crate::db::UserRole;
 | 
				
			||||||
 | 
					use crate::db::{DBConnection, DBPool, with_db};
 | 
				
			||||||
 | 
					use crate::routes::AppError;
 | 
				
			||||||
 | 
					use crate::routes::auth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type UserInfo = (crate::db::User, crate::db::Token);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn authenticated(db: DBPool) -> impl Filter<Extract=(UserInfo,), Error=warp::reject::Rejection> + Clone {
 | 
				
			||||||
 | 
					    warp::header::headers_cloned()
 | 
				
			||||||
 | 
					        .map(move |_headers: HeaderMap<HeaderValue>| _headers)
 | 
				
			||||||
 | 
					        .and(with_db(db))
 | 
				
			||||||
 | 
					        .and_then(authorize)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn admin(db: DBPool) -> impl Filter<Extract=(UserInfo, ), Error=warp::reject::Rejection> + Clone {
 | 
				
			||||||
 | 
					    warp::header::headers_cloned()
 | 
				
			||||||
 | 
					        .map(move |_headers: HeaderMap<HeaderValue>| _headers)
 | 
				
			||||||
 | 
					        .and(with_db(db))
 | 
				
			||||||
 | 
					        .and_then(|_headers, db| async {
 | 
				
			||||||
 | 
					        let info = authorize(_headers, db).await?;
 | 
				
			||||||
 | 
					        if info.0.role == UserRole::Admin {
 | 
				
			||||||
 | 
					            Ok(info)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            AppError::Forbidden("Forbidden").err()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn authorize(_headers: HeaderMap<HeaderValue>, mut db: DBConnection) -> Result<UserInfo, warp::reject::Rejection> {
 | 
				
			||||||
 | 
					    authorize_jwt(extract_jwt(&_headers).map_err(|e| e.reject())?, &mut db).await
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn authorize_jwt(jwt: String, db: &mut DBConnection) -> Result<UserInfo, warp::reject::Rejection> {
 | 
				
			||||||
 | 
					    let decoded = jsonwebtoken::decode::<auth::JWTClaims>(
 | 
				
			||||||
 | 
					        &jwt,
 | 
				
			||||||
 | 
					        &crate::routes::auth::JWT_DECODE_KEY,
 | 
				
			||||||
 | 
					        &jsonwebtoken::Validation::new(auth::JWT_ALGORITHM)
 | 
				
			||||||
 | 
					    ).map_err(|_| AppError::Forbidden("Invalid token"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    db.cleanup_tokens();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut user = db.get_user(decoded.claims.sub)
 | 
				
			||||||
 | 
					        .ok_or(AppError::Forbidden("Invalid token"))?;
 | 
				
			||||||
 | 
					    let token = db.get_token(decoded.claims.jti)
 | 
				
			||||||
 | 
					        .ok_or(AppError::Forbidden("Invalid token"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if user.id != token.owner_id {
 | 
				
			||||||
 | 
					        return AppError::Forbidden("Invalid token").err();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if user.role == UserRole::Disabled {
 | 
				
			||||||
 | 
					        return AppError::Forbidden("Account disabled").err();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if user.gitlab {
 | 
				
			||||||
 | 
					        let info = auth::gitlab::get_gitlab_user(user.gitlab_at.clone().unwrap());
 | 
				
			||||||
 | 
					        let info = match info {
 | 
				
			||||||
 | 
					            Some(v) => Some(v),
 | 
				
			||||||
 | 
					            None => {
 | 
				
			||||||
 | 
					                let tokens = auth::gitlab::get_gitlab_token(user.gitlab_rt.clone().unwrap(), true);
 | 
				
			||||||
 | 
					                if let Some(tokens) = tokens {
 | 
				
			||||||
 | 
					                    user.gitlab_at = Some(tokens.access_token.clone());
 | 
				
			||||||
 | 
					                    user.gitlab_rt = Some(tokens.refresh_token);
 | 
				
			||||||
 | 
					                    db.save_user(&user);
 | 
				
			||||||
 | 
					                    auth::gitlab::get_gitlab_user(tokens.access_token)
 | 
				
			||||||
 | 
					                } else { None }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        if info.is_none() || info.unwrap().username != user.name {
 | 
				
			||||||
 | 
					            db.delete_all_tokens(token.owner_id);
 | 
				
			||||||
 | 
					            db.delete_all_tokens(user.id);
 | 
				
			||||||
 | 
					            return AppError::Forbidden("Invalid gitlab user").err();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok((user, token))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn extract_jwt(_headers: &HeaderMap<HeaderValue>) -> Result<String, AppError> {
 | 
				
			||||||
 | 
					    let header = match _headers.get(warp::http::header::AUTHORIZATION) {
 | 
				
			||||||
 | 
					        Some(v) => v,
 | 
				
			||||||
 | 
					        None => return Err(AppError::Unauthorized("Missing token"))
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    let header = header.to_str().map_err(|_| AppError::Unauthorized("Missing token"))?;
 | 
				
			||||||
 | 
					    if !header.starts_with("Bearer ") {
 | 
				
			||||||
 | 
					        Err(AppError::Unauthorized("Missing token"))
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        Ok(header.trim_start_matches("Bearer ").to_owned())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										199
									
								
								backend/src/routes/fs/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								backend/src/routes/fs/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,199 @@
 | 
				
			|||||||
 | 
					use std::collections::VecDeque;
 | 
				
			||||||
 | 
					use std::iter::Iterator;
 | 
				
			||||||
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
 | 
				
			||||||
 | 
					use lazy_static::lazy_static;
 | 
				
			||||||
 | 
					use warp::Filter;
 | 
				
			||||||
 | 
					use futures::TryFutureExt;
 | 
				
			||||||
 | 
					use futures::TryStreamExt;
 | 
				
			||||||
 | 
					use crate::db::DBPool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod routes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_routes(db: DBPool) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if !std::path::Path::new("temp").is_dir() {
 | 
				
			||||||
 | 
					            std::fs::create_dir("temp").expect("Failed to create temp dir");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        std::fs::read_dir("temp")
 | 
				
			||||||
 | 
					            .expect("Failed to iter temp dir")
 | 
				
			||||||
 | 
					            .for_each(|dir| {
 | 
				
			||||||
 | 
					            std::fs::remove_file(dir.expect("Failed to retrieve temp dir entry").path()).expect("Failed to delete file in temp dir");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        DELETE_RT.spawn(async {});
 | 
				
			||||||
 | 
					        ZIP_RT.spawn(async {});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    routes::build_routes(db)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub static WINDOWS_INVALID_CHARS: &str = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F<>:\"/\\|";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct ZipProgressEntry {
 | 
				
			||||||
 | 
					    temp_id: u64,
 | 
				
			||||||
 | 
					    done: AtomicBool,
 | 
				
			||||||
 | 
					    progress: AtomicU64,
 | 
				
			||||||
 | 
					    total: AtomicU64,
 | 
				
			||||||
 | 
					    delete_after: AtomicI64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub enum CreateNodeResult {
 | 
				
			||||||
 | 
					    InvalidName,
 | 
				
			||||||
 | 
					    InvalidParent,
 | 
				
			||||||
 | 
					    Exists(bool, i32)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					lazy_static! {
 | 
				
			||||||
 | 
					    static ref DELETE_RT: tokio::runtime::Runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(1).enable_time().build().expect("Failed to create delete runtime");
 | 
				
			||||||
 | 
					    static ref ZIP_RT: tokio::runtime::Runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(3).enable_time().build().expect("Failed to create zip runtime");
 | 
				
			||||||
 | 
					    pub static ref ZIP_TO_PROGRESS: tokio::sync::RwLock<std::collections::HashMap<std::collections::BTreeSet<i32>, Arc<ZipProgressEntry>>> = tokio::sync::RwLock::new(std::collections::HashMap::new());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn cleanup_temp_zips() {
 | 
				
			||||||
 | 
					    let mut existing = ZIP_TO_PROGRESS.write().await;
 | 
				
			||||||
 | 
					    existing.retain(|_, v| {
 | 
				
			||||||
 | 
					        if Arc::strong_count(v) == 1 && v.done.load(Ordering::Relaxed) && v.delete_after.load(Ordering::Relaxed) <= chrono::Utc::now().timestamp() {
 | 
				
			||||||
 | 
					            std::fs::remove_file(std::path::Path::new(&format!("./temp/{}", v.temp_id))).expect("Failed to delete temp file");
 | 
				
			||||||
 | 
					            false
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_nodes_recursive(root: crate::db::Inode, db: &mut crate::db::DBConnection) -> VecDeque<crate::db::Inode> {
 | 
				
			||||||
 | 
					    let mut nodes = VecDeque::from(vec![root.clone()]);
 | 
				
			||||||
 | 
					    if root.is_file { return nodes; }
 | 
				
			||||||
 | 
					    let mut nodes_to_check = VecDeque::from(vec![root]);
 | 
				
			||||||
 | 
					    while !nodes_to_check.is_empty() {
 | 
				
			||||||
 | 
					        let node = nodes_to_check.pop_front().unwrap();
 | 
				
			||||||
 | 
					        db.get_children(node.id).iter().for_each(|node| {
 | 
				
			||||||
 | 
					            nodes.push_back(node.clone());
 | 
				
			||||||
 | 
					            if !node.is_file { nodes_to_check.push_front(node.clone()); }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    nodes
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_node_path(node: crate::db::Inode, db: &mut crate::db::DBConnection) -> VecDeque<crate::db::Inode> {
 | 
				
			||||||
 | 
					    let mut path = VecDeque::from(vec![node.clone()]);
 | 
				
			||||||
 | 
					    let mut node = node;
 | 
				
			||||||
 | 
					    while let Some(parent) = node.parent_id {
 | 
				
			||||||
 | 
					        node = db.get_node(parent).expect("Failed to get node parent");
 | 
				
			||||||
 | 
					        path.push_front(node.clone());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    path
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_total_size(node: crate::db::Inode, db: &mut crate::db::DBConnection) -> u64 {
 | 
				
			||||||
 | 
					    let nodes = get_nodes_recursive(node, db);
 | 
				
			||||||
 | 
					    nodes.iter().fold(0_u64, |acc, node| acc + node.size.unwrap_or(0) as u64)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn get_node_and_validate(user: &crate::db::User, node: i32, db: &mut crate::db::DBConnection) -> Option<crate::db::Inode> {
 | 
				
			||||||
 | 
					    let node = db.get_node(node)?;
 | 
				
			||||||
 | 
					    if node.owner_id != user.id {
 | 
				
			||||||
 | 
					        None
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        Some(node)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create_node(name: String, owner: &crate::db::User, file: bool, parent: Option<i32>, force: bool, db: &mut crate::db::DBConnection)
 | 
				
			||||||
 | 
					    -> Result<crate::db::Inode, CreateNodeResult> {
 | 
				
			||||||
 | 
					    if !force && (name.is_empty() || name.starts_with(' ') || name.contains(|c| {
 | 
				
			||||||
 | 
					        WINDOWS_INVALID_CHARS.contains(c)
 | 
				
			||||||
 | 
					    } || name.ends_with(' ') || name.ends_with('.') || name == "." || name == "..")) {
 | 
				
			||||||
 | 
					        return Err(CreateNodeResult::InvalidName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some(parent) = parent {
 | 
				
			||||||
 | 
					        let parent = match get_node_and_validate(owner, parent, db) {
 | 
				
			||||||
 | 
					            None => { return Err(CreateNodeResult::InvalidParent); }
 | 
				
			||||||
 | 
					            Some(v) => v
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        if parent.is_file { return Err(CreateNodeResult::InvalidParent); }
 | 
				
			||||||
 | 
					        let children = db.get_children(parent.id);
 | 
				
			||||||
 | 
					        for child in children {
 | 
				
			||||||
 | 
					            if child.name == name {
 | 
				
			||||||
 | 
					                return Err(CreateNodeResult::Exists(child.is_file, child.id));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(db.create_node(file, name, parent, owner.id))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn delete_node_root(node: &crate::db::Inode, db: &mut crate::db::DBConnection) {
 | 
				
			||||||
 | 
					    get_nodes_recursive(node.clone(), db).iter().rev().for_each(|node| {
 | 
				
			||||||
 | 
					        db.delete_node(node);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn delete_node(node: &crate::db::Inode, sender: &mut warp::hyper::body::Sender, db: &mut crate::db::DBConnection) {
 | 
				
			||||||
 | 
					    if node.parent_id.is_none() { return; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for node in get_nodes_recursive(node.clone(), db).iter().rev() {
 | 
				
			||||||
 | 
					        sender.send_data(warp::hyper::body::Bytes::from(format!("Deleting {}...", generate_path(node, db)))).await.unwrap();
 | 
				
			||||||
 | 
					        db.delete_node(node);
 | 
				
			||||||
 | 
					        sender.send_data(warp::hyper::body::Bytes::from(" Done \n")).await.unwrap();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn generate_path(node: &crate::db::Inode, db: &mut crate::db::DBConnection) -> String {
 | 
				
			||||||
 | 
					    let mut path = String::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_node_path(node.clone(), db).iter().for_each(|node| {
 | 
				
			||||||
 | 
					        if node.parent_id.is_none() {
 | 
				
			||||||
 | 
					            path += "/";
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            path += &node.name;
 | 
				
			||||||
 | 
					            if !node.is_file {
 | 
				
			||||||
 | 
					                path += "/";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    path
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn generate_path_dto(node: &crate::db::Inode, db: &mut crate::db::DBConnection) -> crate::dto::responses::GetPath {
 | 
				
			||||||
 | 
					    let mut get_path = crate::dto::responses::GetPath {
 | 
				
			||||||
 | 
					        segments: Vec::new()
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_node_path(node.clone(), db).iter().for_each(|node| {
 | 
				
			||||||
 | 
					        if node.parent_id.is_none() {
 | 
				
			||||||
 | 
					            get_path.segments.push(crate::dto::responses::GetPathSegment {
 | 
				
			||||||
 | 
					                path: "/".to_owned(),
 | 
				
			||||||
 | 
					                node: Some(node.id)
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            get_path.segments.push(crate::dto::responses::GetPathSegment {
 | 
				
			||||||
 | 
					                path: node.name.clone(),
 | 
				
			||||||
 | 
					                node: Some(node.id)
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            if !node.is_file {
 | 
				
			||||||
 | 
					                get_path.segments.push(crate::dto::responses::GetPathSegment {
 | 
				
			||||||
 | 
					                    path: "/".to_owned(),
 | 
				
			||||||
 | 
					                    node: None
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_path
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn get_file_stream_body(path: String) -> warp::hyper::Body {
 | 
				
			||||||
 | 
					    warp::hyper::Body::wrap_stream(
 | 
				
			||||||
 | 
					        tokio::fs::File::open(path)
 | 
				
			||||||
 | 
					            .map_ok(|file|
 | 
				
			||||||
 | 
					                tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new())
 | 
				
			||||||
 | 
					                    .map_ok(bytes::BytesMut::freeze)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .try_flatten_stream()
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										444
									
								
								backend/src/routes/fs/routes.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										444
									
								
								backend/src/routes/fs/routes.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,444 @@
 | 
				
			|||||||
 | 
					use std::collections::{BTreeSet, HashMap};
 | 
				
			||||||
 | 
					use std::io::{Read, Write};
 | 
				
			||||||
 | 
					use std::sync::atomic::Ordering;
 | 
				
			||||||
 | 
					use futures::{Stream, StreamExt};
 | 
				
			||||||
 | 
					use headers::HeaderMapExt;
 | 
				
			||||||
 | 
					use warp::{Filter, Reply};
 | 
				
			||||||
 | 
					use crate::db::{DBConnection, DBPool, with_db};
 | 
				
			||||||
 | 
					use crate::dto;
 | 
				
			||||||
 | 
					use crate::routes::{AppError, get_reply};
 | 
				
			||||||
 | 
					use crate::routes::filters::{authenticated, UserInfo};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_routes(db: DBPool) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
 | 
				
			||||||
 | 
					    let root = warp::path!("fs" / "root")
 | 
				
			||||||
 | 
					        .and(warp::get())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(root);
 | 
				
			||||||
 | 
					    let node = warp::path!("fs" / "node" / i32)
 | 
				
			||||||
 | 
					        .and(warp::get())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(node)
 | 
				
			||||||
 | 
					        .with(warp::compression::brotli());
 | 
				
			||||||
 | 
					    let path = warp::path!("fs" / "path" / i32)
 | 
				
			||||||
 | 
					        .and(warp::get())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(path);
 | 
				
			||||||
 | 
					    let create_folder = warp::path!("fs" / "create_folder")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(|data, info, db| create_node(data, info, db, false));
 | 
				
			||||||
 | 
					    let create_file = warp::path!("fs" / "create_file")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(|data, info, db| create_node(data, info, db, true));
 | 
				
			||||||
 | 
					    let delete_node = warp::path!("fs" / "delete" / i32)
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(delete_node);
 | 
				
			||||||
 | 
					    let upload = warp::path!("fs" / "upload" / i32)
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::stream())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(upload);
 | 
				
			||||||
 | 
					    let create_zip = warp::path!("fs" / "create_zip")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::json())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(create_zip);
 | 
				
			||||||
 | 
					    let download = warp::path!("fs" / "download")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::form())
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(download);
 | 
				
			||||||
 | 
					    let download_multi = warp::path!("fs" / "download_multi")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::body::form())
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(download_multi);
 | 
				
			||||||
 | 
					    let download_preview = warp::path!("fs" / "download_preview" / i32)
 | 
				
			||||||
 | 
					        .and(warp::get())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(download_preview);
 | 
				
			||||||
 | 
					    let get_type = warp::path!("fs" / "get_type" / i32)
 | 
				
			||||||
 | 
					        .and(warp::get())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db))
 | 
				
			||||||
 | 
					        .and_then(get_type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    root.or(node).or(path).or(create_folder).or(create_file).or(delete_node).or(upload).or(create_zip).or(download).or(download_multi).or(download_preview).or(get_type)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn root(info: UserInfo) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Root {
 | 
				
			||||||
 | 
					        statusCode: 200,
 | 
				
			||||||
 | 
					        rootId: info.0.root_id
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn node(node: i32, info: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let guard_lock = DBConnection::get_lock(info.0.id).await;
 | 
				
			||||||
 | 
					    let _guard = guard_lock.read().await;
 | 
				
			||||||
 | 
					    let node = super::get_node_and_validate(&info.0, node, &mut db)
 | 
				
			||||||
 | 
					        .ok_or(AppError::BadRequest("Unknown node"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::GetNode {
 | 
				
			||||||
 | 
					        statusCode: 200,
 | 
				
			||||||
 | 
					        id: node.id,
 | 
				
			||||||
 | 
					        name: node.name,
 | 
				
			||||||
 | 
					        isFile: node.is_file,
 | 
				
			||||||
 | 
					        preview: node.has_preview,
 | 
				
			||||||
 | 
					        parent: node.parent_id,
 | 
				
			||||||
 | 
					        size: node.size,
 | 
				
			||||||
 | 
					        children: (!node.is_file).then(|| {
 | 
				
			||||||
 | 
					            db.get_children(node.id).iter().map(|child| dto::responses::GetNodeEntry {
 | 
				
			||||||
 | 
					                id: child.id,
 | 
				
			||||||
 | 
					                name: child.name.clone(),
 | 
				
			||||||
 | 
					                isFile: child.is_file,
 | 
				
			||||||
 | 
					                preview: child.has_preview,
 | 
				
			||||||
 | 
					                parent: child.parent_id,
 | 
				
			||||||
 | 
					                size: child.size
 | 
				
			||||||
 | 
					            }).collect()
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn path(node: i32, info: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let guard_lock = DBConnection::get_lock(info.0.id).await;
 | 
				
			||||||
 | 
					    let _guard = guard_lock.read().await;
 | 
				
			||||||
 | 
					    let node = super::get_node_and_validate(&info.0, node, &mut db)
 | 
				
			||||||
 | 
					        .ok_or(AppError::BadRequest("Unknown node"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&super::generate_path_dto(&node, &mut db))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn create_node(data: dto::requests::CreateNode, info: UserInfo, mut db: DBConnection, file: bool) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let guard_lock = DBConnection::get_lock(info.0.id).await;
 | 
				
			||||||
 | 
					    let _guard = guard_lock.read().await;
 | 
				
			||||||
 | 
					    let node = super::create_node(data.name, &info.0, file, Some(data.parent), false, &mut db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match node {
 | 
				
			||||||
 | 
					        Ok(v) => get_reply(&dto::responses::NewNode {
 | 
				
			||||||
 | 
					                statusCode: 200,
 | 
				
			||||||
 | 
					                id: v.id
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        Err(v) => {
 | 
				
			||||||
 | 
					            match v {
 | 
				
			||||||
 | 
					                super::CreateNodeResult::InvalidName => AppError::BadRequest("Invalid name").err(),
 | 
				
			||||||
 | 
					                super::CreateNodeResult::InvalidParent => AppError::BadRequest("Invalid parent").err(),
 | 
				
			||||||
 | 
					                super::CreateNodeResult::Exists(file, id) => get_reply(&dto::responses::NodeExists {
 | 
				
			||||||
 | 
					                    statusCode: 200,
 | 
				
			||||||
 | 
					                    id,
 | 
				
			||||||
 | 
					                    exists: true,
 | 
				
			||||||
 | 
					                    isFile: file
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn delete_node(node: i32, info: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let guard_lock = DBConnection::get_lock(info.0.id).await;
 | 
				
			||||||
 | 
					    let inner_guard_lock = guard_lock.clone();
 | 
				
			||||||
 | 
					    let _guard = guard_lock.read().await;
 | 
				
			||||||
 | 
					    let node = super::get_node_and_validate(&info.0, node, &mut db)
 | 
				
			||||||
 | 
					        .ok_or(AppError::BadRequest("Unknown node"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if node.parent_id.is_none() {
 | 
				
			||||||
 | 
					        return AppError::BadRequest("Can't delete root").err();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let (mut sender, body) = warp::hyper::Body::channel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sender.send_data(warp::hyper::body::Bytes::from("Waiting in queue\n")).await.unwrap();
 | 
				
			||||||
 | 
					    super::DELETE_RT.spawn(async move {
 | 
				
			||||||
 | 
					        let guard_lock = inner_guard_lock.clone();
 | 
				
			||||||
 | 
					        let _guard = guard_lock.write().await;
 | 
				
			||||||
 | 
					        super::delete_node(&node, &mut sender, &mut db).await;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut resp = warp::reply::Response::new(body);
 | 
				
			||||||
 | 
					    *resp.status_mut() = warp::http::StatusCode::OK;
 | 
				
			||||||
 | 
					    resp.headers_mut().typed_insert(
 | 
				
			||||||
 | 
					        headers::ContentType::text_utf8()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(resp)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn upload<S, B>(node: i32, stream: S, info: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        S: Stream<Item = Result<B, warp::Error>>,
 | 
				
			||||||
 | 
					        S: StreamExt,
 | 
				
			||||||
 | 
					        B: warp::Buf
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    let guard_lock = DBConnection::get_lock(info.0.id).await;
 | 
				
			||||||
 | 
					    let _guard = guard_lock.read().await;
 | 
				
			||||||
 | 
					    let mut node = super::get_node_and_validate(&info.0, node, &mut db)
 | 
				
			||||||
 | 
					        .ok_or(AppError::BadRequest("Unknown node"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !node.is_file {
 | 
				
			||||||
 | 
					        return AppError::BadRequest("Can't upload to a directory").err();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut file_size = 0_i64;
 | 
				
			||||||
 | 
					    let file_name = format!("./files/{}", node.id);
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        let mut file = std::fs::File::create(file_name.clone()).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stream.for_each(|f| {
 | 
				
			||||||
 | 
					            let mut buffer = f.unwrap();
 | 
				
			||||||
 | 
					            file_size += buffer.remaining() as i64;
 | 
				
			||||||
 | 
					            while buffer.remaining() != 0 {
 | 
				
			||||||
 | 
					                let chunk = buffer.chunk();
 | 
				
			||||||
 | 
					                buffer.advance(file.write(chunk).expect("Failed to write file"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            futures::future::ready(())
 | 
				
			||||||
 | 
					        }).await;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let generate_preview = || -> Option<()> {
 | 
				
			||||||
 | 
					        if file_size > 20 * 1024 * 1024 { return None; }
 | 
				
			||||||
 | 
					        let mime = mime_guess::from_path(std::path::Path::new(&node.name)).first()?.to_string();
 | 
				
			||||||
 | 
					        let img = image::load(
 | 
				
			||||||
 | 
					            std::io::BufReader::new(std::fs::File::open(file_name.clone()).unwrap()),
 | 
				
			||||||
 | 
					            image::ImageFormat::from_mime_type(mime)?
 | 
				
			||||||
 | 
					        ).ok()?;
 | 
				
			||||||
 | 
					        let img = img.resize(300, 300, image::imageops::FilterType::Triangle);
 | 
				
			||||||
 | 
					        img.save(std::path::Path::new(&(file_name + "_preview.jpg"))).expect("Failed to save preview image");
 | 
				
			||||||
 | 
					        Some(())
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    node.has_preview = generate_preview().is_some();
 | 
				
			||||||
 | 
					    node.size = Some(file_size);
 | 
				
			||||||
 | 
					    db.save_node(&node);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					        statusCode: 200
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn create_zip(data: dto::requests::CreateZip, info: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let guard_lock = DBConnection::get_lock(info.0.id).await;
 | 
				
			||||||
 | 
					    let inner_guard_lock = guard_lock.clone();
 | 
				
			||||||
 | 
					    let _guard = guard_lock.read().await;
 | 
				
			||||||
 | 
					    let mut nodes: Vec<crate::db::Inode> = Vec::new();
 | 
				
			||||||
 | 
					    for node in data.nodes.clone() {
 | 
				
			||||||
 | 
					        nodes.push(
 | 
				
			||||||
 | 
					            super::get_node_and_validate(&info.0, node, &mut db)
 | 
				
			||||||
 | 
					                .ok_or(AppError::BadRequest("Unknown node"))?
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let zip_nodes = BTreeSet::from_iter(data.nodes.iter().copied());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        let guard = super::ZIP_TO_PROGRESS.read().await;
 | 
				
			||||||
 | 
					        if let Some(entry) = guard.get(&zip_nodes) {
 | 
				
			||||||
 | 
					            return get_reply(&dto::responses::CreateZipDone {
 | 
				
			||||||
 | 
					                statusCode: 200,
 | 
				
			||||||
 | 
					                done: entry.done.load(Ordering::Relaxed),
 | 
				
			||||||
 | 
					                progress: Some(entry.progress.load(Ordering::Relaxed)),
 | 
				
			||||||
 | 
					                total: Some(entry.total.load(Ordering::Relaxed))
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let entry = {
 | 
				
			||||||
 | 
					        let mut guard = super::ZIP_TO_PROGRESS.write().await;
 | 
				
			||||||
 | 
					        guard.insert(zip_nodes.clone(), std::sync::Arc::from(super::ZipProgressEntry {
 | 
				
			||||||
 | 
					            temp_id: super::NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed),
 | 
				
			||||||
 | 
					            done: std::sync::atomic::AtomicBool::new(false),
 | 
				
			||||||
 | 
					            progress: std::sync::atomic::AtomicU64::new(0),
 | 
				
			||||||
 | 
					            total: std::sync::atomic::AtomicU64::new(1),
 | 
				
			||||||
 | 
					            delete_after: std::sync::atomic::AtomicI64::new(0)
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					        guard.get(&zip_nodes).unwrap().clone()
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    super::ZIP_RT.spawn(async move {
 | 
				
			||||||
 | 
					        type NodeMap = HashMap<i32, crate::db::Inode>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super::cleanup_temp_zips().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let _guard = inner_guard_lock.read().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn get_path(node: &crate::db::Inode, dirs: &NodeMap) -> String {
 | 
				
			||||||
 | 
					            let mut path = node.name.clone();
 | 
				
			||||||
 | 
					            let mut _node = dirs.get(&node.parent_id.unwrap_or(-1));
 | 
				
			||||||
 | 
					            while let Some(node) = _node {
 | 
				
			||||||
 | 
					                path.insert_str(0, &(node.name.clone() + "/"));
 | 
				
			||||||
 | 
					                _node = dirs.get(&node.parent_id.unwrap_or(-1));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            path
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        nodes.iter().for_each(|node| {
 | 
				
			||||||
 | 
					            entry.total.fetch_add(super::get_total_size(node.clone(), &mut db), Ordering::Relaxed);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        entry.total.fetch_sub(1, Ordering::Relaxed);
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let mut buf = vec![0_u8; 1024 * 1024 * 4];
 | 
				
			||||||
 | 
					            let file = std::fs::File::create(format!("./temp/{}", entry.temp_id)).expect("Failed to create temp file");
 | 
				
			||||||
 | 
					            let mut zip = zip::ZipWriter::new(file);
 | 
				
			||||||
 | 
					            let zip_options = zip::write::FileOptions::default().large_file(true);
 | 
				
			||||||
 | 
					            let (files, dirs): (NodeMap, NodeMap) =
 | 
				
			||||||
 | 
					                nodes.iter()
 | 
				
			||||||
 | 
					                    .flat_map(|node| super::get_nodes_recursive(node.clone(), &mut db))
 | 
				
			||||||
 | 
					                    .map(|node| (node.id, node))
 | 
				
			||||||
 | 
					                    .partition(|v| v.1.is_file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dirs.values().for_each(|dir| {
 | 
				
			||||||
 | 
					                zip.add_directory(get_path(dir, &dirs), zip_options).expect("Failed to add dir to zip");
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            files.values().for_each(|node| {
 | 
				
			||||||
 | 
					                zip.start_file(get_path(node, &dirs), zip_options).expect("Failed to start zip file");
 | 
				
			||||||
 | 
					                let mut file = std::fs::File::open(format!("./files/{}", node.id)).expect("Failed to open file for zip");
 | 
				
			||||||
 | 
					                loop {
 | 
				
			||||||
 | 
					                    let count = file.read(&mut buf).expect("Failed to read file for zip");
 | 
				
			||||||
 | 
					                    if count == 0 { break; }
 | 
				
			||||||
 | 
					                    zip.write_all(&buf[..count]).expect("Failed to write zip");
 | 
				
			||||||
 | 
					                    entry.progress.fetch_add(count as u64, Ordering::Relaxed);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            zip.finish().expect("Failed to finish zip");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        entry.done.store(true, Ordering::Relaxed);
 | 
				
			||||||
 | 
					        entry.delete_after.store(chrono::Utc::now().timestamp() + 10 * 60, Ordering::Relaxed);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::CreateZipDone {
 | 
				
			||||||
 | 
					        statusCode: 200,
 | 
				
			||||||
 | 
					        done: false,
 | 
				
			||||||
 | 
					        progress: Some(0),
 | 
				
			||||||
 | 
					        total: Some(1)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn download(data: dto::requests::Download, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let info = crate::routes::filters::authorize_jwt(data.jwtToken, &mut db).await?;
 | 
				
			||||||
 | 
					    let guard_lock = DBConnection::get_lock(info.0.id).await;
 | 
				
			||||||
 | 
					    let _guard = guard_lock.read().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let node: crate::db::Inode = super::get_node_and_validate(&info.0, data.id, &mut db)
 | 
				
			||||||
 | 
					        .ok_or(AppError::BadRequest("Unknown node"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if node.is_file {
 | 
				
			||||||
 | 
					        let mut resp = warp::reply::Response::new(super::get_file_stream_body(
 | 
				
			||||||
 | 
					            format!("./files/{}", node.id)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        *resp.status_mut() = warp::http::StatusCode::OK;
 | 
				
			||||||
 | 
					        resp.headers_mut().typed_insert(
 | 
				
			||||||
 | 
					            headers::ContentLength(node.size.unwrap() as u64)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        resp.headers_mut().typed_insert(
 | 
				
			||||||
 | 
					            headers::ContentType::from(
 | 
				
			||||||
 | 
					                mime_guess::from_path(std::path::Path::new(&node.name)).first_or_octet_stream()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        resp.headers_mut().insert(
 | 
				
			||||||
 | 
					            "Content-Disposition",
 | 
				
			||||||
 | 
					            ("attachment; filename=".to_owned() + &node.name).parse().unwrap()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        Ok(resp)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        let nodes_key = BTreeSet::from([node.id]);
 | 
				
			||||||
 | 
					        let guard = super::ZIP_TO_PROGRESS.read().await;
 | 
				
			||||||
 | 
					        let entry = guard.get(&nodes_key)
 | 
				
			||||||
 | 
					            .ok_or(AppError::BadRequest("Unknown node"))?;
 | 
				
			||||||
 | 
					        if !entry.done.load(Ordering::Relaxed) {
 | 
				
			||||||
 | 
					            AppError::BadRequest("Unknown node").err()
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            let file = format!("./temp/{}", entry.temp_id);
 | 
				
			||||||
 | 
					            let mut resp = warp::reply::Response::new(super::get_file_stream_body(file.clone()));
 | 
				
			||||||
 | 
					            *resp.status_mut() = warp::http::StatusCode::OK;
 | 
				
			||||||
 | 
					            resp.headers_mut().typed_insert(
 | 
				
			||||||
 | 
					                headers::ContentLength(std::fs::metadata(std::path::Path::new(&file)).unwrap().len())
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            resp.headers_mut().typed_insert(
 | 
				
			||||||
 | 
					                headers::ContentType::from(
 | 
				
			||||||
 | 
					                    mime_guess::from_ext("zip").first().unwrap()
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            resp.headers_mut().insert(
 | 
				
			||||||
 | 
					                "Content-Disposition",
 | 
				
			||||||
 | 
					                ("attachment; filename=".to_owned() + &node.name + ".zip").parse().unwrap()
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            Ok(resp)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn download_multi(data: dto::requests::DownloadMulti, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let info = crate::routes::filters::authorize_jwt(data.jwtToken, &mut db).await?;
 | 
				
			||||||
 | 
					    let guard_lock = DBConnection::get_lock(info.0.id).await;
 | 
				
			||||||
 | 
					    let _guard = guard_lock.read().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut nodes: Vec<crate::db::Inode> = Vec::new();
 | 
				
			||||||
 | 
					    for node in data.id.split(',').map(|v| v.parse::<i32>()
 | 
				
			||||||
 | 
					            .map_err(|_| AppError::BadRequest("Failed to parse").reject())
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					        nodes.push(
 | 
				
			||||||
 | 
					            super::get_node_and_validate(&info.0, node?, &mut db)
 | 
				
			||||||
 | 
					                .ok_or(AppError::BadRequest("Unknown node"))?
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let nodes_key = BTreeSet::from_iter(nodes.iter().map(|node| node.id));
 | 
				
			||||||
 | 
					    let guard = super::ZIP_TO_PROGRESS.read().await;
 | 
				
			||||||
 | 
					    let entry = guard.get(&nodes_key)
 | 
				
			||||||
 | 
					        .ok_or(AppError::BadRequest("Unknown zip"))?;
 | 
				
			||||||
 | 
					    if !entry.done.load(Ordering::Relaxed) {
 | 
				
			||||||
 | 
					        AppError::BadRequest("Unfinished zip").err()
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        let file = format!("./temp/{}", entry.temp_id);
 | 
				
			||||||
 | 
					        let mut resp = warp::reply::Response::new(super::get_file_stream_body(file.clone()));
 | 
				
			||||||
 | 
					        *resp.status_mut() = warp::http::StatusCode::OK;
 | 
				
			||||||
 | 
					        resp.headers_mut().typed_insert(
 | 
				
			||||||
 | 
					            headers::ContentLength(std::fs::metadata(std::path::Path::new(&file)).unwrap().len())
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        resp.headers_mut().typed_insert(
 | 
				
			||||||
 | 
					            headers::ContentType::from(
 | 
				
			||||||
 | 
					                mime_guess::from_ext("zip").first().unwrap()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        resp.headers_mut().insert(
 | 
				
			||||||
 | 
					            "Content-Disposition",
 | 
				
			||||||
 | 
					            "attachment; filename=files.zip".parse().unwrap()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        Ok(resp)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn download_preview(node: i32, info: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let guard_lock = DBConnection::get_lock(info.0.id).await;
 | 
				
			||||||
 | 
					    let _guard = guard_lock.read().await;
 | 
				
			||||||
 | 
					    let node: crate::db::Inode = super::get_node_and_validate(&info.0, node, &mut db)
 | 
				
			||||||
 | 
					        .ok_or(AppError::BadRequest("Unknown node"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if node.has_preview {
 | 
				
			||||||
 | 
					        let file = format!("./files/{}_preview.jpg", node.id);
 | 
				
			||||||
 | 
					        get_reply(&dto::responses::DownloadBase64 {
 | 
				
			||||||
 | 
					            statusCode: 200,
 | 
				
			||||||
 | 
					            data: "data:image/png;base64,".to_owned() + &base64::encode(std::fs::read(std::path::Path::new(&file)).unwrap())
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        AppError::BadRequest("No preview").err()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn get_type(node: i32, info: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    let node: crate::db::Inode = super::get_node_and_validate(&info.0, node, &mut db)
 | 
				
			||||||
 | 
					        .ok_or(AppError::BadRequest("Unknown node"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Type {
 | 
				
			||||||
 | 
					        statusCode: 200,
 | 
				
			||||||
 | 
					        _type: mime_guess::from_path(std::path::Path::new(&node.name)).first_or_octet_stream().to_string()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										114
									
								
								backend/src/routes/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								backend/src/routes/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
				
			|||||||
 | 
					mod filters;
 | 
				
			||||||
 | 
					mod auth;
 | 
				
			||||||
 | 
					mod admin;
 | 
				
			||||||
 | 
					mod user;
 | 
				
			||||||
 | 
					pub mod fs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use warp::{Filter, Reply};
 | 
				
			||||||
 | 
					use crate::db::DBPool;
 | 
				
			||||||
 | 
					use crate::dto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_routes(db: DBPool) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
 | 
				
			||||||
 | 
					    warp::path::path("api")
 | 
				
			||||||
 | 
					        .and(
 | 
				
			||||||
 | 
					            auth::build_routes(db.clone())
 | 
				
			||||||
 | 
					                .or(admin::build_routes(db.clone()))
 | 
				
			||||||
 | 
					                .or(user::build_routes(db.clone()))
 | 
				
			||||||
 | 
					                .or(fs::build_routes(db))
 | 
				
			||||||
 | 
					                .recover(error_handler)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .or(warp::fs::dir("./static/"))
 | 
				
			||||||
 | 
					        .or(warp::fs::file("./static/index.html"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn get_reply<T>(data: &T) -> Result<warp::reply::Response, warp::Rejection> where T: serde::Serialize {
 | 
				
			||||||
 | 
					    Ok(warp::reply::with_status(warp::reply::json(data), warp::http::StatusCode::OK).into_response())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(thiserror::Error, Debug, Clone)]
 | 
				
			||||||
 | 
					pub enum AppError {
 | 
				
			||||||
 | 
					    #[error("unauthorized")]
 | 
				
			||||||
 | 
					    Unauthorized(&'static str),
 | 
				
			||||||
 | 
					    #[error("forbidden")]
 | 
				
			||||||
 | 
					    Forbidden(&'static str),
 | 
				
			||||||
 | 
					    #[error("bad request")]
 | 
				
			||||||
 | 
					    BadRequest(&'static str),
 | 
				
			||||||
 | 
					    #[error("internal error")]
 | 
				
			||||||
 | 
					    InternalError(&'static str)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					impl warp::reject::Reject for AppError {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl AppError {
 | 
				
			||||||
 | 
					    pub fn reject(&self) -> warp::reject::Rejection {
 | 
				
			||||||
 | 
					        warp::reject::custom(self.clone())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn err<T>(&self) -> Result<T, warp::reject::Rejection> {
 | 
				
			||||||
 | 
					        Err(self.reject())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn error_handler(err: warp::reject::Rejection) -> Result<impl Reply, std::convert::Infallible> {
 | 
				
			||||||
 | 
					    if err.is_not_found() {
 | 
				
			||||||
 | 
					        return Ok(warp::reply::with_status(
 | 
				
			||||||
 | 
					            warp::reply::json(&dto::responses::Error {
 | 
				
			||||||
 | 
					                statusCode: 404,
 | 
				
			||||||
 | 
					                message: "bruh".to_owned()
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					            warp::http::StatusCode::NOT_FOUND
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if let Some(e) = err.find::<AppError>() {
 | 
				
			||||||
 | 
					        return Ok(warp::reply::with_status(
 | 
				
			||||||
 | 
					            warp::reply::json(&dto::responses::Error {
 | 
				
			||||||
 | 
					                statusCode: match e {
 | 
				
			||||||
 | 
					                    AppError::BadRequest(_) => 400,
 | 
				
			||||||
 | 
					                    AppError::Unauthorized(_) => 401,
 | 
				
			||||||
 | 
					                    AppError::Forbidden(_) => 403,
 | 
				
			||||||
 | 
					                    AppError::InternalError(_) => 500
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                message: match e {
 | 
				
			||||||
 | 
					                    AppError::BadRequest(v) => v.to_string(),
 | 
				
			||||||
 | 
					                    AppError::Unauthorized(v) => v.to_string(),
 | 
				
			||||||
 | 
					                    AppError::Forbidden(v) => v.to_string(),
 | 
				
			||||||
 | 
					                    AppError::InternalError(v) => v.to_string()
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					            match e {
 | 
				
			||||||
 | 
					                AppError::BadRequest(_) => warp::http::StatusCode::BAD_REQUEST,
 | 
				
			||||||
 | 
					                AppError::Unauthorized(_) => warp::http::StatusCode::UNAUTHORIZED,
 | 
				
			||||||
 | 
					                AppError::Forbidden(_) => warp::http::StatusCode::FORBIDDEN,
 | 
				
			||||||
 | 
					                AppError::InternalError(_) => warp::http::StatusCode::INTERNAL_SERVER_ERROR
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if let Some(e) = err.find::<warp::body::BodyDeserializeError>() {
 | 
				
			||||||
 | 
					        return Ok(warp::reply::with_status(
 | 
				
			||||||
 | 
					            warp::reply::json(&dto::responses::Error {
 | 
				
			||||||
 | 
					                statusCode: 400,
 | 
				
			||||||
 | 
					                message: e.to_string(),
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					            warp::http::StatusCode::BAD_REQUEST
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if let Some(e) = err.find::<warp::reject::InvalidQuery>() {
 | 
				
			||||||
 | 
					        return Ok(warp::reply::with_status(
 | 
				
			||||||
 | 
					            warp::reply::json(&dto::responses::Error {
 | 
				
			||||||
 | 
					                statusCode: 400,
 | 
				
			||||||
 | 
					                message: e.to_string(),
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					            warp::http::StatusCode::BAD_REQUEST
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if let Some(e) = err.find::<warp::reject::MethodNotAllowed>() {
 | 
				
			||||||
 | 
					        return Ok(warp::reply::with_status(
 | 
				
			||||||
 | 
					            warp::reply::json(&dto::responses::Error {
 | 
				
			||||||
 | 
					                statusCode: 405,
 | 
				
			||||||
 | 
					                message: e.to_string(),
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					            warp::http::StatusCode::METHOD_NOT_ALLOWED
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Err(err).expect("Can't handle error")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										42
									
								
								backend/src/routes/user.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								backend/src/routes/user.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					use warp::{Filter, Reply};
 | 
				
			||||||
 | 
					use crate::db::{DBConnection, DBPool, with_db};
 | 
				
			||||||
 | 
					use crate::dto;
 | 
				
			||||||
 | 
					use crate::routes::get_reply;
 | 
				
			||||||
 | 
					use crate::routes::filters::{authenticated, UserInfo};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_routes(db: DBPool) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
 | 
				
			||||||
 | 
					    let info = warp::path!("user" / "info")
 | 
				
			||||||
 | 
					        .and(warp::get())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and_then(info);
 | 
				
			||||||
 | 
					    let delete_user = warp::path!("user" / "delete")
 | 
				
			||||||
 | 
					        .and(warp::post())
 | 
				
			||||||
 | 
					        .and(authenticated(db.clone()))
 | 
				
			||||||
 | 
					        .and(with_db(db))
 | 
				
			||||||
 | 
					        .and_then(delete_user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    info.or(delete_user)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn info(info: UserInfo) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::UserInfo {
 | 
				
			||||||
 | 
					        statusCode: info.0.id,
 | 
				
			||||||
 | 
					        name: info.0.name,
 | 
				
			||||||
 | 
					        gitlab: info.0.gitlab,
 | 
				
			||||||
 | 
					        tfaEnabled: info.0.tfa_type != crate::db::TfaTypes::None
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn delete_user(info: UserInfo, mut db: DBConnection) -> Result<impl Reply, warp::Rejection> {
 | 
				
			||||||
 | 
					    db.delete_all_tokens(info.0.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let root_node = super::fs::get_node_and_validate(&info.0, info.0.root_id, &mut db).expect("Failed to get root node for deleting");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    super::fs::delete_node_root(&root_node, &mut db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    db.delete_user(&info.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get_reply(&dto::responses::Success {
 | 
				
			||||||
 | 
					        statusCode: 200
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										42
									
								
								backend/src/schema.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								backend/src/schema.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					// @generated automatically by Diesel CLI.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					diesel::table! {
 | 
				
			||||||
 | 
					    inode (id) {
 | 
				
			||||||
 | 
					        id -> Integer,
 | 
				
			||||||
 | 
					        is_file -> Bool,
 | 
				
			||||||
 | 
					        name -> Text,
 | 
				
			||||||
 | 
					        parent_id -> Nullable<Integer>,
 | 
				
			||||||
 | 
					        owner_id -> Integer,
 | 
				
			||||||
 | 
					        size -> Nullable<BigInt>,
 | 
				
			||||||
 | 
					        has_preview -> Bool,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					diesel::table! {
 | 
				
			||||||
 | 
					    tokens (id) {
 | 
				
			||||||
 | 
					        id -> Integer,
 | 
				
			||||||
 | 
					        owner_id -> Integer,
 | 
				
			||||||
 | 
					        exp -> BigInt,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					diesel::table! {
 | 
				
			||||||
 | 
					    user (id) {
 | 
				
			||||||
 | 
					        id -> Integer,
 | 
				
			||||||
 | 
					        gitlab -> Bool,
 | 
				
			||||||
 | 
					        name -> Text,
 | 
				
			||||||
 | 
					        password -> Text,
 | 
				
			||||||
 | 
					        role -> SmallInt,
 | 
				
			||||||
 | 
					        root_id -> Integer,
 | 
				
			||||||
 | 
					        tfa_type -> SmallInt,
 | 
				
			||||||
 | 
					        tfa_secret -> Nullable<Binary>,
 | 
				
			||||||
 | 
					        gitlab_at -> Nullable<Text>,
 | 
				
			||||||
 | 
					        gitlab_rt -> Nullable<Text>,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					diesel::allow_tables_to_appear_in_same_query!(
 | 
				
			||||||
 | 
					    inode,
 | 
				
			||||||
 | 
					    tokens,
 | 
				
			||||||
 | 
					    user,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
@@ -1,21 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
 | 
					 | 
				
			||||||
  "name": "backend",
 | 
					 | 
				
			||||||
  "version-string": "1.0.0",
 | 
					 | 
				
			||||||
  "dependencies": [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "name": "drogon",
 | 
					 | 
				
			||||||
      "features": ["orm", "sqlite3"]
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "name": "opencv4",
 | 
					 | 
				
			||||||
      "default-features": false,
 | 
					 | 
				
			||||||
      "features": ["tiff", "png", "jpeg", "webp", "openexr"]
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "jwt-cpp",
 | 
					 | 
				
			||||||
    "botan",
 | 
					 | 
				
			||||||
    "nayuki-qr-code-generator",
 | 
					 | 
				
			||||||
    "openssl",
 | 
					 | 
				
			||||||
    "kubazip"
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -5,6 +5,7 @@ import {
 | 
				
			|||||||
	post_token_form,
 | 
						post_token_form,
 | 
				
			||||||
	isErrorResponse
 | 
						isErrorResponse
 | 
				
			||||||
} from './base';
 | 
					} from './base';
 | 
				
			||||||
 | 
					import axios from 'axios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const get_root = (
 | 
					export const get_root = (
 | 
				
			||||||
	token: string
 | 
						token: string
 | 
				
			||||||
@@ -31,7 +32,7 @@ export const create_folder = (
 | 
				
			|||||||
	Responses.CreateFolder | Responses.CreateFolderExists | Responses.Error
 | 
						Responses.CreateFolder | Responses.CreateFolderExists | Responses.Error
 | 
				
			||||||
> =>
 | 
					> =>
 | 
				
			||||||
	post_token<Requests.CreateFolder>(
 | 
						post_token<Requests.CreateFolder>(
 | 
				
			||||||
		'/api/fs/createFolder',
 | 
							'/api/fs/create_folder',
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			parent: parent,
 | 
								parent: parent,
 | 
				
			||||||
			name: name
 | 
								name: name
 | 
				
			||||||
@@ -47,7 +48,7 @@ export const create_file = (
 | 
				
			|||||||
	Responses.CreateFolder | Responses.CreateFolderExists | Responses.Error
 | 
						Responses.CreateFolder | Responses.CreateFolderExists | Responses.Error
 | 
				
			||||||
> =>
 | 
					> =>
 | 
				
			||||||
	post_token<Requests.CreateFolder>(
 | 
						post_token<Requests.CreateFolder>(
 | 
				
			||||||
		'/api/fs/createFile',
 | 
							'/api/fs/create_file',
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			parent: parent,
 | 
								parent: parent,
 | 
				
			||||||
			name: name
 | 
								name: name
 | 
				
			||||||
@@ -89,14 +90,22 @@ export async function upload_file(
 | 
				
			|||||||
	if ('exists' in node && !node.isFile)
 | 
						if ('exists' in node && !node.isFile)
 | 
				
			||||||
		return { statusCode: 400, message: 'File exists as folder' };
 | 
							return { statusCode: 400, message: 'File exists as folder' };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const form = new FormData();
 | 
						return axios
 | 
				
			||||||
	form.set('file', file.file);
 | 
							.post(`/api/fs/upload/${node.id}`, file.file, {
 | 
				
			||||||
	return post_token_form(
 | 
								headers: {
 | 
				
			||||||
		`/api/fs/upload/${node.id}`,
 | 
									Authorization: 'Bearer ' + token,
 | 
				
			||||||
		form,
 | 
									'Content-type': 'multipart/form-data'
 | 
				
			||||||
		token,
 | 
								},
 | 
				
			||||||
		onProgress
 | 
								onUploadProgress: onProgress
 | 
				
			||||||
	);
 | 
							})
 | 
				
			||||||
 | 
							.then((res) => {
 | 
				
			||||||
 | 
								console.log(res);
 | 
				
			||||||
 | 
								return res.data;
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.catch((err) => {
 | 
				
			||||||
 | 
								console.log(err);
 | 
				
			||||||
 | 
								return err.response.data;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function download_file(token: string, id: number) {
 | 
					export function download_file(token: string, id: number) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,12 +18,19 @@ export async function check_token(
 | 
				
			|||||||
	token: TokenInjectType
 | 
						token: TokenInjectType
 | 
				
			||||||
): Promise<string | void> {
 | 
					): Promise<string | void> {
 | 
				
			||||||
	if (!token.jwt.value) return token.logout();
 | 
						if (!token.jwt.value) return token.logout();
 | 
				
			||||||
	const payload = jwtDecode<JwtPayload>(token.jwt.value);
 | 
						try {
 | 
				
			||||||
	if (!payload) return token.logout();
 | 
							const payload = jwtDecode<JwtPayload>(token.jwt.value);
 | 
				
			||||||
	// Expires in more than 60 Minute
 | 
							if (!payload) return token.logout();
 | 
				
			||||||
	if (payload.exp && payload.exp > Math.floor(Date.now() / 1000 + 60 * 60))
 | 
							// Expires in more than 60 Minute
 | 
				
			||||||
		return token.jwt.value;
 | 
							if (
 | 
				
			||||||
	return update_token(token);
 | 
								payload.exp &&
 | 
				
			||||||
 | 
								payload.exp > Math.floor(Date.now() / 1000 + 60 * 60)
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
								return token.jwt.value;
 | 
				
			||||||
 | 
							return update_token(token);
 | 
				
			||||||
 | 
						} catch {
 | 
				
			||||||
 | 
							return token.logout();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type TokenInjectType = {
 | 
					export type TokenInjectType = {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								tokio-top.cmd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tokio-top.cmd
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					tokio-console http://127.0.0.1:9999/ --colorterm 24bit --retain-for "2s"
 | 
				
			||||||
		Reference in New Issue
	
	Block a user