├── .gitignore ├── android ├── .gitignore ├── .idea │ ├── .gitignore │ └── misc.xml ├── app │ ├── .gitignore │ ├── build.gradle │ ├── capacitor.build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── getcapacitor │ │ │ └── myapp │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── telegramdrive │ │ │ │ └── app │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable-land-hdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-mdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-xhdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-xxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-xxxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-hdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-mdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-xhdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-xxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-xxxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ ├── ic_launcher_background.xml │ │ │ └── splash.png │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ ├── ic_launcher_background.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── file_paths.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── getcapacitor │ │ └── myapp │ │ └── ExampleUnitTest.java ├── build.gradle ├── capacitor.settings.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── variables.gradle ├── capacitor.config.ts ├── package-lock.json ├── package.json ├── readme.md ├── server ├── apis │ ├── apis.ts │ ├── controllers │ │ ├── file.ts │ │ ├── folder.ts │ │ └── user.ts │ └── middlewares.ts ├── bot.ts ├── constants.ts ├── database │ ├── database.ts │ ├── folder.ts │ └── user.ts ├── fileManager.ts ├── index.ts ├── request.ts └── utils.ts ├── tsconfig.json └── ui ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── fileIcon.png ├── help │ ├── call-bot-api.png │ ├── create-bot-token.png │ ├── create-group-1.png │ ├── create-group-2.png │ ├── create-group-3.png │ ├── create-group-4.png │ └── help.md ├── index.html ├── robots.txt └── ss │ └── ss-home.png ├── src ├── App.tsx ├── components │ ├── components.css │ ├── folder │ │ ├── file.tsx │ │ ├── fileMask.tsx │ │ ├── folder.css │ │ └── folder.tsx │ ├── layout.tsx │ ├── navigator.tsx │ └── uploader │ │ ├── styles.css │ │ └── uploader.tsx ├── index.css ├── index.tsx ├── pages │ ├── home │ │ ├── home.css │ │ ├── home.tsx │ │ └── modals │ │ │ ├── addFolderModal.tsx │ │ │ ├── deleteFolderModal.tsx │ │ │ └── uploadFileModal.tsx │ └── login │ │ ├── login.css │ │ ├── login.tsx │ │ └── register.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── services │ ├── common.ts │ ├── endpoints.ts │ ├── file.ts │ ├── folder.ts │ └── user.ts └── setupTests.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env 3 | files/ 4 | docs.json 5 | .vscode 6 | .DS_Store 7 | uploads/ 8 | .npmrc 9 | dist/ -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | # Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore 2 | 3 | # Built application files 4 | *.apk 5 | *.aar 6 | *.ap_ 7 | *.aab 8 | 9 | # Files for the ART/Dalvik VM 10 | *.dex 11 | 12 | # Java class files 13 | *.class 14 | 15 | # Generated files 16 | bin/ 17 | gen/ 18 | out/ 19 | # Uncomment the following line in case you need and you don't have the release build type files in your app 20 | # release/ 21 | 22 | # Gradle files 23 | .gradle/ 24 | build/ 25 | 26 | # Local configuration file (sdk path, etc) 27 | local.properties 28 | 29 | # Proguard folder generated by Eclipse 30 | proguard/ 31 | 32 | # Log Files 33 | *.log 34 | 35 | # Android Studio Navigation editor temp files 36 | .navigation/ 37 | 38 | # Android Studio captures folder 39 | captures/ 40 | 41 | # IntelliJ 42 | *.iml 43 | .idea/workspace.xml 44 | .idea/tasks.xml 45 | .idea/gradle.xml 46 | .idea/assetWizardSettings.xml 47 | .idea/dictionaries 48 | .idea/libraries 49 | # Android Studio 3 in .gitignore file. 50 | .idea/caches 51 | .idea/modules.xml 52 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 53 | .idea/navEditor.xml 54 | 55 | # Keystore files 56 | # Uncomment the following lines if you do not want to check your keystore files in. 57 | #*.jks 58 | #*.keystore 59 | 60 | # External native build folder generated in Android Studio 2.2 and later 61 | .externalNativeBuild 62 | .cxx/ 63 | 64 | # Google Services (e.g. APIs or Firebase) 65 | # google-services.json 66 | 67 | # Freeline 68 | freeline.py 69 | freeline/ 70 | freeline_project_description.json 71 | 72 | # fastlane 73 | fastlane/report.xml 74 | fastlane/Preview.html 75 | fastlane/screenshots 76 | fastlane/test_output 77 | fastlane/readme.md 78 | 79 | # Version control 80 | vcs.xml 81 | 82 | # lint 83 | lint/intermediates/ 84 | lint/generated/ 85 | lint/outputs/ 86 | lint/tmp/ 87 | # lint/reports/ 88 | 89 | # Android Profiling 90 | *.hprof 91 | 92 | # Cordova plugins for Capacitor 93 | capacitor-cordova-android-plugins 94 | 95 | # Copied web assets 96 | app/src/main/assets/public 97 | 98 | # Generated Config files 99 | app/src/main/assets/capacitor.config.json 100 | app/src/main/assets/capacitor.plugins.json 101 | app/src/main/res/xml/config.xml 102 | -------------------------------------------------------------------------------- /android/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | !/build/.npmkeep 3 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | defaultConfig { 6 | applicationId "com.telegramdrive.app" 7 | minSdkVersion rootProject.ext.minSdkVersion 8 | targetSdkVersion rootProject.ext.targetSdkVersion 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | aaptOptions { 13 | // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. 14 | // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 15 | ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' 16 | } 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | repositories { 27 | flatDir{ 28 | dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(include: ['*.jar'], dir: 'libs') 34 | implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" 35 | implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" 36 | implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" 37 | implementation project(':capacitor-android') 38 | testImplementation "junit:junit:$junitVersion" 39 | androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" 40 | androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" 41 | implementation project(':capacitor-cordova-android-plugins') 42 | } 43 | 44 | apply from: 'capacitor.build.gradle' 45 | 46 | try { 47 | def servicesJSON = file('google-services.json') 48 | if (servicesJSON.text) { 49 | apply plugin: 'com.google.gms.google-services' 50 | } 51 | } catch(Exception e) { 52 | logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") 53 | } 54 | -------------------------------------------------------------------------------- /android/app/capacitor.build.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | 3 | android { 4 | compileOptions { 5 | sourceCompatibility JavaVersion.VERSION_11 6 | targetCompatibility JavaVersion.VERSION_11 7 | } 8 | } 9 | 10 | apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" 11 | dependencies { 12 | 13 | 14 | } 15 | 16 | 17 | if (hasProperty('postBuildExtras')) { 18 | postBuildExtras() 19 | } 20 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import android.content.Context; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | import androidx.test.platform.app.InstrumentationRegistry; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 23 | 24 | assertEquals("com.getcapacitor.app", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/telegramdrive/app/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.telegramdrive.app; 2 | 3 | import com.getcapacitor.BridgeActivity; 4 | 5 | public class MainActivity extends BridgeActivity {} 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/drawable-land-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/drawable-land-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/drawable-land-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/drawable-land-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/drawable-land-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/drawable-port-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/drawable-port-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/drawable-port-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/drawable-port-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/drawable-port-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/drawable/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Telegram Drive 4 | Telegram Drive 5 | com.telegramdrive.app 6 | com.telegramdrive.app 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 17 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | 14 | @Test 15 | public void addition_isCorrect() throws Exception { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:7.2.1' 11 | classpath 'com.google.gms:google-services:4.3.13' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | apply from: "variables.gradle" 19 | 20 | allprojects { 21 | repositories { 22 | google() 23 | mavenCentral() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/capacitor.settings.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | include ':capacitor-android' 3 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') 4 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | # Automatically convert third-party libraries to use AndroidX 24 | android.enableJetifier=true 25 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':capacitor-cordova-android-plugins' 3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') 4 | 5 | apply from: 'capacitor.settings.gradle' -------------------------------------------------------------------------------- /android/variables.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | minSdkVersion = 22 3 | compileSdkVersion = 32 4 | targetSdkVersion = 32 5 | androidxActivityVersion = '1.4.0' 6 | androidxAppCompatVersion = '1.4.2' 7 | androidxCoordinatorLayoutVersion = '1.2.0' 8 | androidxCoreVersion = '1.8.0' 9 | androidxFragmentVersion = '1.4.1' 10 | coreSplashScreenVersion = '1.0.0-rc01' 11 | androidxWebkitVersion = '1.4.0' 12 | junitVersion = '4.13.2' 13 | androidxJunitVersion = '1.1.3' 14 | androidxEspressoCoreVersion = '3.4.0' 15 | cordovaAndroidVersion = '10.1.1' 16 | } -------------------------------------------------------------------------------- /capacitor.config.ts: -------------------------------------------------------------------------------- 1 | import { CapacitorConfig } from '@capacitor/cli'; 2 | 3 | const config: CapacitorConfig = { 4 | appId: 'com.telegramdrive.app', 5 | appName: 'Telegram Drive', 6 | webDir: 'dist/ui', 7 | bundledWebRuntime: false 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "telegram-cloud-storage", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "engines": { 7 | "node": "16.x" 8 | }, 9 | "scripts": { 10 | "start": "cd dist && NODE_ENV=production node index.js", 11 | "build": "rm -rf dist && npm run react:build && tsc --sourceMap false && mv ui/build dist/ui && mkdir dist/uploads", 12 | "react:build": "cd ui && npm run build && cd ..", 13 | "react:dev": "cd ui && PORT=3001 npm start", 14 | "node:dev": "NODE_ENV=development nodemon server/index.ts", 15 | "install": "npm install --prefix ./ui", 16 | "android:dev": "npx cap run android" 17 | }, 18 | "keywords": [], 19 | "author": "", 20 | "license": "ISC", 21 | "dependencies": { 22 | "@capacitor/android": "4.4.0", 23 | "@capacitor/core": "4.4.0", 24 | "@capacitor/ios": "4.4.0", 25 | "bcrypt": "5.1.0", 26 | "body-parser": "1.20.1", 27 | "cli-progress": "^3.9.0", 28 | "cors": "2.8.5", 29 | "dotenv": "^10.0.0", 30 | "express": "4.18.2", 31 | "glob": "^7.1.7", 32 | "jsonwebtoken": "8.5.1", 33 | "mongoose": "6.6.5", 34 | "multer": "1.4.5-lts.1", 35 | "node-telegram-bot-api": "^0.53.0", 36 | "nodemon": "^2.0.20", 37 | "readline": "^1.3.0", 38 | "request": "^2.88.2", 39 | "yargs": "^17.0.1" 40 | }, 41 | "devDependencies": { 42 | "@capacitor/cli": "4.4.0", 43 | "@types/express": "4.17.14", 44 | "@types/glob": "^7.1.3", 45 | "@types/jsonwebtoken": "8.5.9", 46 | "@types/multer": "1.4.7", 47 | "@types/node": "15.14.9", 48 | "@types/node-telegram-bot-api": "^0.51.1", 49 | "@types/yargs": "^17.0.0", 50 | "ts-node": "10.9.1", 51 | "typescript": "4.8.4" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Telegram Cloud Storage 2 | 3 | ### Environment variables required 4 | 5 | Create a file `.env` on the root folder with the following credentials. 6 | 7 | `mongoPassword = ******` 8 | 9 | `mongoUser = ******` 10 | 11 | `jwtSecret = ******` 12 | 13 | ### Installation 14 | 15 | - `npm install` 16 | 17 | ### Start Servers 18 | 19 | For development we have 2 servers for Node and React. 20 | 21 | - `npm run react:dev` 22 | - `npm run node:dev` 23 | 24 | Now the UI will be available at http://localhost:3001 25 | 26 | Production [URL](https://telegramcloudstorage-production.up.railway.app). 27 | 28 | ![server](./ui/public/ss/ss-home.png) 29 | 30 | ## Register new user 31 | 32 | [Read here](https://github.com/ebinxavier/telegramCloudStorage/blob/main/ui/public/help/help.md) 33 | -------------------------------------------------------------------------------- /server/apis/apis.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import bodyParser from "body-parser"; 3 | import path from "path"; 4 | import cors from "cors"; 5 | 6 | import userController from "./controllers/user"; 7 | import folderController from "./controllers/folder"; 8 | import fileController from "./controllers/file"; 9 | 10 | const app = express(); 11 | 12 | app.use(bodyParser.urlencoded({ extended: false })); 13 | app.use(bodyParser.json()); 14 | app.use(cors()); 15 | 16 | export const startAPIServer = () => { 17 | // API Endpoints 18 | app.use("/api/v1/user/", userController); 19 | app.use("/api/v1/folder/", folderController); 20 | app.use("/api/v1/file/", fileController); 21 | 22 | app.get("/api/v1/healthy", (_req, res) => { 23 | res.send({ status: "healthy", req: _req.headers }); 24 | }); 25 | 26 | // Serving React UI 27 | app.use("/", express.static(path.join(__dirname, "../ui"))); 28 | 29 | app.get("*", (_req, res) => { 30 | console.log("url:", _req.url); 31 | res.sendFile(path.join(__dirname, "../ui/index.html")); 32 | }); 33 | 34 | const PORT = process.env.PORT || 3000; 35 | app.listen(PORT); 36 | console.log(`Express server started on port: ${PORT}!`); 37 | 38 | return app; 39 | }; 40 | -------------------------------------------------------------------------------- /server/apis/controllers/file.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { Request, Response } from "express"; 3 | import multer from "multer"; 4 | import path from "path"; 5 | import fs from "fs"; 6 | import { 7 | downloadDocument, 8 | getDownloadURL, 9 | removeDocument, 10 | uploadDocument, 11 | } from "../../bot"; 12 | import { injectOwnerId } from "../middlewares"; 13 | import { addFile, removeFile } from "../../database/folder"; 14 | import mongoose from "mongoose"; 15 | import { FILE_STORAGE_PATH } from "../../constants"; 16 | 17 | const storage = multer.diskStorage({ 18 | destination: function (req, file, cb) { 19 | fs.mkdirSync(FILE_STORAGE_PATH, { recursive: true }); 20 | cb(null, FILE_STORAGE_PATH); 21 | }, 22 | filename: function (req, file, cb) { 23 | cb(null, Date.now() + path.extname(file.originalname)); //Appending extension 24 | }, 25 | }); 26 | 27 | const upload = multer({ dest: FILE_STORAGE_PATH, storage }); 28 | 29 | const file = express.Router(); 30 | 31 | file.use(injectOwnerId); 32 | 33 | file.post( 34 | "/upload", 35 | upload.single("file"), 36 | async (req: Request, res: Response) => { 37 | try { 38 | console.log("Saving...", req.file); 39 | const chatId = req.headers.chatId; 40 | const token = req.headers.token; 41 | const owner = req.headers.owner; 42 | const data = fs.readFileSync(req.file.path); 43 | const response = await uploadDocument( 44 | data, 45 | req.file.originalname, 46 | token, 47 | chatId 48 | ); 49 | const updatedFolder = await addFile( 50 | new mongoose.Types.ObjectId(owner as string), 51 | req.body.path, 52 | req.file.originalname, 53 | { 54 | ...(response.document || response.sticker), // some file will treated as stickers eg: *.webp 55 | message_id: response.message_id, 56 | } 57 | ); 58 | // removing temp file form server 59 | fs.unlink(req.file.path, (error) => { 60 | if (error) { 61 | console.log(`Unable to remove temp file: ${req.file.path}!`); 62 | } 63 | res.send(updatedFolder); 64 | }); 65 | } catch (e) { 66 | res.status(500).send({ 67 | e, 68 | message: e?.message, 69 | }); 70 | } 71 | } 72 | ); 73 | 74 | file.get("/download", async (req: Request, res: Response) => { 75 | try { 76 | const token = req.headers.token; 77 | const filePath = await downloadDocument(req.query.fileId, token); 78 | const url = getDownloadURL(filePath, token); 79 | res.send({ url }); 80 | } catch (e) { 81 | res.status(500).send({ 82 | e, 83 | message: e?.message, 84 | }); 85 | } 86 | }); 87 | 88 | file.post("/remove", async (req: Request, res: Response) => { 89 | try { 90 | const token = req.headers.token; 91 | const owner = req.headers.owner; 92 | const chatId = req.headers.chatId; 93 | 94 | const path = req.body.path; 95 | const fileId = req.body.fileId; // mongoID 96 | const messageId = req.body.messageId; // telegramID 97 | 98 | // Remove from MongoDB 99 | await removeFile( 100 | new mongoose.Types.ObjectId(owner as string), 101 | path, 102 | fileId 103 | ); 104 | // Remove from Telegram 105 | const tgResponse = await removeDocument(messageId, token, chatId); 106 | res.send({ folder: tgResponse }); 107 | } catch (e) { 108 | res.status(500).send({ 109 | e, 110 | message: e?.message, 111 | }); 112 | } 113 | }); 114 | 115 | export default file; 116 | -------------------------------------------------------------------------------- /server/apis/controllers/folder.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { Request, Response } from "express"; 3 | import mongoose from "mongoose"; 4 | import { listFolder, makeFolder, removeFolder } from "../../database/folder"; 5 | import { injectOwnerId } from "../middlewares"; 6 | const folder = express.Router(); 7 | 8 | folder.use(injectOwnerId); 9 | 10 | folder.post("/ls", async (req: Request, res: Response) => { 11 | try { 12 | const folder = await listFolder( 13 | new mongoose.Types.ObjectId(req.headers.owner as string), 14 | req.body.path 15 | ); 16 | res.send(folder); 17 | } catch (e) { 18 | console.log(e); 19 | res.status(500).send({ 20 | e, 21 | message: e?.message, 22 | }); 23 | } 24 | }); 25 | 26 | folder.post("/mkdir", async (req: Request, res: Response) => { 27 | try { 28 | const folder = await makeFolder( 29 | new mongoose.Types.ObjectId(req.headers.owner as string), 30 | req.body.path, 31 | req.body.folderName 32 | ); 33 | res.send(folder); 34 | } catch (e) { 35 | console.log(e); 36 | res.status(500).send({ 37 | e, 38 | message: e?.message, 39 | }); 40 | } 41 | }); 42 | 43 | folder.post("/rmdir", async (req: Request, res: Response) => { 44 | try { 45 | const status = await removeFolder( 46 | new mongoose.Types.ObjectId(req.headers.owner as string), 47 | req.body.path 48 | ); 49 | res.send(status); 50 | } catch (e) { 51 | console.log(e); 52 | res.status(500).send({ 53 | e, 54 | message: e?.message, 55 | }); 56 | } 57 | }); 58 | 59 | export default folder; 60 | -------------------------------------------------------------------------------- /server/apis/controllers/user.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { Request, Response } from "express"; 3 | import jwt from "jsonwebtoken"; 4 | import { JWT_EXPIRY } from "../../constants"; 5 | 6 | import { deRegisterUser, loginUser, registerUser } from "../../database/user"; 7 | import { injectOwnerId } from "../middlewares"; 8 | const user = express.Router(); 9 | 10 | /** 11 | * POST: { body.username, body.password } 12 | */ 13 | 14 | user.post("/login", async (req: Request, res: Response) => { 15 | try { 16 | const secret = process.env.jwtSecret; 17 | if (!secret) { 18 | console.log("JWT Secret not defined in the env!"); 19 | throw new Error("Internal server error"); 20 | } 21 | const { id, chatId, token } = await loginUser( 22 | req.body.username, 23 | req.body.password 24 | ); 25 | const JWTtoken = await jwt.sign({ id, chatId, token }, secret, { 26 | expiresIn: JWT_EXPIRY, 27 | }); 28 | res.send({ token: JWTtoken }); 29 | } catch (e) { 30 | res.status(500).send({ 31 | e, 32 | message: e?.message, 33 | }); 34 | } 35 | }); 36 | 37 | /** 38 | * POST: { body.username, body.password, body.botToken, body.chatId } 39 | */ 40 | user.post("/register", async (req: Request, res) => { 41 | try { 42 | const user = await registerUser( 43 | req.body.username, 44 | req.body.password, 45 | req.body.botToken, 46 | req.body.chatId 47 | ); 48 | res.send(user); 49 | } catch (e) { 50 | console.log(e); 51 | res.status(500).send({ 52 | e, 53 | message: e?.message, 54 | }); 55 | } 56 | }); 57 | 58 | /** 59 | * POST: { body.username, body.password } 60 | */ 61 | user.post("/deregister", async (req: Request, res) => { 62 | try { 63 | const done = await deRegisterUser(req.body.username, req.body.password); 64 | res.send({ done }); 65 | } catch (e) { 66 | console.log(e); 67 | res.status(500).send({ 68 | e, 69 | message: e?.message, 70 | }); 71 | } 72 | }); 73 | 74 | export default user; 75 | -------------------------------------------------------------------------------- /server/apis/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import jwt from "jsonwebtoken"; 3 | 4 | import { getSecret } from "../utils"; 5 | 6 | export const injectOwnerId = ( 7 | req: Request, 8 | res: Response, 9 | next: NextFunction 10 | ) => { 11 | try { 12 | const auth = req.headers.authorization; // Bearer 13 | const token = auth?.split(" ")[1]; 14 | if (!token) { 15 | console.log("JWT token not found!"); 16 | throw ""; 17 | } 18 | const jwtSecret = getSecret("jwtSecret"); 19 | const isValid = jwt.verify(token, jwtSecret); 20 | if (isValid) { 21 | const tokenInfo = jwt.decode(token); 22 | req.headers["owner"] = tokenInfo["id"]; 23 | req.headers["chatId"] = tokenInfo["chatId"]; 24 | req.headers["token"] = tokenInfo["token"]; 25 | next(); 26 | } else { 27 | console.log("Invalid Token!"); 28 | throw ""; 29 | } 30 | } catch (e) { 31 | console.log(e); 32 | res.status(401).send("Unauthorized!"); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /server/bot.ts: -------------------------------------------------------------------------------- 1 | import TelegramBot from "node-telegram-bot-api"; 2 | 3 | const cachedBots = {}; 4 | const getMemoizedBot = (token) => { 5 | if (!cachedBots[token]) { 6 | cachedBots[token] = new TelegramBot(token); 7 | } 8 | return cachedBots[token]; 9 | }; 10 | 11 | export const uploadDocument = (data, fileName, token, chatId) => { 12 | const bot = getMemoizedBot(token); 13 | return bot.sendDocument( 14 | chatId, 15 | data, 16 | {}, 17 | { 18 | filename: fileName, 19 | contentType: "application/octet-stream", 20 | } 21 | ); 22 | }; 23 | 24 | export const removeDocument = (messageId, token, chatId) => { 25 | const bot = getMemoizedBot(token); 26 | return bot.deleteMessage(chatId, messageId); 27 | }; 28 | 29 | export const downloadDocument = async (fileId, token) => { 30 | const bot = getMemoizedBot(token); 31 | const file = await bot.getFile(fileId); 32 | return file.file_path; 33 | }; 34 | 35 | export const getDownloadURL = (fileName, token) => { 36 | return `https://api.telegram.org/file/bot${token}/${fileName}`; 37 | }; 38 | -------------------------------------------------------------------------------- /server/constants.ts: -------------------------------------------------------------------------------- 1 | export const JWT_EXPIRY = "10d"; 2 | export const FILE_STORAGE_PATH = "uploads/"; 3 | -------------------------------------------------------------------------------- /server/database/database.ts: -------------------------------------------------------------------------------- 1 | export { registerUser, loginUser } from "./user"; 2 | export { listFolder } from "./folder"; 3 | 4 | import mongoose from "mongoose"; 5 | 6 | export const MONGO_URL = `mongodb+srv://${process.env.mongoUser}:${process.env.mongoPassword}@cluster0.jld9cbs.mongodb.net/tgStorage`; 7 | 8 | export const connect = () => { 9 | console.log("Connecting to DB..."); 10 | mongoose 11 | .connect(MONGO_URL) 12 | .then(() => console.log("Connected to DB")) 13 | .catch((e) => console.log(e)); 14 | }; 15 | -------------------------------------------------------------------------------- /server/database/folder.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | const Schema = mongoose.Schema; 3 | 4 | const FileSchema = new Schema({ 5 | fileName: String, 6 | content: Object, 7 | }); 8 | 9 | export const FolderSchema = new Schema( 10 | { 11 | folderName: { 12 | type: String, 13 | required: [true, "foldername is required!"], 14 | }, 15 | path: String, // eg: /user/folderA/folderB 16 | folders: [Schema.Types.Mixed], 17 | files: [FileSchema], 18 | lastModified: Date, 19 | created: Date, 20 | owner: Schema.Types.ObjectId, 21 | parentFolder: Schema.Types.ObjectId, 22 | }, 23 | { 24 | strict: false, 25 | } 26 | ); 27 | 28 | export interface FolderDTO { 29 | folderName: string; 30 | path: string; 31 | folders: FolderDTO[]; 32 | files: string[]; 33 | lastModified: Date; 34 | created: Date; 35 | owner: mongoose.Types.ObjectId; 36 | } 37 | 38 | const removeTrailingSlash = (path: string) => { 39 | return path.slice(-1)[0] === "/" ? path.slice(0, -1) : path; // remove trailing '/' 40 | }; 41 | 42 | export const FolderModel = mongoose.model(`FolderModel`, FolderSchema); 43 | 44 | export const makeFolderModel = async ( 45 | owner: mongoose.Types.ObjectId, 46 | path: string, 47 | folderName: string, 48 | parentFolder: mongoose.Types.ObjectId 49 | ) => { 50 | path = removeTrailingSlash(path); 51 | // Creating a folder model 52 | const folder = new FolderModel({ 53 | folderName, 54 | path: `${path}/${folderName}`, 55 | lastModified: new Date(), 56 | created: new Date(), 57 | folders: [], 58 | files: [], 59 | owner, 60 | parentFolder, 61 | }); 62 | 63 | // Saving the folder to DB 64 | await folder.save(); 65 | return folder; 66 | }; 67 | 68 | export const listFolder = async ( 69 | owner: mongoose.Types.ObjectId, 70 | path: string 71 | ): Promise => { 72 | if (!owner || !path) throw new Error("Owner and path are required!"); 73 | path = removeTrailingSlash(path); 74 | const FolderModel = mongoose.model(`FolderModel`, FolderSchema); 75 | const folder = await FolderModel.findOne({ path, owner }).populate("folders"); 76 | if (!folder) throw new Error("Folder not found!"); 77 | const result: FolderDTO = folder.toJSON(); 78 | return result; 79 | }; 80 | 81 | export const makeFolder = async ( 82 | owner: mongoose.Types.ObjectId, 83 | path: string, 84 | folderName: string 85 | ): Promise => { 86 | if (!owner || !path || !folderName) 87 | throw new Error("Owner, path and folderName are required!"); 88 | const FolderModel = mongoose.model(`FolderModel`, FolderSchema); 89 | console.log("creating folder path:", path); 90 | path = removeTrailingSlash(path); 91 | const folder = await FolderModel.findOne({ path, owner }); 92 | if (!folder) throw new Error("Folder not found!"); 93 | if (folder.folders.find((folder) => folder.folderName === folderName)) { 94 | throw new Error(`Folder '${folderName}' already exists on path '${path}'`); 95 | } 96 | const subFolder = await makeFolderModel(owner, path, folderName, folder._id); 97 | console.log("Making subFolder", folderName); 98 | folder.folders.push(subFolder as any); 99 | folder.lastModified = new Date(); 100 | await folder.save(); 101 | console.log("Folder added"); 102 | const result: FolderDTO = folder.toJSON(); 103 | return result; 104 | }; 105 | 106 | const getAllDocsToBeDeleted = async ( 107 | parentId: mongoose.Types.ObjectId, 108 | accumulator: mongoose.Types.ObjectId[] 109 | ) => { 110 | const parent = await FolderModel.findOne({ _id: parentId }).populate( 111 | "folders" 112 | ); 113 | if (!parent) return; 114 | const children = parent.folders; 115 | accumulator.push(...children.map((child) => child._id)); 116 | for (let child of children) { 117 | await getAllDocsToBeDeleted(child._id, accumulator); 118 | } 119 | return accumulator; 120 | }; 121 | 122 | export const removeFolder = async ( 123 | owner: mongoose.Types.ObjectId, 124 | path: string 125 | ): Promise => { 126 | if (!owner || !path) throw new Error("Owner and path are required!"); 127 | path = removeTrailingSlash(path); 128 | if (path === "/root") throw new Error("Root folder cannot be deleted!"); 129 | const FolderModel = mongoose.model(`FolderModel`, FolderSchema); 130 | 131 | // Fetching the folder using path 132 | const folder = await FolderModel.findOne({ path, owner }).populate("folders"); 133 | if (!folder) throw new Error("Folder not found!"); 134 | 135 | // Delete reference on its parent 136 | if (folder.parentFolder) { 137 | const parent = await FolderModel.findOne({ 138 | _id: folder.parentFolder, 139 | }).populate("folders"); 140 | const childIndex = parent.folders.findIndex((child) => { 141 | return child._id.toString() === folder._id.toString(); 142 | }); 143 | parent.folders.splice(childIndex, 1); 144 | await parent.save(); 145 | } 146 | const listOfDocs = [folder._id]; 147 | const ids = await getAllDocsToBeDeleted(folder._id, listOfDocs); 148 | const status = await FolderModel.deleteMany({ _id: { $in: ids } }); 149 | return status; 150 | }; 151 | 152 | export const addFile = async ( 153 | owner: mongoose.Types.ObjectId, 154 | path: string, 155 | fileName: string, 156 | fileContent: any 157 | ) => { 158 | if (!owner || !path || !fileName || !fileContent) 159 | throw new Error("Owner, path and folderName are required!"); 160 | 161 | const FolderModel = mongoose.model(`FolderModel`, FolderSchema); 162 | path = removeTrailingSlash(path); 163 | const folder = await FolderModel.findOne({ path, owner }); 164 | if (!folder) throw new Error("Folder not found!"); 165 | 166 | const FileModel = mongoose.model(`FileModel`, FileSchema); 167 | const file = new FileModel({ 168 | fileName, 169 | content: fileContent, 170 | }); 171 | 172 | folder.files.push(file); 173 | 174 | const doc = await folder.save(); 175 | console.log("File added!"); 176 | return doc; 177 | }; 178 | 179 | export const removeFile = async ( 180 | owner: mongoose.Types.ObjectId, 181 | path: string, 182 | fileId: mongoose.Types.ObjectId 183 | ) => { 184 | if (!owner || !path || !fileId) 185 | throw new Error("Owner, path and fileId are required!"); 186 | 187 | const FolderModel = mongoose.model(`FolderModel`, FolderSchema); 188 | path = removeTrailingSlash(path); 189 | const folder = await FolderModel.findOne({ path, owner }); 190 | if (!folder) throw new Error("Folder not found!"); 191 | const deleteFileIndex = folder.files.findIndex( 192 | (file) => file._id.toString() === fileId.toString() 193 | ); 194 | folder.files.splice(deleteFileIndex, 1); 195 | const doc = await folder.save(); 196 | console.log("File removed: ", fileId); 197 | return doc; 198 | }; 199 | -------------------------------------------------------------------------------- /server/database/user.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import bcrypt from "bcrypt"; 3 | 4 | import { makeFolderModel, FolderModel } from "./folder"; 5 | 6 | const Schema = mongoose.Schema; 7 | 8 | const UserSchema = new Schema({ 9 | username: { 10 | type: String, 11 | required: [true, "Username is required!"], 12 | unique: true, 13 | }, 14 | password: String, 15 | botToken: { type: String, required: true }, 16 | chatId: { type: String, required: true }, 17 | createdDate: Date, 18 | rootFolder: { 19 | type: Schema.Types.ObjectId, 20 | ref: "FolderSchema", 21 | }, 22 | }); 23 | 24 | // Compile model from schema 25 | const UserModel = mongoose.model("UserModel", UserSchema); 26 | 27 | const hashPassword = async (password) => { 28 | const salt = await bcrypt.genSalt(10); 29 | const hash = await bcrypt.hash(password, salt); 30 | return hash; 31 | }; 32 | 33 | const validatePassword = (password: string, hash: string) => { 34 | return bcrypt.compare(password, hash); 35 | }; 36 | 37 | export interface UserDTO { 38 | username: string; 39 | password: string; 40 | botToken: string; 41 | chatId: string; 42 | _id: mongoose.Types.ObjectId; 43 | createdDate: Date; 44 | rootFolder: mongoose.Types.ObjectId; 45 | } 46 | 47 | export const registerUser = async ( 48 | username: string, 49 | password: string, 50 | botToken: string, 51 | chatId: string 52 | ): Promise => { 53 | try { 54 | const hash = await hashPassword(password); 55 | // Creating new user 56 | const userModel = new UserModel({ 57 | username, 58 | password: hash, 59 | botToken, 60 | chatId, 61 | createdDate: new Date(), 62 | }); 63 | const userData = await userModel.save(); 64 | // Creating root folder model 65 | const rootFolder = await makeFolderModel(userData._id, "", "root", null); 66 | await userModel.update( 67 | { 68 | rootFolder, 69 | }, 70 | { upsert: true } 71 | ); 72 | const response: UserDTO = userData.toJSON(); 73 | delete response.password; 74 | console.log(`User "${username}" registered!`); 75 | return response; 76 | } catch (e) { 77 | console.log("Error while registering user"); 78 | throw e; 79 | } 80 | }; 81 | 82 | export const loginUser = async ( 83 | username: string, 84 | password: string 85 | ): Promise<{ id: mongoose.Types.ObjectId; token: string; chatId: string }> => { 86 | try { 87 | const record = await UserModel.findOne({ username }); 88 | if (record) { 89 | const isValid = await validatePassword( 90 | password, 91 | record.password as string 92 | ); 93 | if (isValid) { 94 | console.log("Login success!", record._id); 95 | return { 96 | id: record._id, 97 | token: record.botToken as string, 98 | chatId: record.chatId as string, 99 | }; 100 | } else { 101 | console.log("Wrong pswd!"); 102 | throw new Error("Login Error!"); 103 | } 104 | } else { 105 | console.log("No such user!"); 106 | throw new Error("No such user!"); 107 | } 108 | } catch (e) { 109 | console.log("Error while login!"); 110 | throw e; 111 | } 112 | }; 113 | 114 | export const deRegisterUser = async ( 115 | username: string, 116 | password: string 117 | ): Promise => { 118 | try { 119 | const record = await UserModel.findOne({ username }); 120 | if (record) { 121 | const isValid = await validatePassword( 122 | password, 123 | record.password as string 124 | ); 125 | if (isValid) { 126 | console.log("Login success!"); 127 | try { 128 | const owner = record._id; 129 | await record.delete(); 130 | console.log(`User "${username}" deleted!`); 131 | console.log(`Deleting all the folder of ${username}`); 132 | const deleteResult = await FolderModel.deleteMany({ 133 | owner, 134 | }); 135 | console.log(`Deleted all the folders of ${username}`, deleteResult); 136 | return true; 137 | } catch (e) { 138 | console.log("Error", e); 139 | throw e; 140 | } 141 | } else { 142 | console.log("Login Error!"); 143 | throw new Error("Login Error!"); 144 | } 145 | } else { 146 | console.log("No such user!"); 147 | throw new Error("No such user!"); 148 | } 149 | return false; 150 | } catch (e) { 151 | console.log("Error while login!", e); 152 | throw e; 153 | } 154 | }; 155 | -------------------------------------------------------------------------------- /server/fileManager.ts: -------------------------------------------------------------------------------- 1 | // import glob from "glob"; 2 | // import fs from "fs"; 3 | // import { downloadDocument, getDownloadURL, uploadDocument } from "./bot"; 4 | // import path from "path"; 5 | // import { download as downloadFile } from "./request"; 6 | 7 | // const getFiles = (src, callback) => { 8 | // glob(src + "/**/*", { nodir: true }, callback); 9 | // }; 10 | 11 | // export const listAllFiles = (path): Promise => { 12 | // return new Promise((resolve, reject) => { 13 | // getFiles(path, (err, res) => { 14 | // if (err) { 15 | // reject(err); 16 | // } else { 17 | // resolve(res); 18 | // } 19 | // }); 20 | // }); 21 | // }; 22 | 23 | // export const saveSnapShot = (json) => { 24 | // console.log("saving status..."); 25 | // fs.writeFileSync("docs.bkp.json", JSON.stringify(json, null, 2)); 26 | // try { 27 | // fs.unlinkSync("docs.json"); 28 | // } catch (e) { 29 | // console.log("No previous doc.json found!"); 30 | // } 31 | // fs.renameSync("docs.bkp.json", "docs.json"); 32 | // }; 33 | 34 | // export const uploadDirectory = async ( 35 | // directory: string, 36 | // updateSnapShotInterval: number = 10 37 | // ) => { 38 | // const snapShot = []; 39 | // let updateIndex = 0; 40 | // let fileIndex = 1; 41 | // const files: string[] = await listAllFiles(directory); 42 | // console.log("\n\n---------\n" + files.length + " files found\n---------\n\n"); 43 | 44 | // for (let fileName of files) { 45 | // try { 46 | // if (updateIndex > updateSnapShotInterval) { 47 | // saveSnapShot(snapShot); 48 | // updateIndex = 0; 49 | // } 50 | // const filePath = path.resolve(fileName); 51 | // const data = fs.readFileSync(filePath); 52 | // console.log(`Uploading: ${fileName} [${fileIndex++}/${files.length}]`); 53 | // const res = await uploadDocument(data, fileName); 54 | // snapShot.push({ 55 | // file: fileName.replace(`${directory}/`, ""), 56 | // fileId: res.document?.file_id || res.video?.file_id, 57 | // }); 58 | // } catch (e) { 59 | // console.log("Upload Error:", fileName, e); 60 | // } finally { 61 | // console.log("Done!"); 62 | // } 63 | // } 64 | // if (files.length) saveSnapShot(snapShot); 65 | // }; 66 | 67 | // export const downloadDirectory = async ( 68 | // docsFileName: string, 69 | // outputDir: string 70 | // ) => { 71 | // try { 72 | // fs.mkdirSync(outputDir); 73 | // } catch (e) { 74 | // console.log("Directory Exists! specify an empty directory"); 75 | // return; 76 | // } 77 | // let files; 78 | // try { 79 | // const data = fs.readFileSync(path.resolve(docsFileName)); 80 | // files = JSON.parse(data.toString()); 81 | // } catch (e) { 82 | // console.log("File not found :", docsFileName); 83 | // return; 84 | // } 85 | // let fileIndex = 1; 86 | // for (let item of files) { 87 | // const dir = item.file.substr(0, item.file.lastIndexOf("/")); 88 | // fs.mkdirSync(`${outputDir}/${dir}`, { recursive: true }); 89 | // const file = await downloadDocument(item.fileId); 90 | // console.log(`[${fileIndex++}/${files.length}]\t${outputDir}/${item.file}`); 91 | // const url = getDownloadURL(file); 92 | // await downloadFile(url, `${outputDir}/temp`); 93 | // fs.renameSync(`${outputDir}/temp`, `${outputDir}/${item.file}`); 94 | // } 95 | // }; 96 | -------------------------------------------------------------------------------- /server/index.ts: -------------------------------------------------------------------------------- 1 | import env from "dotenv"; 2 | env.config(); 3 | 4 | import { connect } from "./database/database"; 5 | 6 | import { startAPIServer } from "./apis/apis"; 7 | 8 | // Connecting to MongoDB before all operations. 9 | connect(); 10 | // Start Express Server 11 | startAPIServer(); 12 | -------------------------------------------------------------------------------- /server/request.ts: -------------------------------------------------------------------------------- 1 | import request from "request"; 2 | import fs from "fs"; 3 | import cliProgress from "cli-progress"; 4 | 5 | export const download = (url, filename) => { 6 | return new Promise((resolve, reject) => { 7 | const callback = (msg) => { 8 | reject(msg); 9 | }; 10 | const progressBar = new cliProgress.SingleBar( 11 | { 12 | format: "{bar} {percentage}% | ETA: {eta}s", 13 | }, 14 | cliProgress.Presets.shades_classic 15 | ); 16 | 17 | const file = fs.createWriteStream(filename); 18 | let receivedBytes = 0; 19 | 20 | request 21 | .get(url) 22 | .on("response", (response) => { 23 | if (response.statusCode !== 200) { 24 | return callback("Response status was " + response.statusCode); 25 | } 26 | 27 | const totalBytes = response.headers["content-length"]; 28 | progressBar.start(totalBytes, 0); 29 | }) 30 | .on("data", (chunk) => { 31 | receivedBytes += chunk.length; 32 | progressBar.update(receivedBytes); 33 | }) 34 | .pipe(file) 35 | .on("error", (err) => { 36 | fs.unlinkSync(filename); 37 | progressBar.stop(); 38 | return callback(err.message); 39 | }); 40 | 41 | file.on("finish", () => { 42 | progressBar.stop(); 43 | file.close(); 44 | resolve("finished"); 45 | }); 46 | 47 | file.on("error", (err) => { 48 | fs.unlinkSync(filename); 49 | progressBar.stop(); 50 | return callback(err.message); 51 | }); 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /server/utils.ts: -------------------------------------------------------------------------------- 1 | export const delay = async (ms: number) => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res(""); 5 | }, ms); 6 | }); 7 | }; 8 | 9 | export const getSecret = (secretName: string) => { 10 | const secret = process.env[secretName]; 11 | if (!secret) { 12 | console.log(`Secret ${secretName} is not defined in env`); 13 | throw new Error("Server error while reading secret!"); 14 | } 15 | return secret; 16 | }; 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "server", 4 | "outDir": "dist/", 5 | "sourceMap": true, 6 | "target": "es5", 7 | "module": "commonjs", 8 | "esModuleInterop": true, 9 | "moduleResolution": "node", 10 | "lib": ["es2015", "dom", "es2017"] 11 | }, 12 | "include": ["server/"] 13 | } 14 | -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /ui/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "5.16.5", 7 | "@testing-library/react": "13.4.0", 8 | "@testing-library/user-event": "13.5.0", 9 | "@types/jest": "27.5.2", 10 | "@types/node": "16.11.68", 11 | "@types/react": "18.0.21", 12 | "@types/react-dom": "18.0.6", 13 | "antd": "4.23.6", 14 | "axios": "1.1.3", 15 | "history": "5.3.0", 16 | "react": "^18.2.0", 17 | "react-device-detect": "2.2.2", 18 | "react-dom": "^18.2.0", 19 | "react-dropzone-uploader": "2.11.0", 20 | "react-router-dom": "6.4.2", 21 | "react-scripts": "5.0.1", 22 | "typescript": "4.8.4", 23 | "web-vitals": "2.1.4" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": [ 33 | "react-app", 34 | "react-app/jest" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/ui/public/favicon.ico -------------------------------------------------------------------------------- /ui/public/fileIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/ui/public/fileIcon.png -------------------------------------------------------------------------------- /ui/public/help/call-bot-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/ui/public/help/call-bot-api.png -------------------------------------------------------------------------------- /ui/public/help/create-bot-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/ui/public/help/create-bot-token.png -------------------------------------------------------------------------------- /ui/public/help/create-group-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/ui/public/help/create-group-1.png -------------------------------------------------------------------------------- /ui/public/help/create-group-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/ui/public/help/create-group-2.png -------------------------------------------------------------------------------- /ui/public/help/create-group-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/ui/public/help/create-group-3.png -------------------------------------------------------------------------------- /ui/public/help/create-group-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/ui/public/help/create-group-4.png -------------------------------------------------------------------------------- /ui/public/help/help.md: -------------------------------------------------------------------------------- 1 | ## Register New User 2 | 3 | ### 1. Get Telegram Bot Token 4 | 5 | Open BotFather on your telegram and type `/newbot` and give a name to your bot eg: `TelegramCloudStorage` also give your bot's name eg: `ebinsCloudStorageBot` 6 | 7 | - Create a bot 8 | ![alt text](./create-bot-token.png) 9 | Copy this `TOKEN` for step 2. 10 | 11 | ### 2. Get Telegram Group ID 12 | 13 | We have to create a Telegram Group and add our `Bot` to it. 14 | 15 | - Create Group 16 | ![alt text](./create-group-1.png) 17 | - Add our bot to it 18 | ![alt text](./create-group-2.png) 19 | - Give a group name 20 | ![alt text](./create-group-3.png) 21 | - Send a message `/test` to the group 22 | ![alt text](./create-group-4.png) 23 | - Now open this URL on browser by replacing `TOKEN` with yours: https://api.telegram.org/bot{TOKEN}/getUpdates 24 | - Now you have got the `Group ID` or Chat ID 25 | ![alt text](./call-bot-api.png) 26 | -------------------------------------------------------------------------------- /ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 25 | Telegram Cloud Storage 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ui/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /ui/public/ss/ss-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebinxavier/telegramCloudStorage/9c73fd10a9af1649c6a4325b512019ce2c41432f/ui/public/ss/ss-home.png -------------------------------------------------------------------------------- /ui/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Routes, Route, Navigate } from "react-router-dom"; 2 | import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom"; 3 | 4 | import Home from "./pages/home/home"; 5 | import Login from "./pages/login/login"; 6 | import Register from "./pages/login/register"; 7 | import { history, isLoggedIn } from "./services/common"; 8 | 9 | function App() { 10 | return ( 11 | 12 | 13 | }> 14 | }> 15 | }> 16 | 21 | ) : ( 22 | 23 | ) 24 | } 25 | > 26 | 27 | 28 | ); 29 | } 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /ui/src/components/components.css: -------------------------------------------------------------------------------- 1 | .clickable{ 2 | cursor: pointer; 3 | } 4 | 5 | .navigator-margin { 6 | margin: 16px 0; 7 | } 8 | 9 | .header-title{ 10 | font-size: 25px; 11 | color: white; 12 | font-weight:bold; 13 | float: left; 14 | } 15 | 16 | .logout-btn{ 17 | float: right; 18 | } -------------------------------------------------------------------------------- /ui/src/components/folder/file.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "./folder.css"; 3 | import { getDownloadURL } from "../../services/file"; 4 | import { Image, Skeleton } from "antd"; 5 | import { getFileSize, history, shortenFileName } from "../../services/common"; 6 | import { isMobile } from "react-device-detect"; 7 | import FileMask from "./fileMask"; 8 | import { useParams } from "react-router-dom"; 9 | 10 | interface FolderProps { 11 | fileName: string; 12 | onDownload: any; 13 | onDelete: any; 14 | thumbnail: string; 15 | file: any; 16 | } 17 | const File: React.FC = ({ 18 | fileName, 19 | onDownload, 20 | onDelete, 21 | thumbnail, 22 | file, 23 | }) => { 24 | const [icon, setIcon] = useState(""); 25 | const [loading, setLoading] = useState(true); 26 | const [openPreview, setOpenPreview] = useState(false); 27 | const [isImage, setIsImage] = useState(false); 28 | const [hdImage, setHdImage] = useState(""); 29 | const params = useParams(); 30 | 31 | const handlePreviewClick = () => { 32 | const hdFileId = file?.content?.file_id; 33 | if (!hdImage && hdFileId) { 34 | getDownloadURL(hdFileId).then((image) => { 35 | setHdImage(image.url); 36 | }); 37 | } 38 | setOpenPreview(true); 39 | }; 40 | 41 | useEffect(() => { 42 | if (file?.content?.mime_type?.includes("image")) { 43 | setIsImage(true); 44 | } 45 | if (thumbnail) { 46 | getDownloadURL(thumbnail) 47 | .then((image) => { 48 | setIcon(image.url); 49 | }) 50 | .finally(() => { 51 | setLoading(false); 52 | }); 53 | } else { 54 | setLoading(false); 55 | } 56 | }, [file, thumbnail]); 57 | 58 | useEffect(() => { 59 | setTimeout(() => { 60 | if (history.location.hash !== "#preview") { 61 | setOpenPreview(false); 62 | } 63 | }); 64 | }, [params]); 65 | 66 | useEffect(() => { 67 | // Adding navigation params for preview 68 | if (openPreview) { 69 | history.push( 70 | history.location.pathname + 71 | history.location.search + 72 | "#preview" 73 | ); 74 | } 75 | }, [openPreview]) 76 | 77 | 78 | return ( 79 | 80 |
81 | {loading ? ( 82 | 88 | ) : ( 89 | thumbnail 103 | ), 104 | visible: openPreview, 105 | onVisibleChange(value) { 106 | if (!value) { 107 | setOpenPreview(false); 108 | history.back(); 109 | } 110 | }, 111 | }} 112 | /> 113 | )} 114 |
115 |

