diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index 9989762..0000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,29 +0,0 @@
-module.exports = {
- parser: '@typescript-eslint/parser',
- parserOptions: {
- project: 'tsconfig.json',
- tsconfigRootDir: __dirname,
- sourceType: 'module',
- },
- plugins: ['@typescript-eslint/eslint-plugin', 'no-relative-import-paths'],
- extends: [
- 'plugin:@typescript-eslint/recommended',
- 'plugin:prettier/recommended',
- ],
- root: true,
- env: {
- node: true,
- jest: true,
- },
- ignorePatterns: ['.eslintrc.js'],
- rules: {
- '@typescript-eslint/interface-name-prefix': 'off',
- '@typescript-eslint/explicit-function-return-type': 'off',
- '@typescript-eslint/explicit-module-boundary-types': 'off',
- '@typescript-eslint/no-explicit-any': 'off',
- 'no-relative-import-paths/no-relative-import-paths': [
- 'error',
- { 'allowSameFolder': true, 'rootDir': 'src' }
- ]
- },
-};
diff --git a/.gitignore b/.gitignore
index 257f64e..f2c94e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,37 +1,58 @@
-# Created by .ignore support plugin (hsz.mobi)
-### JetBrains template
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Created by https://www.toptal.com/developers/gitignore/api/clion
+# Edit at https://www.toptal.com/developers/gitignore?templates=clion
+
+### 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:
+# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
-.idea/dictionaries
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
-# Sensitive or high-churn files:
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
-.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
-# Gradle:
+# Gradle
.idea/**/gradle.xml
.idea/**/libraries
-# CMake
-cmake-build-debug/
+# 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
-# Mongo Explorer plugin:
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
.idea/**/mongoSettings.xml
-## File-based project format:
+# File-based project format
*.iws
-## Plugin-specific files:
-
# IntelliJ
out/
@@ -44,358 +65,55 @@ 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
-### VisualStudio template
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
-# User-specific files
-*.suo
-*.user
-*.userosscache
-*.sln.docstates
+# Editor-based Rest Client
+.idea/httpRequests
-# User-specific files (MonoDevelop/Xamarin Studio)
-*.userprefs
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
-# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
-x64/
-x86/
-bld/
-[Bb]in/
-[Oo]bj/
-[Ll]og/
+### CLion Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
-# Visual Studio 2015 cache/options directory
-.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
-# NUNIT
-*.VisualState.xml
-TestResult.xml
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
-# Benchmark Results
-BenchmarkDotNet.Artifacts/
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
-# .NET Core
-project.lock.json
-project.fragment.lock.json
-artifacts/
-**/Properties/launchSettings.json
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
-*_i.c
-*_p.c
-*_i.h
-*.ilk
-*.meta
-*.obj
-*.pch
-*.pdb
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.tmp_proj
-*.log
-*.vspscc
-*.vssscc
-.builds
-*.pidb
-*.svclog
-*.scc
+# Azure Toolkit for IntelliJ plugin
+# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
+.idea/**/azureSettings.xml
-# Chutzpah Test files
-_Chutzpah*
+# End of https://www.toptal.com/developers/gitignore/api/clion
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-*.VC.db
-*.VC.VC.opendb
-
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
-
-# Visual Studio Trace Files
-*.e2e
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# JustCode is a .NET coding add-in
-.JustCode
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# AxoCover is a Code Coverage Tool
-.axoCover/*
-!.axoCover/settings.json
-
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# Note: Comment the next line if you want to checkin your web deploy settings,
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# Microsoft Azure Web App publish settings. Comment the next line if you want to
-# checkin your Azure Web App publish settings, but sensitive information contained
-# in these scripts will be unencrypted
-PublishScripts/
-
-# NuGet Packages
-*.nupkg
-# The packages folder can be ignored because of Package Restore
-**/[Pp]ackages/*
-# except build/, which is used as an MSBuild target.
-!**/[Pp]ackages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/[Pp]ackages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
-*.nuget.props
-*.nuget.targets
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Windows Store app package directories and files
-AppPackages/
-BundleArtifacts/
-Package.StoreAssociation.xml
-_pkginfo.txt
-*.appx
-
-# Visual Studio cache files
-# files ending in .cache can be ignored
-*.[Cc]ache
-# but keep track of directories ending in .cache
-!*.[Cc]ache/
-
-# Others
-ClientBin/
-~$*
-*~
-*.dbmdl
-*.dbproj.schemaview
-*.jfm
-*.pfx
-*.publishsettings
-orleans.codegen.cs
-
-# Since there are multiple workflows, uncomment next line to ignore bower_components
-# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
-#bower_components/
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-
-# SQL Server files
-*.mdf
-*.ldf
-*.ndf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-node_modules/
-
-# Typescript v1 declaration files
-typings/
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-paket-files/
-
-# FAKE - F# Make
-.fake/
-
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# IDE - VSCode
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-
-# CodeRush
-.cr/
-
-# Python Tools for Visual Studio (PTVS)
-__pycache__/
-*.pyc
-
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
-
-# Tabs Studio
-*.tss
-
-# Telerik's JustMock configuration file
-*.jmconfig
-
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
-
-# OpenCover UI analysis results
-OpenCover/
-coverage/
-
-### macOS template
-# General
-.DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-# Thumbnails
-._*
-
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-.VolumeIcon.icns
-.com.apple.timemachine.donotpresent
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
-
-=======
-# Local
-.env
-dist
-
-files
-sqlite.db
\ No newline at end of file
+run/
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d82d25f..a9de079 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,89 +1,42 @@
-image: node:latest
+image: ubuntu:latest
stages:
- - setup
- - test
- build
- package
-cache: &global_cache
- paths:
- - .yarn
- - node_modules
- - frontend/.yarn
- - frontend/node_modules
- policy: pull
-
-before_script:
- - yarn install --cache-folder .yarn --frozen-lockfile
- - cd frontend
- - yarn install --cache-folder .yarn --frozen-lockfile
- - cd ..
-
-.dto_artifacts_need: &dto_artifacts_need
- job: test_build_dto
- artifacts: true
-
-test_build_dto:
- stage: setup
- cache:
- <<: *global_cache
- policy: pull-push
- before_script: []
- script:
- - cd dto
- - yarn install --frozen-lockfile
- - yarn lint
- - yarn build
- - cd ..
- - yarn install --cache-folder .yarn --frozen-lockfile
- - yarn add ./dto
- - cd frontend
- - yarn install --cache-folder .yarn --frozen-lockfile
- - yarn add ../dto
- artifacts:
- paths:
- - dto/lib/
-
-
-test_backend:
- needs:
- - *dto_artifacts_need
- stage: test
- script:
- - yarn lint
-
-test_frontend:
- needs:
- - *dto_artifacts_need
- stage: test
- script:
- - cd frontend
- - yarn lint
-
build_backend:
stage: build
- needs:
- - *dto_artifacts_need
- - job: test_backend
- artifacts: false
+ cache:
+ paths:
+ - /root/.cache/vcpkg
script:
- - echo This has to work till I rewrite the backend
- - false && echo
- - yarn webpack
+ - apt-get update
+ - apt-get install g++ gcc make cmake git curl zip unzip tar python3 pkg-config -y
+ - SRC="$PWD"
+ - TMP=$(mktemp -d)
+ - cd $TMP
+ - git clone https://github.com/Microsoft/vcpkg.git .
+ - ./bootstrap-vcpkg.sh -disableMetrics
+ - cd $SRC
+ - cmake -B build -S backend -DCMAKE_TOOLCHAIN_FILE=$TMP/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release
+ - cmake --build build
+ - cp build/backend server
artifacts:
paths:
- - dist/
+ - server
expire_in: 1h
-build_frontend:
+test_and_build_frontend:
+ image: node:latest
stage: build
- needs:
- - *dto_artifacts_need
- - job: test_frontend
- artifacts: false
+ cache:
+ paths:
+ - frontend/.yarn
+ - frontend/node_modules
script:
- cd frontend
+ - yarn install --cache-folder .yarn --frozen-lockfile
+ - yarn lint
- yarn build
artifacts:
paths:
@@ -92,23 +45,16 @@ build_frontend:
package_server:
stage: package
- cache: []
before_script: []
needs:
- job: build_backend
artifacts: true
- - job: build_frontend
+ - job: test_and_build_frontend
artifacts: true
script:
- - TMP=$(mktemp -d)
- - mv dist/* "$TMP"
- - mkdir "$TMP/frontend"
- - mv frontend/dist/* "$TMP/frontend"
- - rm -r *
- - rm -r .* || true
- - mv "$TMP/"* .
+ - mkdir static
+ - mv frontend/dist/* static/
artifacts:
paths:
- - package.json
- - server.js
- - frontend/
+ - server
+ - static/
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..98cd9e7
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+backend
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..61eb0a1
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:$PROJECT_DIR$/old_backend/sqlite.db
+ $ProjectFileDir$
+
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:$PROJECT_DIR$/run/sqlite.db
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/.idea/file_server.iml b/.idea/file_server.iml
new file mode 100644
index 0000000..6d70257
--- /dev/null
+++ b/.idea/file_server.iml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..f0452e2
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..75aa0b0
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..9661ac7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index 9145d2d..0000000
--- a/.prettierrc
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "tabWidth": 4,
- "useTabs": true,
- "singleQuote": true,
- "trailingComma": "none",
- "endOfLine": "lf"
-}
diff --git a/README.md b/README.md
deleted file mode 100644
index c8a4e92..0000000
--- a/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Mutzi's fileserver
-
-## Description
-The most crackhead fileserver you will find on the market
-
-## Installation
-```bash
-npm install
-cd frontend && npm install
-```
-
-## Running the app
-```bash
-npm run start:dev
-```
-Run in parallel for building the frontend:
-````bash
-cd frontend && npm run serve
-````
\ No newline at end of file
diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt
new file mode 100644
index 0000000..ad20563
--- /dev/null
+++ b/backend/CMakeLists.txt
@@ -0,0 +1,70 @@
+cmake_minimum_required(VERSION 3.20)
+project(backend)
+
+set(CMAKE_CXX_STANDARD 23)
+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/db/model/Inode.cc
+ src/db/model/Inode.h
+ src/db/model/Tokens.cc
+ src/db/model/Tokens.h
+ src/db/model/User.cc
+ src/db/model/User.h
+
+ src/controllers/controllers.h
+ src/controllers/admin.cpp
+ src/controllers/fs.cpp
+ src/controllers/user.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
+)
+
+
+
+find_package(Drogon CONFIG REQUIRED)
+find_package(CURL CONFIG REQUIRED)
+find_package(PNG 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_path(PNGPP_INCLUDE_DIRS "png++/color.hpp")
+find_library(BOTAN_LIBRARY botan-2)
+find_library(QR_LIBRARY nayuki-qr-code-generator)
+
+target_include_directories(backend PRIVATE
+ src
+ ${JWT_CPP_INCLUDE_DIRS}
+ ${BOTAN_INCLUDE_DIRS}
+ ${QR_INCLUDE_DIRS}
+ ${PNGPP_INCLUDE_DIRS}
+)
+
+target_link_libraries(backend
+ Drogon::Drogon
+ CURL::libcurl
+ PNG::PNG
+ ${BOTAN_LIBRARY}
+ ${QR_LIBRARY}
+)
+
+install(TARGETS backend)
+
+target_compile_options(backend PRIVATE
+ $<$:-g -Wall -Wno-unknown-pragmas>
+ $<$:-O3>
+)
\ No newline at end of file
diff --git a/backend/src/controllers/admin.cpp b/backend/src/controllers/admin.cpp
new file mode 100644
index 0000000..ae777a1
--- /dev/null
+++ b/backend/src/controllers/admin.cpp
@@ -0,0 +1,88 @@
+#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 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(json, "user").value();
+ db::UserRole role = (db::UserRole)dto::json_get(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(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();
+ try {
+ uint64_t user_id = dto::json_get(json, "user").value();
+
+ db::MapperUser user_mapper(drogon::app().getDbClient());
+ auto user = user_mapper.findByPrimaryKey(user_id);
+ auth::revoke_all(user);
+ fs::delete_node(fs::get_node(user.getValueOfRootId()).value(), 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(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"));
+ }
+ }
+}
+#pragma clang diagnostic pop
\ No newline at end of file
diff --git a/backend/src/controllers/auth/auth_2fa.cpp b/backend/src/controllers/auth/auth_2fa.cpp
new file mode 100644
index 0000000..a0a5c52
--- /dev/null
+++ b/backend/src/controllers/auth/auth_2fa.cpp
@@ -0,0 +1,103 @@
+#pragma clang diagnostic push
+#pragma ide diagnostic ignored "readability-make-member-function-const"
+#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
+
+#include
+#include
+#include
+#include
+
+#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) {
+ const 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();
+ png::image image(mod_count*qrcode_pixel_size, mod_count*qrcode_pixel_size);
+ for (int x = 0; x < mod_count; x++) for (int y = 0; y < mod_count; y++) {
+ const bool mod = code.getModule(x, y);
+ const int x_img_start = x * qrcode_pixel_size, y_img_start = y * qrcode_pixel_size;
+ for (int x_img = x_img_start; x_img < x_img_start + qrcode_pixel_size; x_img++) for (int y_img = y_img_start; y_img < y_img_start + qrcode_pixel_size; y_img++)
+ image[x_img][y_img] = mod ? 0 : 0xff;
+ }
+ std::stringstream image_ss;
+ image.write_stream(image_ss);
+
+ std::string image_str = image_ss.str();
+ std::vector secret(image_str.data(), image_str.data()+image_str.size());
+
+ return "data:image/png;base64," + Botan::base64_encode(secret);
+}
+
+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(json, "mail").value();
+
+ auto secret_uchar = rng->random_vec(32);
+ std::vector 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(json, "mail").value();
+ uint32_t code = std::stoi(dto::json_get(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
\ No newline at end of file
diff --git a/backend/src/controllers/auth/auth_basic.cpp b/backend/src/controllers/auth/auth_basic.cpp
new file mode 100644
index 0000000..1b0cfbb
--- /dev/null
+++ b/backend/src/controllers/auth/auth_basic.cpp
@@ -0,0 +1,135 @@
+#pragma clang diagnostic push
+#pragma ide diagnostic ignored "readability-make-member-function-const"
+#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
+
+#include
+#include
+#include
+#include
+
+#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(json, "username").value();
+ std::string password = dto::json_get(json, "password").value();
+ std::optional otp = dto::json_get(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(json, "username").value();
+ std::string password = dto::json_get(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& e) {
+ 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(json, "oldPassword").value();
+ std::string new_pw = dto::json_get(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
\ No newline at end of file
diff --git a/backend/src/controllers/auth/auth_common.cpp b/backend/src/controllers/auth/auth_common.cpp
new file mode 100644
index 0000000..5ce8d73
--- /dev/null
+++ b/backend/src/controllers/auth/auth_common.cpp
@@ -0,0 +1,110 @@
+#pragma clang diagnostic push
+#pragma ide diagnostic ignored "readability-make-member-function-const"
+#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
+
+#include
+#include
+
+#include
+#include
+#include
+
+#if defined(BOTAN_HAS_SYSTEM_RNG)
+#include
+#else
+#include
+#endif
+
+#include
+#include
+#include
+
+#include "controllers/controllers.h"
+#include "db/db.h"
+#include "dto/dto.h"
+
+size_t payload_source(char* ptr, size_t size, size_t nmemb, void* userp) {
+ auto* ss = (std::stringstream*)userp;
+ return ss->readsome(ptr, (long)(size*nmemb));
+}
+
+namespace api {
+#if defined(BOTAN_HAS_SYSTEM_RNG)
+ std::unique_ptr auth::rng = std::make_unique();
+#else
+ std::unique_ptr auth::rng = std::make_unique();
+#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&) 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::stringstream ss;
+ std::time_t t = std::time(nullptr);
+ const auto& totp_secret = (const std::vector&) user.getValueOfTfaSecret();
+ char totp[16];
+ std::snprintf(totp, 16, "%06d", Botan::TOTP(Botan::OctetString(totp_secret)).generate_totp(t));
+ ss.imbue(std::locale("en_US.utf8"));
+ ss << "Date: " << std::put_time(std::localtime(&t), "%a, %d %b %Y %T %z") << "\r\n";
+ ss << "To: " << user.getValueOfName() << "\r\n";
+ ss << "From: fileserver@mattv.de\r\n";
+ ss << "Message-ID: " << Botan::UUID(*rng).to_string() << "@mattv.de>\r\n";
+ ss << "Subject: Fileserver - EMail 2fa code\r\n";
+ ss << "Your code is: " << totp << "\r\n";
+ ss << "It is valid for 5 Minutes\r\n";
+
+ CURL* curl = curl_easy_init();
+ curl_easy_setopt(curl, CURLOPT_USERNAME, "no-reply@mattv.de");
+ curl_easy_setopt(curl, CURLOPT_PASSWORD, "noreplyLONGPASS123");
+ curl_easy_setopt(curl, CURLOPT_URL, "smtp://mail.mattv.de:587");
+ curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
+ auto recp = curl_slist_append(nullptr, user.getValueOfName().c_str());
+ curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recp);
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, &payload_source);
+ curl_easy_setopt(curl, CURLOPT_READDATA, &ss);
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
+ curl_easy_perform(curl);
+ curl_slist_free_all(recp);
+ curl_easy_cleanup(curl);
+ }
+
+ 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::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()
+ .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{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(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()));
+ }
+}
+
+#pragma clang diagnostic pop
diff --git a/backend/src/controllers/auth/auth_gitlab.cpp b/backend/src/controllers/auth/auth_gitlab.cpp
new file mode 100644
index 0000000..f5074eb
--- /dev/null
+++ b/backend/src/controllers/auth/auth_gitlab.cpp
@@ -0,0 +1,118 @@
+#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
+
+#include "controllers/controllers.h"
+#include "dto/dto.h"
+
+const std::string GITLAB_ID = "98bcbad78cb1f880d1d1de62291d70a791251a7bea077bfe7df111ef3c115760";
+const std::string GITLAB_SECRET = "7ee01d2b204aff3a05f9d028f004d169b6d381ec873e195f314b3935fa150959";
+const std::string GITLAB_URL = "https://gitlab.mattv.de";
+const std::string GITLAB_API_URL = "https://ssh.gitlab.mattv.de";
+
+std::string get_redirect_uri(req_type req) {
+ auto host_header = req->headers().find("host");
+ std::stringstream ss;
+ ss << (req->isOnSecureConnection() ? "https" : "http")
+ << "://"
+ << (host_header != req->headers().end() ? host_header->second : "127.0.0.1:1234")
+ << "/api/auth/gitlab_callback";
+ return drogon::utils::urlEncode(ss.str());
+}
+
+const drogon::HttpClientPtr& get_gitlab_client() {
+ static drogon::HttpClientPtr client = drogon::HttpClient::newHttpClient(GITLAB_API_URL, drogon::app().getLoop(), false, false);
+ return client;
+}
+
+
+namespace api {
+ std::optional auth::get_gitlab_tokens(req_type req, const std::string& code_or_token, bool token) {
+ std::stringstream ss;
+ ss << "/oauth/token"
+ << "?redirect_uri=" << get_redirect_uri(req)
+ << "&client_id=" << GITLAB_ID
+ << "&client_secret=" << GITLAB_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(
+ json["access_token"].as(),
+ json["refresh_token"].as()
+ );
+ }
+
+ std::optional 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(
+ json["username"].as(),
+ json.get("is_admin", false).as()
+ );
+ }
+
+ void auth::gitlab(req_type req, cbk_type cbk) {
+ std::stringstream ss;
+ ss << GITLAB_URL << "/oauth/authorize"
+ << "?redirect_uri=" << get_redirect_uri(req)
+ << "&client_id=" << GITLAB_ID
+ << "&scope=read_user&response_type=code";
+ cbk(drogon::HttpResponse::newRedirectionResponse(ss.str()));
+ }
+
+ void auth::gitlab_callback(req_type req, cbk_type cbk, std::string code) {
+ auto tokens = get_gitlab_tokens(req, 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);
+
+ const std::string& token = get_token(db_user);
+ cbk(drogon::HttpResponse::newRedirectionResponse("/set_token?token="+token));
+ }
+}
+
+#pragma clang diagnostic pop
\ No newline at end of file
diff --git a/backend/src/controllers/controllers.h b/backend/src/controllers/controllers.h
new file mode 100644
index 0000000..c9e1ba7
--- /dev/null
+++ b/backend/src/controllers/controllers.h
@@ -0,0 +1,119 @@
+#ifndef BACKEND_CONTROLLERS_H
+#define BACKEND_CONTROLLERS_H
+#include
+#include
+#include
+#include
+#include
+
+#include "db/db.h"
+
+using req_type = const drogon::HttpRequestPtr&;
+using cbk_type = std::function&&;
+
+namespace api {
+class admin : public drogon::HttpController {
+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_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);
+};
+
+class auth : public drogon::HttpController {
+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 rng;
+
+ static std::optional get_gitlab_tokens(req_type, const std::string&, bool token);
+ static std::optional 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&);
+
+ 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 {
+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, "/createFolder", drogon::Post, "Login");
+ METHOD_ADD(fs::create_node_req, "/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::download, "/download", drogon::Post, "Login");
+ METHOD_LIST_END
+
+ static std::optional get_node(uint64_t node);
+ static std::optional get_node_and_validate(const db::User& user, uint64_t node);
+ static std::vector get_children(const db::INode& parent);
+ static std::variant create_node(std::string name, const db::User& owner, bool file, const std::optional &parent, bool force = false);
+ static void delete_node(db::INode node, bool allow_root = false);
+
+
+ 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 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 download(req_type, cbk_type);
+};
+
+class user : public drogon::HttpController {
+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
diff --git a/backend/src/controllers/fs.cpp b/backend/src/controllers/fs.cpp
new file mode 100644
index 0000000..d6f5fb2
--- /dev/null
+++ b/backend/src/controllers/fs.cpp
@@ -0,0 +1,211 @@
+#pragma clang diagnostic push
+#pragma ide diagnostic ignored "performance-unnecessary-value-param"
+#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
+
+#include
+#include "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<>:\"/\\|";
+
+std::string generate_path(db::INode node) {
+ db::MapperInode inode_mapper(drogon::app().getDbClient());
+ std::stack path;
+ path.push(node);
+ while (node.getParentId() != nullptr) {
+ node = inode_mapper.findByPrimaryKey(node.getValueOfParentId());
+ path.push(node);
+ }
+ std::stringstream ss;
+ while (!path.empty()) {
+ const db::INode& seg = path.top();
+ ss << seg.getValueOfName();
+ if (seg.getValueOfIsFile() == 0) ss << '/';
+ path.pop();
+ }
+ return ss.str();
+}
+
+namespace api {
+ std::optional 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 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 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 fs::create_node(std::string name, const db::User& owner, bool file, const std::optional &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 {"Invalid name"};
+
+ db::INode node;
+ node.setIsFile(file ? 1 : 0);
+ node.setName(name);
+ node.setOwnerId(owner.getValueOfId());
+ if (parent.has_value()) {
+ auto parent_node = get_node_and_validate(owner, *parent);
+ if (!parent_node.has_value())
+ return {"Invalid parent"};
+ if (parent_node->getValueOfIsFile() != 0)
+ return {"Can't use file as parent"};
+ auto children = get_children(*parent_node);
+ for (const auto& child : children)
+ if (child.getValueOfName() == name)
+ return {"File/Folder already exists"};
+ node.setParentId(*parent);
+ }
+ db::MapperInode inode_mapper(drogon::app().getDbClient());
+ inode_mapper.insert(node);
+ return {node};
+ }
+
+ void fs::delete_node(db::INode node, bool allow_root) {
+ if (node.getValueOfParentId() == 0 && (!allow_root)) return;
+ if (node.getValueOfIsFile() == 0) {
+ auto children = get_children(node);
+ for (const auto& child : children) delete_node(child, false);
+ } else {
+ std::filesystem::path p("./files");
+ p /= std::to_string(node.getValueOfId());
+ std::filesystem::remove(p);
+ }
+ db::MapperInode inode_mapper(drogon::app().getDbClient());
+ inode_mapper.deleteOne(node);
+ }
+
+ 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);
+ auto inode = get_node_and_validate(user, node);
+ if (!inode.has_value())
+ cbk(dto::Responses::get_badreq_res("Unknown node"));
+ else if (inode->getValueOfIsFile() == 0) {
+ std::vector children;
+ for (const db::INode& child : get_children(*inode)) children.push_back(child.getValueOfId());
+ cbk(dto::Responses::get_node_folder_res(
+ inode->getValueOfId(),
+ inode->getValueOfName(),
+ inode->getParentId(),
+ children
+ ));
+ } else
+ cbk(dto::Responses::get_node_file_res(
+ inode->getValueOfId(),
+ inode->getValueOfName(),
+ inode->getParentId(),
+ inode->getValueOfSize()
+ ));
+ }
+
+ void fs::path(req_type req, cbk_type cbk, uint64_t node) {
+ db::User user = dto::get_user(req);
+ auto inode = get_node_and_validate(user, node);
+ if (!inode.has_value())
+ cbk(dto::Responses::get_badreq_res("Unknown node"));
+ else
+ cbk(dto::Responses::get_path_res( generate_path(*inode)));
+ }
+
+ template
+ 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(json, "parent").value();
+ std::string name = dto::json_get(json, "name").value();
+
+ auto new_node = create_node(name, user, file, std::make_optional(parent));
+ if (std::holds_alternative(new_node))
+ cbk(dto::Responses::get_badreq_res(std::get(new_node)));
+ else
+ cbk(dto::Responses::get_new_node_res(std::get(new_node).getValueOfId()));
+ } 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);
+ 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 {
+ delete_node(*inode);
+ cbk(dto::Responses::get_success_res());
+ }
+ }
+
+ void fs::upload(req_type req, cbk_type cbk, uint64_t node) {
+ db::User user = dto::get_user(req);
+
+ 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());
+
+ inode->setSize(file.fileLength());
+ db::MapperInode inode_mapper(drogon::app().getDbClient());
+ inode_mapper.update(*inode);
+ cbk(dto::Responses::get_success_res());
+ }
+
+ void fs::download(req_type req, cbk_type cbk) {
+ db::User user = dto::get_user(req);
+
+ auto node_id = req->getOptionalParameter("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;
+ }
+
+ std::filesystem::path p("./files");
+ p /= std::to_string(inode->getValueOfId());
+
+ cbk(drogon::HttpResponse::newFileResponse(
+ p.string(),
+ inode->getValueOfName()
+ ));
+ }
+}
+#pragma clang diagnostic pop
\ No newline at end of file
diff --git a/backend/src/controllers/user.cpp b/backend/src/controllers/user.cpp
new file mode 100644
index 0000000..aab0fb7
--- /dev/null
+++ b/backend/src/controllers/user.cpp
@@ -0,0 +1,29 @@
+#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());
+
+ db::User user = dto::get_user(req);
+ auth::revoke_all(user);
+ fs::delete_node((fs::get_node(user.getValueOfRootId())).value(), true);
+ user_mapper.deleteOne(user);
+
+ cbk(dto::Responses::get_success_res());
+ }
+}
+#pragma clang diagnostic pop
\ No newline at end of file
diff --git a/backend/src/db/db.cpp b/backend/src/db/db.cpp
new file mode 100644
index 0000000..88cf967
--- /dev/null
+++ b/backend/src/db/db.cpp
@@ -0,0 +1,11 @@
+#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();
+ }
+}
diff --git a/backend/src/db/db.h b/backend/src/db/db.h
new file mode 100644
index 0000000..f72e684
--- /dev/null
+++ b/backend/src/db/db.h
@@ -0,0 +1,43 @@
+#ifndef BACKEND_DB_H
+#define BACKEND_DB_H
+
+#include
+
+#include
+#include
+
+#include "model/Inode.h"
+#include "model/Tokens.h"
+#include "model/User.h"
+
+const std::string jwt_secret = "CUM";
+
+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;
+ using MapperToken = drogon::orm::Mapper;
+ using MapperUser = drogon::orm::Mapper;
+
+ 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
diff --git a/backend/src/db/model/Inode.cc b/backend/src/db/model/Inode.cc
new file mode 100644
index 0000000..01d839e
--- /dev/null
+++ b/backend/src/db/model/Inode.cc
@@ -0,0 +1,1095 @@
+/**
+ *
+ * Inode.cc
+ * DO NOT EDIT. This file is generated by drogon_ctl
+ *
+ */
+
+#include "Inode.h"
+#include
+#include
+
+using namespace drogon;
+using namespace drogon::orm;
+using namespace drogon_model::sqlite3;
+
+const std::string Inode::Cols::_id = "id";
+const std::string Inode::Cols::_is_file = "is_file";
+const std::string Inode::Cols::_name = "name";
+const std::string Inode::Cols::_parent_id = "parent_id";
+const std::string Inode::Cols::_owner_id = "owner_id";
+const std::string Inode::Cols::_size = "size";
+const std::string Inode::primaryKeyName = "id";
+const bool Inode::hasPrimaryKey = true;
+const std::string Inode::tableName = "inode";
+
+const std::vector Inode::metaData_={
+{"id","uint64_t","integer",8,1,1,1},
+{"is_file","uint64_t","integer",8,0,0,1},
+{"name","std::string","text",0,0,0,0},
+{"parent_id","uint64_t","integer",8,0,0,0},
+{"owner_id","uint64_t","integer",8,0,0,1},
+{"size","uint64_t","integer",8,0,0,0}
+};
+const std::string &Inode::getColumnName(size_t index) noexcept(false)
+{
+ assert(index < metaData_.size());
+ return metaData_[index].colName_;
+}
+Inode::Inode(const Row &r, const ssize_t indexOffset) noexcept
+{
+ if(indexOffset < 0)
+ {
+ if(!r["id"].isNull())
+ {
+ id_=std::make_shared(r["id"].as());
+ }
+ if(!r["is_file"].isNull())
+ {
+ isFile_=std::make_shared(r["is_file"].as());
+ }
+ if(!r["name"].isNull())
+ {
+ name_=std::make_shared(r["name"].as());
+ }
+ if(!r["parent_id"].isNull())
+ {
+ parentId_=std::make_shared(r["parent_id"].as());
+ }
+ if(!r["owner_id"].isNull())
+ {
+ ownerId_=std::make_shared(r["owner_id"].as());
+ }
+ if(!r["size"].isNull())
+ {
+ size_=std::make_shared(r["size"].as());
+ }
+ }
+ else
+ {
+ size_t offset = (size_t)indexOffset;
+ if(offset + 6 > 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(r[index].as());
+ }
+ index = offset + 1;
+ if(!r[index].isNull())
+ {
+ isFile_=std::make_shared(r[index].as());
+ }
+ index = offset + 2;
+ if(!r[index].isNull())
+ {
+ name_=std::make_shared(r[index].as());
+ }
+ index = offset + 3;
+ if(!r[index].isNull())
+ {
+ parentId_=std::make_shared(r[index].as());
+ }
+ index = offset + 4;
+ if(!r[index].isNull())
+ {
+ ownerId_=std::make_shared(r[index].as());
+ }
+ index = offset + 5;
+ if(!r[index].isNull())
+ {
+ size_=std::make_shared(r[index].as());
+ }
+ }
+
+}
+
+Inode::Inode(const Json::Value &pJson, const std::vector &pMasqueradingVector) noexcept(false)
+{
+ if(pMasqueradingVector.size() != 6)
+ {
+ 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)pJson[pMasqueradingVector[0]].asUInt64());
+ }
+ }
+ if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
+ {
+ dirtyFlag_[1] = true;
+ if(!pJson[pMasqueradingVector[1]].isNull())
+ {
+ isFile_=std::make_shared((uint64_t)pJson[pMasqueradingVector[1]].asUInt64());
+ }
+ }
+ if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2]))
+ {
+ dirtyFlag_[2] = true;
+ if(!pJson[pMasqueradingVector[2]].isNull())
+ {
+ name_=std::make_shared(pJson[pMasqueradingVector[2]].asString());
+ }
+ }
+ if(!pMasqueradingVector[3].empty() && pJson.isMember(pMasqueradingVector[3]))
+ {
+ dirtyFlag_[3] = true;
+ if(!pJson[pMasqueradingVector[3]].isNull())
+ {
+ parentId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[3]].asUInt64());
+ }
+ }
+ if(!pMasqueradingVector[4].empty() && pJson.isMember(pMasqueradingVector[4]))
+ {
+ dirtyFlag_[4] = true;
+ if(!pJson[pMasqueradingVector[4]].isNull())
+ {
+ ownerId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[4]].asUInt64());
+ }
+ }
+ if(!pMasqueradingVector[5].empty() && pJson.isMember(pMasqueradingVector[5]))
+ {
+ dirtyFlag_[5] = true;
+ if(!pJson[pMasqueradingVector[5]].isNull())
+ {
+ size_=std::make_shared((uint64_t)pJson[pMasqueradingVector[5]].asUInt64());
+ }
+ }
+}
+
+Inode::Inode(const Json::Value &pJson) noexcept(false)
+{
+ if(pJson.isMember("id"))
+ {
+ dirtyFlag_[0]=true;
+ if(!pJson["id"].isNull())
+ {
+ id_=std::make_shared((uint64_t)pJson["id"].asUInt64());
+ }
+ }
+ if(pJson.isMember("is_file"))
+ {
+ dirtyFlag_[1]=true;
+ if(!pJson["is_file"].isNull())
+ {
+ isFile_=std::make_shared((uint64_t)pJson["is_file"].asUInt64());
+ }
+ }
+ if(pJson.isMember("name"))
+ {
+ dirtyFlag_[2]=true;
+ if(!pJson["name"].isNull())
+ {
+ name_=std::make_shared(pJson["name"].asString());
+ }
+ }
+ if(pJson.isMember("parent_id"))
+ {
+ dirtyFlag_[3]=true;
+ if(!pJson["parent_id"].isNull())
+ {
+ parentId_=std::make_shared((uint64_t)pJson["parent_id"].asUInt64());
+ }
+ }
+ if(pJson.isMember("owner_id"))
+ {
+ dirtyFlag_[4]=true;
+ if(!pJson["owner_id"].isNull())
+ {
+ ownerId_=std::make_shared((uint64_t)pJson["owner_id"].asUInt64());
+ }
+ }
+ if(pJson.isMember("size"))
+ {
+ dirtyFlag_[5]=true;
+ if(!pJson["size"].isNull())
+ {
+ size_=std::make_shared((uint64_t)pJson["size"].asUInt64());
+ }
+ }
+}
+
+void Inode::updateByMasqueradedJson(const Json::Value &pJson,
+ const std::vector &pMasqueradingVector) noexcept(false)
+{
+ if(pMasqueradingVector.size() != 6)
+ {
+ 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)pJson[pMasqueradingVector[0]].asUInt64());
+ }
+ }
+ if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
+ {
+ dirtyFlag_[1] = true;
+ if(!pJson[pMasqueradingVector[1]].isNull())
+ {
+ isFile_=std::make_shared((uint64_t)pJson[pMasqueradingVector[1]].asUInt64());
+ }
+ }
+ if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2]))
+ {
+ dirtyFlag_[2] = true;
+ if(!pJson[pMasqueradingVector[2]].isNull())
+ {
+ name_=std::make_shared(pJson[pMasqueradingVector[2]].asString());
+ }
+ }
+ if(!pMasqueradingVector[3].empty() && pJson.isMember(pMasqueradingVector[3]))
+ {
+ dirtyFlag_[3] = true;
+ if(!pJson[pMasqueradingVector[3]].isNull())
+ {
+ parentId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[3]].asUInt64());
+ }
+ }
+ if(!pMasqueradingVector[4].empty() && pJson.isMember(pMasqueradingVector[4]))
+ {
+ dirtyFlag_[4] = true;
+ if(!pJson[pMasqueradingVector[4]].isNull())
+ {
+ ownerId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[4]].asUInt64());
+ }
+ }
+ if(!pMasqueradingVector[5].empty() && pJson.isMember(pMasqueradingVector[5]))
+ {
+ dirtyFlag_[5] = true;
+ if(!pJson[pMasqueradingVector[5]].isNull())
+ {
+ size_=std::make_shared((uint64_t)pJson[pMasqueradingVector[5]].asUInt64());
+ }
+ }
+}
+
+void Inode::updateByJson(const Json::Value &pJson) noexcept(false)
+{
+ if(pJson.isMember("id"))
+ {
+ if(!pJson["id"].isNull())
+ {
+ id_=std::make_shared((uint64_t)pJson["id"].asUInt64());
+ }
+ }
+ if(pJson.isMember("is_file"))
+ {
+ dirtyFlag_[1] = true;
+ if(!pJson["is_file"].isNull())
+ {
+ isFile_=std::make_shared((uint64_t)pJson["is_file"].asUInt64());
+ }
+ }
+ if(pJson.isMember("name"))
+ {
+ dirtyFlag_[2] = true;
+ if(!pJson["name"].isNull())
+ {
+ name_=std::make_shared(pJson["name"].asString());
+ }
+ }
+ if(pJson.isMember("parent_id"))
+ {
+ dirtyFlag_[3] = true;
+ if(!pJson["parent_id"].isNull())
+ {
+ parentId_=std::make_shared((uint64_t)pJson["parent_id"].asUInt64());
+ }
+ }
+ if(pJson.isMember("owner_id"))
+ {
+ dirtyFlag_[4] = true;
+ if(!pJson["owner_id"].isNull())
+ {
+ ownerId_=std::make_shared((uint64_t)pJson["owner_id"].asUInt64());
+ }
+ }
+ if(pJson.isMember("size"))
+ {
+ dirtyFlag_[5] = true;
+ if(!pJson["size"].isNull())
+ {
+ size_=std::make_shared((uint64_t)pJson["size"].asUInt64());
+ }
+ }
+}
+
+const uint64_t &Inode::getValueOfId() const noexcept
+{
+ const static uint64_t defaultValue = uint64_t();
+ if(id_)
+ return *id_;
+ return defaultValue;
+}
+const std::shared_ptr &Inode::getId() const noexcept
+{
+ return id_;
+}
+void Inode::setId(const uint64_t &pId) noexcept
+{
+ id_ = std::make_shared(pId);
+ dirtyFlag_[0] = true;
+}
+const typename Inode::PrimaryKeyType & Inode::getPrimaryKey() const
+{
+ assert(id_);
+ return *id_;
+}
+
+const uint64_t &Inode::getValueOfIsFile() const noexcept
+{
+ const static uint64_t defaultValue = uint64_t();
+ if(isFile_)
+ return *isFile_;
+ return defaultValue;
+}
+const std::shared_ptr &Inode::getIsFile() const noexcept
+{
+ return isFile_;
+}
+void Inode::setIsFile(const uint64_t &pIsFile) noexcept
+{
+ isFile_ = std::make_shared(pIsFile);
+ dirtyFlag_[1] = true;
+}
+
+const std::string &Inode::getValueOfName() const noexcept
+{
+ const static std::string defaultValue = std::string();
+ if(name_)
+ return *name_;
+ return defaultValue;
+}
+const std::shared_ptr &Inode::getName() const noexcept
+{
+ return name_;
+}
+void Inode::setName(const std::string &pName) noexcept
+{
+ name_ = std::make_shared(pName);
+ dirtyFlag_[2] = true;
+}
+void Inode::setName(std::string &&pName) noexcept
+{
+ name_ = std::make_shared(std::move(pName));
+ dirtyFlag_[2] = true;
+}
+void Inode::setNameToNull() noexcept
+{
+ name_.reset();
+ dirtyFlag_[2] = true;
+}
+
+const uint64_t &Inode::getValueOfParentId() const noexcept
+{
+ const static uint64_t defaultValue = uint64_t();
+ if(parentId_)
+ return *parentId_;
+ return defaultValue;
+}
+const std::shared_ptr &Inode::getParentId() const noexcept
+{
+ return parentId_;
+}
+void Inode::setParentId(const uint64_t &pParentId) noexcept
+{
+ parentId_ = std::make_shared(pParentId);
+ dirtyFlag_[3] = true;
+}
+void Inode::setParentIdToNull() noexcept
+{
+ parentId_.reset();
+ dirtyFlag_[3] = true;
+}
+
+const uint64_t &Inode::getValueOfOwnerId() const noexcept
+{
+ const static uint64_t defaultValue = uint64_t();
+ if(ownerId_)
+ return *ownerId_;
+ return defaultValue;
+}
+const std::shared_ptr &Inode::getOwnerId() const noexcept
+{
+ return ownerId_;
+}
+void Inode::setOwnerId(const uint64_t &pOwnerId) noexcept
+{
+ ownerId_ = std::make_shared(pOwnerId);
+ dirtyFlag_[4] = true;
+}
+
+const uint64_t &Inode::getValueOfSize() const noexcept
+{
+ const static uint64_t defaultValue = uint64_t();
+ if(size_)
+ return *size_;
+ return defaultValue;
+}
+const std::shared_ptr &Inode::getSize() const noexcept
+{
+ return size_;
+}
+void Inode::setSize(const uint64_t &pSize) noexcept
+{
+ size_ = std::make_shared(pSize);
+ dirtyFlag_[5] = true;
+}
+void Inode::setSizeToNull() noexcept
+{
+ size_.reset();
+ dirtyFlag_[5] = true;
+}
+
+void Inode::updateId(const uint64_t id)
+{
+ id_ = std::make_shared(id);
+}
+
+const std::vector &Inode::insertColumns() noexcept
+{
+ static const std::vector inCols={
+ "is_file",
+ "name",
+ "parent_id",
+ "owner_id",
+ "size"
+ };
+ return inCols;
+}
+
+void Inode::outputArgs(drogon::orm::internal::SqlBinder &binder) const
+{
+ if(dirtyFlag_[1])
+ {
+ if(getIsFile())
+ {
+ binder << getValueOfIsFile();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
+ if(dirtyFlag_[2])
+ {
+ if(getName())
+ {
+ binder << getValueOfName();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
+ if(dirtyFlag_[3])
+ {
+ if(getParentId())
+ {
+ binder << getValueOfParentId();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
+ if(dirtyFlag_[4])
+ {
+ if(getOwnerId())
+ {
+ binder << getValueOfOwnerId();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
+ if(dirtyFlag_[5])
+ {
+ if(getSize())
+ {
+ binder << getValueOfSize();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
+}
+
+const std::vector Inode::updateColumns() const
+{
+ std::vector ret;
+ if(dirtyFlag_[1])
+ {
+ ret.push_back(getColumnName(1));
+ }
+ if(dirtyFlag_[2])
+ {
+ ret.push_back(getColumnName(2));
+ }
+ if(dirtyFlag_[3])
+ {
+ ret.push_back(getColumnName(3));
+ }
+ if(dirtyFlag_[4])
+ {
+ ret.push_back(getColumnName(4));
+ }
+ if(dirtyFlag_[5])
+ {
+ ret.push_back(getColumnName(5));
+ }
+ return ret;
+}
+
+void Inode::updateArgs(drogon::orm::internal::SqlBinder &binder) const
+{
+ if(dirtyFlag_[1])
+ {
+ if(getIsFile())
+ {
+ binder << getValueOfIsFile();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
+ if(dirtyFlag_[2])
+ {
+ if(getName())
+ {
+ binder << getValueOfName();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
+ if(dirtyFlag_[3])
+ {
+ if(getParentId())
+ {
+ binder << getValueOfParentId();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
+ if(dirtyFlag_[4])
+ {
+ if(getOwnerId())
+ {
+ binder << getValueOfOwnerId();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
+ if(dirtyFlag_[5])
+ {
+ if(getSize())
+ {
+ binder << getValueOfSize();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
+}
+Json::Value Inode::toJson() const
+{
+ Json::Value ret;
+ if(getId())
+ {
+ ret["id"]=(Json::UInt64)getValueOfId();
+ }
+ else
+ {
+ ret["id"]=Json::Value();
+ }
+ if(getIsFile())
+ {
+ ret["is_file"]=(Json::UInt64)getValueOfIsFile();
+ }
+ else
+ {
+ ret["is_file"]=Json::Value();
+ }
+ if(getName())
+ {
+ ret["name"]=getValueOfName();
+ }
+ else
+ {
+ ret["name"]=Json::Value();
+ }
+ if(getParentId())
+ {
+ ret["parent_id"]=(Json::UInt64)getValueOfParentId();
+ }
+ else
+ {
+ ret["parent_id"]=Json::Value();
+ }
+ if(getOwnerId())
+ {
+ ret["owner_id"]=(Json::UInt64)getValueOfOwnerId();
+ }
+ else
+ {
+ ret["owner_id"]=Json::Value();
+ }
+ if(getSize())
+ {
+ ret["size"]=(Json::UInt64)getValueOfSize();
+ }
+ else
+ {
+ ret["size"]=Json::Value();
+ }
+ return ret;
+}
+
+Json::Value Inode::toMasqueradedJson(
+ const std::vector &pMasqueradingVector) const
+{
+ Json::Value ret;
+ if(pMasqueradingVector.size() == 6)
+ {
+ if(!pMasqueradingVector[0].empty())
+ {
+ if(getId())
+ {
+ ret[pMasqueradingVector[0]]=(Json::UInt64)getValueOfId();
+ }
+ else
+ {
+ ret[pMasqueradingVector[0]]=Json::Value();
+ }
+ }
+ if(!pMasqueradingVector[1].empty())
+ {
+ if(getIsFile())
+ {
+ ret[pMasqueradingVector[1]]=(Json::UInt64)getValueOfIsFile();
+ }
+ else
+ {
+ ret[pMasqueradingVector[1]]=Json::Value();
+ }
+ }
+ if(!pMasqueradingVector[2].empty())
+ {
+ if(getName())
+ {
+ ret[pMasqueradingVector[2]]=getValueOfName();
+ }
+ else
+ {
+ ret[pMasqueradingVector[2]]=Json::Value();
+ }
+ }
+ if(!pMasqueradingVector[3].empty())
+ {
+ if(getParentId())
+ {
+ ret[pMasqueradingVector[3]]=(Json::UInt64)getValueOfParentId();
+ }
+ else
+ {
+ ret[pMasqueradingVector[3]]=Json::Value();
+ }
+ }
+ if(!pMasqueradingVector[4].empty())
+ {
+ if(getOwnerId())
+ {
+ ret[pMasqueradingVector[4]]=(Json::UInt64)getValueOfOwnerId();
+ }
+ else
+ {
+ ret[pMasqueradingVector[4]]=Json::Value();
+ }
+ }
+ if(!pMasqueradingVector[5].empty())
+ {
+ if(getSize())
+ {
+ ret[pMasqueradingVector[5]]=(Json::UInt64)getValueOfSize();
+ }
+ else
+ {
+ ret[pMasqueradingVector[5]]=Json::Value();
+ }
+ }
+ return ret;
+ }
+ LOG_ERROR << "Masquerade failed";
+ if(getId())
+ {
+ ret["id"]=(Json::UInt64)getValueOfId();
+ }
+ else
+ {
+ ret["id"]=Json::Value();
+ }
+ if(getIsFile())
+ {
+ ret["is_file"]=(Json::UInt64)getValueOfIsFile();
+ }
+ else
+ {
+ ret["is_file"]=Json::Value();
+ }
+ if(getName())
+ {
+ ret["name"]=getValueOfName();
+ }
+ else
+ {
+ ret["name"]=Json::Value();
+ }
+ if(getParentId())
+ {
+ ret["parent_id"]=(Json::UInt64)getValueOfParentId();
+ }
+ else
+ {
+ ret["parent_id"]=Json::Value();
+ }
+ if(getOwnerId())
+ {
+ ret["owner_id"]=(Json::UInt64)getValueOfOwnerId();
+ }
+ else
+ {
+ ret["owner_id"]=Json::Value();
+ }
+ if(getSize())
+ {
+ ret["size"]=(Json::UInt64)getValueOfSize();
+ }
+ else
+ {
+ ret["size"]=Json::Value();
+ }
+ return ret;
+}
+
+bool Inode::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("is_file"))
+ {
+ if(!validJsonOfField(1, "is_file", pJson["is_file"], err, true))
+ return false;
+ }
+ else
+ {
+ err="The is_file column cannot be null";
+ return false;
+ }
+ if(pJson.isMember("name"))
+ {
+ if(!validJsonOfField(2, "name", pJson["name"], err, true))
+ return false;
+ }
+ if(pJson.isMember("parent_id"))
+ {
+ if(!validJsonOfField(3, "parent_id", pJson["parent_id"], err, true))
+ return false;
+ }
+ if(pJson.isMember("owner_id"))
+ {
+ if(!validJsonOfField(4, "owner_id", pJson["owner_id"], err, true))
+ return false;
+ }
+ else
+ {
+ err="The owner_id column cannot be null";
+ return false;
+ }
+ if(pJson.isMember("size"))
+ {
+ if(!validJsonOfField(5, "size", pJson["size"], err, true))
+ return false;
+ }
+ return true;
+}
+bool Inode::validateMasqueradedJsonForCreation(const Json::Value &pJson,
+ const std::vector &pMasqueradingVector,
+ std::string &err)
+{
+ if(pMasqueradingVector.size() != 6)
+ {
+ 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;
+ }
+ }
+ if(!pMasqueradingVector[3].empty())
+ {
+ if(pJson.isMember(pMasqueradingVector[3]))
+ {
+ if(!validJsonOfField(3, pMasqueradingVector[3], pJson[pMasqueradingVector[3]], err, true))
+ return false;
+ }
+ }
+ if(!pMasqueradingVector[4].empty())
+ {
+ if(pJson.isMember(pMasqueradingVector[4]))
+ {
+ if(!validJsonOfField(4, pMasqueradingVector[4], pJson[pMasqueradingVector[4]], err, true))
+ return false;
+ }
+ else
+ {
+ err="The " + pMasqueradingVector[4] + " column cannot be null";
+ return false;
+ }
+ }
+ if(!pMasqueradingVector[5].empty())
+ {
+ if(pJson.isMember(pMasqueradingVector[5]))
+ {
+ if(!validJsonOfField(5, pMasqueradingVector[5], pJson[pMasqueradingVector[5]], err, true))
+ return false;
+ }
+ }
+ }
+ catch(const Json::LogicError &e)
+ {
+ err = e.what();
+ return false;
+ }
+ return true;
+}
+bool Inode::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("is_file"))
+ {
+ if(!validJsonOfField(1, "is_file", pJson["is_file"], err, false))
+ return false;
+ }
+ if(pJson.isMember("name"))
+ {
+ if(!validJsonOfField(2, "name", pJson["name"], err, false))
+ return false;
+ }
+ if(pJson.isMember("parent_id"))
+ {
+ if(!validJsonOfField(3, "parent_id", pJson["parent_id"], err, false))
+ return false;
+ }
+ if(pJson.isMember("owner_id"))
+ {
+ if(!validJsonOfField(4, "owner_id", pJson["owner_id"], err, false))
+ return false;
+ }
+ if(pJson.isMember("size"))
+ {
+ if(!validJsonOfField(5, "size", pJson["size"], err, false))
+ return false;
+ }
+ return true;
+}
+bool Inode::validateMasqueradedJsonForUpdate(const Json::Value &pJson,
+ const std::vector &pMasqueradingVector,
+ std::string &err)
+{
+ if(pMasqueradingVector.size() != 6)
+ {
+ 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;
+ }
+ if(!pMasqueradingVector[3].empty() && pJson.isMember(pMasqueradingVector[3]))
+ {
+ if(!validJsonOfField(3, pMasqueradingVector[3], pJson[pMasqueradingVector[3]], err, false))
+ return false;
+ }
+ if(!pMasqueradingVector[4].empty() && pJson.isMember(pMasqueradingVector[4]))
+ {
+ if(!validJsonOfField(4, pMasqueradingVector[4], pJson[pMasqueradingVector[4]], err, false))
+ return false;
+ }
+ if(!pMasqueradingVector[5].empty() && pJson.isMember(pMasqueradingVector[5]))
+ {
+ if(!validJsonOfField(5, pMasqueradingVector[5], pJson[pMasqueradingVector[5]], err, false))
+ return false;
+ }
+ }
+ catch(const Json::LogicError &e)
+ {
+ err = e.what();
+ return false;
+ }
+ return true;
+}
+bool Inode::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())
+ {
+ return true;
+ }
+ if(!pJson.isString())
+ {
+ err="Type error in the "+fieldName+" field";
+ return false;
+ }
+ break;
+ case 3:
+ if(pJson.isNull())
+ {
+ return true;
+ }
+ if(!pJson.isUInt64())
+ {
+ err="Type error in the "+fieldName+" field";
+ return false;
+ }
+ break;
+ case 4:
+ 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 5:
+ if(pJson.isNull())
+ {
+ return true;
+ }
+ 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;
+}
diff --git a/backend/src/db/model/Inode.h b/backend/src/db/model/Inode.h
new file mode 100644
index 0000000..2f59e33
--- /dev/null
+++ b/backend/src/db/model/Inode.h
@@ -0,0 +1,275 @@
+/**
+ *
+ * Inode.h
+ * DO NOT EDIT. This file is generated by drogon_ctl
+ *
+ */
+
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#ifdef __cpp_impl_coroutine
+#include
+#endif
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace drogon
+{
+namespace orm
+{
+class DbClient;
+using DbClientPtr = std::shared_ptr;
+}
+}
+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;
+ };
+
+ 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 &pMasqueradingVector) noexcept(false);
+
+ Inode() = default;
+
+ void updateByJson(const Json::Value &pJson) noexcept(false);
+ void updateByMasqueradedJson(const Json::Value &pJson,
+ const std::vector &pMasqueradingVector) noexcept(false);
+ static bool validateJsonForCreation(const Json::Value &pJson, std::string &err);
+ static bool validateMasqueradedJsonForCreation(const Json::Value &,
+ const std::vector &pMasqueradingVector,
+ std::string &err);
+ static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err);
+ static bool validateMasqueradedJsonForUpdate(const Json::Value &,
+ const std::vector &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 &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 &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 &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 &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 &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 &getSize() const noexcept;
+ ///Set the value of the column size
+ void setSize(const uint64_t &pSize) noexcept;
+ void setSizeToNull() noexcept;
+
+
+ static size_t getColumnNumber() noexcept { return 6; }
+ static const std::string &getColumnName(size_t index) noexcept(false);
+
+ Json::Value toJson() const;
+ Json::Value toMasqueradedJson(const std::vector &pMasqueradingVector) const;
+ /// Relationship interfaces
+ private:
+ friend drogon::orm::Mapper;
+#ifdef __cpp_impl_coroutine
+ friend drogon::orm::CoroMapper;
+#endif
+ static const std::vector &insertColumns() noexcept;
+ void outputArgs(drogon::orm::internal::SqlBinder &binder) const;
+ const std::vector updateColumns() const;
+ void updateArgs(drogon::orm::internal::SqlBinder &binder) const;
+ ///For mysql or sqlite3
+ void updateId(const uint64_t id);
+ std::shared_ptr id_;
+ std::shared_ptr isFile_;
+ std::shared_ptr name_;
+ std::shared_ptr parentId_;
+ std::shared_ptr ownerId_;
+ std::shared_ptr size_;
+ 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_;
+ bool dirtyFlag_[6]={ 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(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(parametersCount > 0)
+ {
+ sql.resize(sql.length() - 1);
+ }
+ sql.append(1, ')');
+ LOG_TRACE << sql;
+ return sql;
+ }
+};
+} // namespace sqlite3
+} // namespace drogon_model
diff --git a/backend/src/db/model/Tokens.cc b/backend/src/db/model/Tokens.cc
new file mode 100644
index 0000000..d02df5e
--- /dev/null
+++ b/backend/src/db/model/Tokens.cc
@@ -0,0 +1,631 @@
+/**
+ *
+ * Tokens.cc
+ * DO NOT EDIT. This file is generated by drogon_ctl
+ *
+ */
+
+#include "Tokens.h"
+#include
+#include
+
+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 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(r["id"].as());
+ }
+ if(!r["owner_id"].isNull())
+ {
+ ownerId_=std::make_shared(r["owner_id"].as());
+ }
+ if(!r["exp"].isNull())
+ {
+ exp_=std::make_shared(r["exp"].as());
+ }
+ }
+ 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(r[index].as());
+ }
+ index = offset + 1;
+ if(!r[index].isNull())
+ {
+ ownerId_=std::make_shared(r[index].as());
+ }
+ index = offset + 2;
+ if(!r[index].isNull())
+ {
+ exp_=std::make_shared(r[index].as());
+ }
+ }
+
+}
+
+Tokens::Tokens(const Json::Value &pJson, const std::vector &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)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)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)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)pJson["id"].asUInt64());
+ }
+ }
+ if(pJson.isMember("owner_id"))
+ {
+ dirtyFlag_[1]=true;
+ if(!pJson["owner_id"].isNull())
+ {
+ ownerId_=std::make_shared((uint64_t)pJson["owner_id"].asUInt64());
+ }
+ }
+ if(pJson.isMember("exp"))
+ {
+ dirtyFlag_[2]=true;
+ if(!pJson["exp"].isNull())
+ {
+ exp_=std::make_shared((uint64_t)pJson["exp"].asUInt64());
+ }
+ }
+}
+
+void Tokens::updateByMasqueradedJson(const Json::Value &pJson,
+ const std::vector &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)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)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)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)pJson["id"].asUInt64());
+ }
+ }
+ if(pJson.isMember("owner_id"))
+ {
+ dirtyFlag_[1] = true;
+ if(!pJson["owner_id"].isNull())
+ {
+ ownerId_=std::make_shared((uint64_t)pJson["owner_id"].asUInt64());
+ }
+ }
+ if(pJson.isMember("exp"))
+ {
+ dirtyFlag_[2] = true;
+ if(!pJson["exp"].isNull())
+ {
+ exp_=std::make_shared((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 &Tokens::getId() const noexcept
+{
+ return id_;
+}
+void Tokens::setId(const uint64_t &pId) noexcept
+{
+ id_ = std::make_shared(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 &Tokens::getOwnerId() const noexcept
+{
+ return ownerId_;
+}
+void Tokens::setOwnerId(const uint64_t &pOwnerId) noexcept
+{
+ ownerId_ = std::make_shared(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 &Tokens::getExp() const noexcept
+{
+ return exp_;
+}
+void Tokens::setExp(const uint64_t &pExp) noexcept
+{
+ exp_ = std::make_shared(pExp);
+ dirtyFlag_[2] = true;
+}
+
+void Tokens::updateId(const uint64_t id)
+{
+ id_ = std::make_shared(id);
+}
+
+const std::vector &Tokens::insertColumns() noexcept
+{
+ static const std::vector 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 Tokens::updateColumns() const
+{
+ std::vector 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 &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 &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