{shortenFileName(fileName, "file", isMobile ? 12 : 20)}

116 |
117 | ); 118 | }; 119 | 120 | export default File; 121 | -------------------------------------------------------------------------------- /ui/src/components/folder/fileMask.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Modal } from "antd"; 2 | import { DownloadOutlined, EyeOutlined, DeleteOutlined } from "@ant-design/icons"; 3 | import { useState } from "react"; 4 | 5 | const btnWidth = 30; 6 | const FileMask = ({ onPreview, onDownload, fileSize, onDelete }: any) => { 7 | const [deleteModalOpen, setDeleteModalOpen] = useState(false); 8 | return ( 9 |
10 | {onPreview && ( 11 | <> 12 | 18 | 19 | )} 20 | 26 | 33 | 34 |
35 |
36 | {fileSize} 37 | setDeleteModalOpen(false)} 42 | destroyOnClose 43 | footer={[ 44 | , 47 | , 55 | ]} 56 | > 57 | Are you sure you want to delete this file ? 58 | 59 |
60 | ); 61 | }; 62 | 63 | export default FileMask; 64 | -------------------------------------------------------------------------------- /ui/src/components/folder/folder.css: -------------------------------------------------------------------------------- 1 | .folder{ 2 | display: inline-block; 3 | padding: 10px; 4 | border: 1px solid rgb(218 220 224); 5 | width: 200px; 6 | border-radius: 6px; 7 | margin: 10px 10px 10px 0; 8 | cursor: pointer; 9 | font-weight: 500; 10 | user-select: none; 11 | background: #9fc5ea; 12 | } 13 | .folder:hover{ 14 | background: #e7e7e7; 15 | } 16 | 17 | .folder p { 18 | text-overflow: ellipsis; 19 | white-space: nowrap; 20 | overflow: hidden; 21 | margin: 5px; 22 | display: inline-block; 23 | vertical-align: middle; 24 | } 25 | .folder span{ 26 | vertical-align: middle; 27 | } 28 | 29 | .folderIcon{ 30 | margin-right: 10px; 31 | font-size: 25px; 32 | vertical-align: sub; 33 | 34 | } 35 | 36 | .file{ 37 | display: inline-block; 38 | padding: 10px; 39 | border: 1px solid rgb(218 220 224); 40 | width: 230px; 41 | border-radius: 6px; 42 | margin: 10px 10px 10px 0; 43 | cursor: pointer; 44 | font-weight: 500; 45 | user-select: none; 46 | text-align: center; 47 | background: white; 48 | } 49 | 50 | .mask{ 51 | background-color: black; 52 | } 53 | 54 | .file img { 55 | object-fit: cover; 56 | width: 210px; 57 | } 58 | 59 | .file:hover{ 60 | background: #e7e7e7; 61 | } 62 | .file p { 63 | margin-bottom: 0; 64 | margin-top: 10px; 65 | text-overflow: ellipsis; 66 | white-space: nowrap; 67 | overflow: hidden; 68 | margin: 5px; 69 | } 70 | 71 | .fileIcon{ 72 | margin-right: 10px; 73 | font-size: 100px; 74 | font-weight: normal; 75 | vertical-align: sub; 76 | padding-top: 50px; 77 | padding-bottom: 50px; 78 | } 79 | 80 | .imageSkeleton{ 81 | padding: 23px 0; 82 | } 83 | 84 | .imageSkeleton div { 85 | width: 130px !important; 86 | height: 154px !important; 87 | } 88 | 89 | /* Mobile styles */ 90 | 91 | .file-mobile{ 92 | width: 150px; 93 | padding: 3px; 94 | } 95 | 96 | .file-mobile img { 97 | width: 140px; 98 | height: 120px; 99 | } 100 | 101 | .file-mobile div > .ant-image-mask { 102 | opacity: 1; 103 | } 104 | 105 | .folder-mobile{ 106 | width: 120px; 107 | padding: 3px; 108 | } 109 | 110 | .folder-mobile img { 111 | width: 114px; 112 | height: 120px; 113 | } 114 | 115 | .folder-mobile span { 116 | width: 114px; 117 | object-fit: cover; 118 | padding-top: 15px; 119 | } 120 | 121 | .imageSkeleton-mobile div { 122 | width: 100px !important; 123 | height: 90px !important; 124 | } -------------------------------------------------------------------------------- /ui/src/components/folder/folder.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { FolderOutlined } from "@ant-design/icons"; 3 | import { isMobile } from "react-device-detect"; 4 | import "./folder.css"; 5 | import { shortenFileName } from "../../services/common"; 6 | 7 | interface FolderProps { 8 | folderName: string; 9 | onClick: any; 10 | } 11 | const Folder: React.FC = ({ folderName, onClick }) => { 12 | return ( 13 | 18 | 19 | 20 | 21 |

{shortenFileName(folderName, "folder", isMobile ? 12 : 20)}

22 |
23 | ); 24 | }; 25 | 26 | export default Folder; 27 | -------------------------------------------------------------------------------- /ui/src/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Divider, Layout, Skeleton, Tooltip } from "antd"; 2 | import { Content, Footer, Header } from "antd/lib/layout/layout"; 3 | import React from "react"; 4 | import Navigator from "./navigator"; 5 | import { PoweroffOutlined } from "@ant-design/icons"; 6 | import { history } from "../services/common"; 7 | import { isMobile } from "react-device-detect"; 8 | 9 | const LayoutComponent: React.FC = ({ 10 | actions, 11 | children, 12 | path, 13 | loading, 14 | }) => { 15 | const handleLogout = () => { 16 | localStorage.removeItem("accessToken"); 17 | history.push("/login"); 18 | }; 19 | return ( 20 | 21 |
22 |
23 |
Telegram Storage
24 |
25 | 26 |
35 |
36 |
37 |
38 | 41 | 42 | {actions} 43 |
44 | {loading ? : children} 45 |
46 |
47 |
48 | 49 | Made with ❤️ by Epidemist 50 |
51 |
52 | ); 53 | }; 54 | 55 | export default LayoutComponent; 56 | -------------------------------------------------------------------------------- /ui/src/components/navigator.tsx: -------------------------------------------------------------------------------- 1 | import { HomeOutlined, FolderOutlined } from "@ant-design/icons"; 2 | import { Breadcrumb } from "antd"; 3 | import React from "react"; 4 | import { useSearchParams } from "react-router-dom"; 5 | import "./components.css"; 6 | 7 | interface NavigatorProps { 8 | path: string; 9 | } 10 | const Navigator: React.FC = ({ path }) => { 11 | const [, setSearchParams] = useSearchParams(); 12 | 13 | const pathList = path.slice(1).split("/"); 14 | const tail = pathList[pathList.length - 1]; 15 | const middleItems = pathList.slice(1, -1); 16 | 17 | const handleClick = (index: number) => { 18 | const completePath = "/" + pathList.slice(0, index + 1).join("/"); 19 | setSearchParams({ path: completePath }); 20 | }; 21 | 22 | return ( 23 | 24 | handleClick(0)}> 25 | 26 | Home 27 | 28 | {middleItems.map((item, index) => { 29 | return ( 30 | handleClick(index + 1)} 34 | > 35 | 36 | {item} 37 | 38 | ); 39 | })} 40 | {tail !== "root" && ( 41 | 42 | 43 | {tail} 44 | 45 | )} 46 | 47 | ); 48 | }; 49 | 50 | export default Navigator; 51 | -------------------------------------------------------------------------------- /ui/src/components/uploader/styles.css: -------------------------------------------------------------------------------- 1 | .dzu-dropzone { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | width: 100%; 6 | min-height: 50vh; 7 | overflow: scroll; 8 | margin: 0 auto; 9 | position: relative; 10 | box-sizing: border-box; 11 | transition: all .15s linear; 12 | border: 2px dashed #d9d9d9; 13 | border-radius: 10px; 14 | } 15 | 16 | .dzu-dropzoneActive { 17 | background-color: #DEEBFF; 18 | border-color: #2484FF; 19 | } 20 | 21 | .dzu-dropzoneDisabled { 22 | opacity: 0.5; 23 | } 24 | 25 | .dzu-dropzoneDisabled *:hover { 26 | cursor: unset; 27 | } 28 | 29 | .dzu-input { 30 | display: none; 31 | } 32 | 33 | .dzu-inputLabel { 34 | display: flex; 35 | justify-content: center; 36 | align-items: center; 37 | position: absolute; 38 | top: 0; 39 | bottom: 0; 40 | left: 0; 41 | right: 0; 42 | font-family: 'Helvetica', sans-serif; 43 | font-size: 20px; 44 | font-weight: 600; 45 | color: #2484FF; 46 | -moz-osx-font-smoothing: grayscale; 47 | -webkit-font-smoothing: antialiased; 48 | cursor: pointer; 49 | } 50 | 51 | .dzu-inputLabelWithFiles { 52 | display: flex; 53 | justify-content: center; 54 | align-items: center; 55 | align-self: flex-start; 56 | padding: 0 14px; 57 | min-height: 32px; 58 | background-color: #E6E6E6; 59 | color: #2484FF; 60 | border: none; 61 | font-family: 'Helvetica', sans-serif; 62 | border-radius: 4px; 63 | font-size: 14px; 64 | font-weight: 600; 65 | margin-top: 20px; 66 | margin-left: 3%; 67 | margin-bottom: 10px; 68 | -moz-osx-font-smoothing: grayscale; 69 | -webkit-font-smoothing: antialiased; 70 | cursor: pointer; 71 | } 72 | 73 | .dzu-previewContainer { 74 | padding: 40px 3%; 75 | display: flex; 76 | flex-direction: row; 77 | align-items: center; 78 | justify-content: space-between; 79 | position: relative; 80 | width: 100%; 81 | min-height: 60px; 82 | z-index: 1; 83 | border-bottom: 1px solid #ECECEC; 84 | box-sizing: border-box; 85 | } 86 | 87 | .dzu-previewStatusContainer { 88 | display: flex; 89 | align-items: center; 90 | } 91 | 92 | .dzu-previewFileName { 93 | font-family: 'Helvetica', sans-serif; 94 | font-size: 14px; 95 | font-weight: 400; 96 | color: #333333; 97 | width: 40px; 98 | } 99 | 100 | .dzu-previewImage { 101 | max-height: 40px; 102 | width: 30px; 103 | border-radius: 4px; 104 | object-fit: cover; 105 | } 106 | 107 | .dzu-previewButton { 108 | background-size: 14px 14px; 109 | background-position: center; 110 | background-repeat: no-repeat; 111 | width: 14px; 112 | height: 14px; 113 | cursor: pointer; 114 | opacity: 0.9; 115 | margin: 0 0 2px 10px; 116 | } 117 | 118 | .dzu-submitButtonContainer { 119 | margin: 24px 0; 120 | z-index: 1; 121 | } 122 | 123 | .dzu-submitButton { 124 | padding: 0 14px; 125 | min-height: 32px; 126 | background-color: #2484FF; 127 | border: none; 128 | border-radius: 4px; 129 | font-family: 'Helvetica', sans-serif; 130 | font-size: 14px; 131 | font-weight: 600; 132 | color: #FFF; 133 | -moz-osx-font-smoothing: grayscale; 134 | -webkit-font-smoothing: antialiased; 135 | cursor: pointer; 136 | } 137 | 138 | .dzu-submitButton:disabled { 139 | background-color: #E6E6E6; 140 | color: #333333; 141 | cursor: unset; 142 | } 143 | -------------------------------------------------------------------------------- /ui/src/components/uploader/uploader.tsx: -------------------------------------------------------------------------------- 1 | import "./styles.css"; 2 | import Dropzone from "react-dropzone-uploader"; 3 | import { isMobile } from "react-device-detect"; 4 | import { UPLOAD_FILE } from "../../services/endpoints"; 5 | import { getBaseURL } from "../../services/common"; 6 | 7 | interface UploaderProps { 8 | path: string; 9 | done: any; 10 | } 11 | 12 | const Uploader: React.FC = ({ path, done }) => { 13 | const getUploadParams = ({ file, meta }: any) => { 14 | const body = new FormData(); 15 | body.append("file", file); 16 | body.append("path", path); 17 | return { 18 | url: `${getBaseURL()}${UPLOAD_FILE}`, 19 | body, 20 | headers: { 21 | Authorization: `Bearer ${localStorage.getItem("accessToken")}`, 22 | }, 23 | }; 24 | }; 25 | 26 | const handleChangeStatus = ({ meta, file, xhr }: any, status: any) => { 27 | if (status === "done") { 28 | done(JSON.parse(xhr.response)); 29 | } 30 | }; 31 | 32 | return ( 33 | 38 | ); 39 | }; 40 | export default Uploader; 41 | -------------------------------------------------------------------------------- /ui/src/index.css: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.css'; 2 | @import url('https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500&display=swap'); 3 | 4 | body { 5 | margin: 0; 6 | font-family: 'Ubuntu', sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /ui/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | 7 | const root = ReactDOM.createRoot( 8 | document.getElementById("root") as HTMLElement 9 | ); 10 | root.render( 11 | // 12 | 13 | // 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /ui/src/pages/home/home.css: -------------------------------------------------------------------------------- 1 | /* tile uploaded pictures */ 2 | .upload-list-inline .ant-upload-list-item { 3 | float: left; 4 | width: 200px; 5 | margin-right: 8px; 6 | } 7 | 8 | .upload-list-inline [class*='-upload-list-rtl'] .ant-upload-list-item { 9 | float: right; 10 | } 11 | 12 | .header-btns > .ant-btn > span { 13 | padding-bottom: 18px; 14 | display: inline-block; 15 | font-size: 30px; 16 | } 17 | 18 | .center { 19 | text-align: center; 20 | } 21 | .header-btns{ 22 | text-align: center; 23 | } 24 | 25 | .header-btns button { 26 | height: 40px; 27 | } -------------------------------------------------------------------------------- /ui/src/pages/home/home.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from "react"; 2 | import { useParams, useSearchParams } from "react-router-dom"; 3 | 4 | import LayoutComponent from "../../components/layout"; 5 | import { listFolder } from "../../services/folder"; 6 | import "../../components/components.css"; 7 | import "./home.css"; 8 | import { Divider, Empty } from "antd"; 9 | import AddFolder from "./modals/addFolderModal"; 10 | 11 | import FolderComponent from "../../components/folder/folder"; 12 | import FileComponent from "../../components/folder/file"; 13 | import { deleteFile, getDownloadURL } from "../../services/file"; 14 | import UploadFile from "./modals/uploadFileModal"; 15 | import DeleteFolder from "./modals/deleteFolderModal"; 16 | import { isMobile } from "react-device-detect"; 17 | import { showErrorMessage } from "../../services/common"; 18 | interface File { 19 | fileName: string; 20 | content: any; 21 | _id: string; 22 | } 23 | interface Folder { 24 | folderName: string; 25 | folders: Folder[]; 26 | path: string; 27 | files: File[]; 28 | } 29 | 30 | const Home: React.FC = () => { 31 | const [searchParams, setSearchParams] = useSearchParams(); 32 | const [folderInfo, setFolderInfo] = useState(); 33 | const [folderLoading, setFolderLoading] = useState(false); 34 | const [previousPath, setPreviousPath] = useState(""); 35 | const params = useParams(); 36 | 37 | 38 | const getCurrentPath = useCallback( 39 | () => { 40 | return searchParams.get("path") || "/root"; 41 | }, 42 | [searchParams], 43 | ) 44 | 45 | const handleFolderClick = (path: string) => { 46 | setSearchParams({ path }); 47 | }; 48 | 49 | const handleDownload = async (file: File) => { 50 | const fileId = file.content.file_id; 51 | const response = await getDownloadURL(fileId); 52 | window.open(response.url, "_self"); 53 | }; 54 | 55 | const handleDelete = async (file: File) => { 56 | try { 57 | const path = getCurrentPath(); 58 | console.log(file, path); 59 | await deleteFile(path, file.content.file_id, file.content.message_id); 60 | if (folderInfo) { 61 | const files = [...folderInfo?.files]; 62 | files.splice(files?.findIndex(f => f === file), 1); 63 | setFolderInfo({ ...folderInfo, files }); 64 | } 65 | } catch (e) { 66 | showErrorMessage("An error occurred", "Error while deleting the file!") 67 | } 68 | }; 69 | 70 | 71 | const updateFolder = (path: string) => { 72 | setFolderLoading(true); 73 | listFolder(path) 74 | .then((data) => { 75 | setFolderInfo(data as any); 76 | }) 77 | .finally(() => { 78 | setFolderLoading(false); 79 | }); 80 | } 81 | 82 | useEffect(() => { 83 | const path = getCurrentPath(); 84 | if (previousPath !== path) { 85 | setPreviousPath(path); 86 | updateFolder(path); 87 | } 88 | }, [params, getCurrentPath, previousPath]); 89 | 90 | return ( 91 | 96 | 97 | 98 | 99 | 100 | } 101 | > 102 | {!folderInfo?.folders?.length && !folderInfo?.files?.length && ( 103 | 107 | )} 108 | {!!folderInfo?.folders?.length && } 109 |
110 | {folderInfo?.folders?.map((folder) => ( 111 | handleFolderClick(folder.path)} 114 | folderName={folder.folderName} 115 | /> 116 | ))} 117 |
118 | 119 | {!!folderInfo?.files?.length && } 120 |
121 | {folderInfo?.files?.map((file) => handleDownload(file)} 124 | onDelete={() => handleDelete(file)} 125 | fileName={file?.fileName} 126 | thumbnail={file?.content?.thumb?.file_id} 127 | file={file} 128 | /> 129 | )} 130 |
131 |
132 | ); 133 | }; 134 | 135 | export default Home; 136 | -------------------------------------------------------------------------------- /ui/src/pages/home/modals/addFolderModal.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Input, Modal } from "antd"; 2 | import React, { useState } from "react"; 3 | import { FolderAddOutlined } from "@ant-design/icons"; 4 | import { useSearchParams } from "react-router-dom"; 5 | import { makeFolder } from "../../../services/folder"; 6 | import { isMobile } from "react-device-detect"; 7 | 8 | interface AddFolderProps { 9 | updateList: any; 10 | } 11 | 12 | const AddFolder: React.FC = ({ updateList }) => { 13 | const [isModalOpen, setIsModalOpen] = useState(false); 14 | const [searchParams] = useSearchParams(); 15 | const [newFolderName, setNewFolderName] = useState(""); 16 | const [creating, setCreating] = useState(false); 17 | 18 | const showModal = () => { 19 | setIsModalOpen(true); 20 | }; 21 | 22 | const handleOk = async () => { 23 | console.log(newFolderName); 24 | const path = searchParams.get("path"); 25 | if (path) { 26 | try { 27 | setCreating(true); 28 | const response = await makeFolder(path, newFolderName); 29 | console.log(response); 30 | updateList(response); 31 | setIsModalOpen(false); 32 | } catch (e) { 33 | } finally { 34 | setCreating(false); 35 | } 36 | } 37 | }; 38 | 39 | const handleCancel = () => { 40 | setIsModalOpen(false); 41 | }; 42 | 43 | return ( 44 | <> 45 | 49 | 57 | Cancel 58 | , 59 | , 68 | ]} 69 | > 70 |

Location: '{searchParams.get("path")}'

71 | setNewFolderName(e.target.value)} 74 | /> 75 |
76 | 77 | ); 78 | }; 79 | 80 | export default AddFolder; 81 | -------------------------------------------------------------------------------- /ui/src/pages/home/modals/deleteFolderModal.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Input, Modal } from "antd"; 2 | import React, { useState } from "react"; 3 | import { DeleteOutlined } from "@ant-design/icons"; 4 | import { useSearchParams } from "react-router-dom"; 5 | import { deleteFolder } from "../../../services/folder"; 6 | import { isMobile } from "react-device-detect"; 7 | interface DeleteFolderProps { 8 | updateList: any; 9 | } 10 | 11 | const DeleteFolder: React.FC = ({ updateList }) => { 12 | const [isModalOpen, setIsModalOpen] = useState(false); 13 | const [searchParams, setSearchParams] = useSearchParams(); 14 | const [confirmationPath, setConfirmationPath] = useState(""); 15 | const [deleting, setDeleting] = useState(false); 16 | 17 | const showModal = () => { 18 | setIsModalOpen(true); 19 | }; 20 | 21 | const handleOk = async () => { 22 | const path = searchParams.get("path"); 23 | if (path) { 24 | try { 25 | setDeleting(true); 26 | const response = await deleteFolder(path); 27 | const parentPath = path.split("/").slice(0, -1).join("/"); 28 | console.log(response, parentPath); 29 | setSearchParams({ path: parentPath }); 30 | setIsModalOpen(false); 31 | setConfirmationPath(""); 32 | } catch (e) { 33 | } finally { 34 | setDeleting(false); 35 | } 36 | } 37 | }; 38 | 39 | const handleCancel = () => { 40 | setIsModalOpen(false); 41 | }; 42 | 43 | return ( 44 | <> 45 | 54 | 62 | Cancel 63 | , 64 | , 74 | ]} 75 | > 76 |

77 | Are you sure you want to delete this folder and its child folders ? 78 |

79 |

80 | Type '{searchParams.get("path")}' to confirm delete 81 |

82 | setConfirmationPath(e.target.value)} 85 | /> 86 |
87 | 88 | ); 89 | }; 90 | 91 | export default DeleteFolder; 92 | -------------------------------------------------------------------------------- /ui/src/pages/home/modals/uploadFileModal.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Modal } from "antd"; 2 | import React, { useState } from "react"; 3 | import { UploadOutlined } from "@ant-design/icons"; 4 | import { useSearchParams } from "react-router-dom"; 5 | import Uploader from "../../../components/uploader/uploader"; 6 | import { isMobile } from "react-device-detect"; 7 | 8 | interface UploadFileProps { 9 | updateList?: any; 10 | } 11 | 12 | const UploadFile: React.FC = ({ updateList }) => { 13 | const [isModalOpen, setIsModalOpen] = useState(false); 14 | const [searchParams] = useSearchParams(); 15 | 16 | const showModal = () => { 17 | setIsModalOpen(true); 18 | }; 19 | 20 | const handleCancel = () => { 21 | setIsModalOpen(false); 22 | }; 23 | 24 | return ( 25 | <> 26 | 30 | 38 | 42 | 43 | 44 | ); 45 | }; 46 | 47 | export default UploadFile; 48 | -------------------------------------------------------------------------------- /ui/src/pages/login/login.css: -------------------------------------------------------------------------------- 1 | .form-border{ 2 | margin: 0 20px; 3 | margin-top: calc(45vh - 180px); 4 | padding: 41px 50px; 5 | border: 1px solid #d9d9d9; 6 | border-radius: 15px; 7 | text-align: center; 8 | } 9 | 10 | .title { 11 | font-weight: bold; 12 | font-size: 25px; 13 | text-align: center; 14 | margin-bottom: 18px; 15 | } -------------------------------------------------------------------------------- /ui/src/pages/login/login.tsx: -------------------------------------------------------------------------------- 1 | import { LockOutlined, UserOutlined } from "@ant-design/icons"; 2 | import { Button, Col, Form, Input, Row } from "antd"; 3 | import React from "react"; 4 | import { Link } from "react-router-dom"; 5 | import { handleLogin } from "../../services/user"; 6 | import { isMobile } from "react-device-detect"; 7 | 8 | import "./login.css"; 9 | 10 | const Login: React.FC = () => { 11 | return ( 12 | 13 | 14 |
20 |
Telegram Storage
21 | 25 | } placeholder="Username" /> 26 | 27 | 31 | } 33 | type="password" 34 | placeholder="Password" 35 | /> 36 | 37 | 38 | 39 | 47 | 48 | Register 49 |
50 | 51 |
52 | ); 53 | }; 54 | 55 | export default Login; 56 | -------------------------------------------------------------------------------- /ui/src/pages/login/register.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | LockOutlined, 3 | UserOutlined, 4 | ClusterOutlined, 5 | TeamOutlined, 6 | } from "@ant-design/icons"; 7 | import { Button, Col, Form, Input, Row } from "antd"; 8 | import React from "react"; 9 | import { Link } from "react-router-dom"; 10 | import { handleRegistration } from "../../services/user"; 11 | import { isMobile } from "react-device-detect"; 12 | 13 | import "./login.css"; 14 | 15 | const Register: React.FC = () => { 16 | return ( 17 | 18 | 19 |
25 |
Telegram Storage
26 | 27 | 31 | } placeholder="Username" /> 32 | 33 | 34 | 38 | } 40 | type="password" 41 | placeholder="Password" 42 | /> 43 | 44 | 45 | 51 | } 53 | placeholder="Telegram Bot Token" 54 | /> 55 | 56 | 57 | 63 | } placeholder="Telegram Group ID" /> 64 | 65 | 66 | 67 | 75 | 76 | Login 77 |
78 | 85 | Help 86 | 87 |
88 | 89 |
90 | ); 91 | }; 92 | 93 | export default Register; 94 | -------------------------------------------------------------------------------- /ui/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /ui/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /ui/src/services/common.ts: -------------------------------------------------------------------------------- 1 | import { notification } from "antd"; 2 | import { message } from "antd"; 3 | import axios from "axios"; 4 | import { createBrowserHistory } from "history"; 5 | 6 | console.log("Env:", process.env.NODE_ENV); 7 | type RequestMethod = "GET" | "POST"; 8 | interface RequestData { 9 | body?: any; 10 | queryParams?: any; 11 | } 12 | 13 | let cachedBaseURL: string; 14 | 15 | const getBaseURLPromise = async () => { 16 | if (cachedBaseURL) { 17 | return cachedBaseURL; 18 | } 19 | const deployments = ["", "http://localhost:3000"]; 20 | 21 | return new Promise(async (resolve, reject) => { 22 | for (let URL of deployments) { 23 | try { 24 | await fetch(`${URL}/api/v1/healthy`); 25 | cachedBaseURL = URL; 26 | console.log("Server Found: ", URL); 27 | resolve(URL); 28 | break; 29 | } catch (e) { 30 | console.log(`${URL} not available!`, e); 31 | } 32 | } 33 | 34 | reject("No deployments found!"); 35 | }); 36 | }; 37 | 38 | export const getBaseURL = () => cachedBaseURL; 39 | 40 | export const makeRequest = async ( 41 | url: string, 42 | reqMethod: RequestMethod, 43 | reqData?: RequestData, 44 | onUploadProgress?: any, 45 | onDownloadProgress?: any 46 | ) => { 47 | try { 48 | await getBaseURLPromise(); 49 | const response = await axios({ 50 | headers: { 51 | Authorization: `bearer ${localStorage.getItem("accessToken")}`, 52 | }, 53 | baseURL: cachedBaseURL, 54 | url, 55 | method: reqMethod, 56 | data: reqData?.body, 57 | params: reqData?.queryParams, 58 | onUploadProgress, 59 | onDownloadProgress, 60 | }); 61 | return response; 62 | } catch (e: any) { 63 | if (e.response.status === 401) { 64 | history.push("/login"); 65 | } 66 | return {} as any; 67 | } 68 | }; 69 | 70 | export const showErrorMessage = (title: string, description: string) => { 71 | notification.error({ 72 | message: title, 73 | description, 74 | }); 75 | }; 76 | 77 | export const history = createBrowserHistory(); 78 | 79 | export const isLoggedIn = () => { 80 | return !!localStorage.getItem("accessToken"); 81 | }; 82 | 83 | export const shortenFileName = ( 84 | fileName: string, 85 | type: string = "file", 86 | length: number = 15 87 | ) => { 88 | const file = fileName.substring(0, length); 89 | if (type === "folder") 90 | return `${file}${fileName.length > length ? "..." : ""}`; 91 | try { 92 | const fileParts = fileName.split("."); 93 | const [extension] = fileParts.slice(-1); 94 | const fileNameWithoutExt = fileParts.slice(0, -1).join("."); 95 | return `${fileNameWithoutExt.substring(0, length)}.${extension}`; 96 | } catch (e) { 97 | return file; 98 | } 99 | }; 100 | 101 | export const getFileSize = (size: number) => { 102 | size = Number(size); 103 | let unit = "KB"; 104 | size = Math.ceil(size / 1000); 105 | if (size > 1000) { 106 | unit = "MB"; 107 | size /= 1000; 108 | return `${size.toFixed(1)} ${unit}`; 109 | } 110 | return `${size} ${unit}`; 111 | }; 112 | 113 | export const showSuccessMessage = (description: string) => { 114 | message.success(description); 115 | }; 116 | -------------------------------------------------------------------------------- /ui/src/services/endpoints.ts: -------------------------------------------------------------------------------- 1 | export const LOGIN = "/api/v1/user/login"; 2 | export const IS_LOGGED_IN = "/api/v1/user/isLoggedIn"; 3 | export const LIST_FOLDER = "/api/v1/folder/ls"; 4 | export const MAKE_FOLDER = "/api/v1/folder/mkdir"; 5 | export const DELETE_FOLDER = "/api/v1/folder/rmdir"; 6 | export const DOWNLOAD_FILE_URL = "/api/v1/file/download"; 7 | export const DELETE_FILE = "/api/v1/file/remove"; 8 | export const UPLOAD_FILE = "/api/v1/file/upload"; 9 | export const REGISTER = "/api/v1/user/register"; 10 | -------------------------------------------------------------------------------- /ui/src/services/file.ts: -------------------------------------------------------------------------------- 1 | import { makeRequest } from "./common"; 2 | import { DELETE_FILE, DOWNLOAD_FILE_URL } from "./endpoints"; 3 | 4 | export const getDownloadURL = async (fileId: string) => { 5 | try { 6 | const request = await makeRequest(DOWNLOAD_FILE_URL, "GET", { 7 | queryParams: { 8 | fileId, 9 | }, 10 | }); 11 | return request.data; 12 | } catch (e: any) { 13 | throw e; 14 | } 15 | }; 16 | 17 | export const deleteFile = async ( 18 | path: string, 19 | fileId: string, 20 | messageId: string 21 | ) => { 22 | try { 23 | const request = await makeRequest(DELETE_FILE, "POST", { 24 | body: { 25 | path, 26 | fileId, 27 | messageId, 28 | }, 29 | }); 30 | return request.data; 31 | } catch (e: any) { 32 | throw e; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /ui/src/services/folder.ts: -------------------------------------------------------------------------------- 1 | import { makeRequest, showErrorMessage } from "./common"; 2 | import { DELETE_FOLDER, LIST_FOLDER, MAKE_FOLDER } from "./endpoints"; 3 | 4 | export const listFolder = async (path: string) => { 5 | const request = await makeRequest(LIST_FOLDER, "POST", { 6 | body: { 7 | path, 8 | }, 9 | }); 10 | return request.data; 11 | }; 12 | 13 | export const makeFolder = async (path: string, folderName: string) => { 14 | try { 15 | const request = await makeRequest(MAKE_FOLDER, "POST", { 16 | body: { 17 | path, 18 | folderName, 19 | }, 20 | }); 21 | return request.data; 22 | } catch (e: any) { 23 | showErrorMessage("Make Folder Error", e?.response?.data?.message); 24 | throw e; 25 | } 26 | }; 27 | 28 | export const deleteFolder = async (path: string) => { 29 | try { 30 | const request = await makeRequest(DELETE_FOLDER, "POST", { 31 | body: { 32 | path, 33 | }, 34 | }); 35 | return request.data; 36 | } catch (e: any) { 37 | showErrorMessage("Delete Folder Error", e?.response?.data?.message); 38 | throw e; 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /ui/src/services/user.ts: -------------------------------------------------------------------------------- 1 | import { history, makeRequest, showErrorMessage, showSuccessMessage } from "./common"; 2 | import { IS_LOGGED_IN, LOGIN, REGISTER } from "./endpoints"; 3 | 4 | export const handleLogin = async (values: any) => { 5 | try { 6 | const loginRequest = await makeRequest(LOGIN, "POST", { 7 | body: { 8 | username: values.username, 9 | password: values.password, 10 | }, 11 | }); 12 | const { data } = loginRequest; 13 | localStorage.setItem("accessToken", data.token); 14 | history.push("/home?path=%2Froot"); 15 | } catch (e) { 16 | showErrorMessage("Authentication Error", "Invalid username or password"); 17 | } 18 | }; 19 | 20 | export const isLoggedIn = async () => { 21 | try { 22 | const loginStatus = await makeRequest(IS_LOGGED_IN, "GET"); 23 | return loginStatus; 24 | } catch (e) { 25 | console.log("Unauthorized user!"); 26 | return false; 27 | } 28 | }; 29 | 30 | export const handleRegistration = async (values: any) => { 31 | try { 32 | const registrationResponse = await makeRequest(REGISTER, "POST", { 33 | body: { 34 | username: values.username, 35 | password: values.password, 36 | botToken: values.botToken, 37 | chatId : values.chatId 38 | }, 39 | }); 40 | const { data } = registrationResponse; 41 | showSuccessMessage("User : "+ data.username+" registered successfully.. Continue Login"); 42 | history.push("/login"); 43 | } catch (e) { 44 | showErrorMessage("Authentication Error", "Couldn't register"+values.username); 45 | } 46 | }; 47 | 48 | -------------------------------------------------------------------------------- /ui/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------