diff --git a/.gitignore b/.gitignore index 8fdb9ad..6f848ac 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,7 @@ minio_data/ /dumpster-server keygen steamfree + +# Tauri Android keystore (contains signing passwords) +web/src-tauri/gen/android/keystore.properties + diff --git a/Makefile b/Makefile index a56c97a..64576f6 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,16 @@ build-tui: build-tauri: cd web && NODE_ENV=development NO_STRIP=true npx tauri build --bundles appimage,deb,rpm +# Build signed Android APK +build-android: + cd web && ANDROID_HOME=$(HOME)/Android/Sdk NDK_HOME=$(HOME)/Android/Sdk/ndk/27.2.12479018 JAVA_HOME=/usr/lib/jvm/java-17-openjdk npx tauri android build --apk + @echo "APK: web/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk" + +# Build signed Android AAB (Google Play bundle) +build-android-aab: + cd web && ANDROID_HOME=$(HOME)/Android/Sdk NDK_HOME=$(HOME)/Android/Sdk/ndk/27.2.12479018 JAVA_HOME=/usr/lib/jvm/java-17-openjdk npx tauri android build --aab + @echo "AAB: web/src-tauri/gen/android/app/build/outputs/bundle/universalRelease/app-universal-release.aab" + # Generate Swagger docs docs: ~/go/bin/swag init -g cmd/server/main.go -o docs diff --git a/_hashgen.go b/_hashgen.go new file mode 100644 index 0000000..73d250c --- /dev/null +++ b/_hashgen.go @@ -0,0 +1,17 @@ +//go:build ignore +// +build ignore + +package main + +import ( + "fmt" + "git.dustin.coffee/hobokenchicken/dumpsterChat/internal/auth" +) + +func main() { + hash, err := auth.HashPassword("password") + if err != nil { + panic(err) + } + fmt.Print(hash) +} diff --git a/cmd/server/main.go b/cmd/server/main.go index cd883f5..b2968ca 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -428,6 +428,13 @@ func main() { )) }) + // Privacy policy page (served before SPA catch-all) + r.HandleFunc("/privacy", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("Cache-Control", "public, max-age=3600") + w.Write([]byte(privacyPage)) + }) + // Static file serving for production (SPA) staticDir := "web/dist" if _, err := os.Stat(staticDir); err == nil { @@ -450,3 +457,104 @@ func main() { os.Exit(1) } } + +const privacyPage = ` + + + + +Privacy Policy — dumpsterChat + + + +
+

Privacy Policy

+

dumpsterChat — Last updated: July 17, 2026

+ +

Overview

+

dumpsterChat is a self-hosted messaging platform. This privacy policy describes how your data is handled when you use the app. Because dumpsterChat is self-hosted, your data is stored on the server instance you connect to, which is operated by the server owner — not by us.

+ +

Data We Collect

+

When you use dumpsterChat, the following data is stored on the server:

+ + +

How We Use Your Data

+

Your data is used solely to operate the chat platform:

+ + +

No Third-Party Analytics

+

dumpsterChat does not include any analytics SDKs, tracking pixels, or telemetry. No usage data is sent to us or to any third party for advertising, profiling, or analytics purposes.

+ +

Third-Party Integrations

+

If enabled by the server owner, optional integrations may be used:

+ +

These integrations are optional and controlled entirely by the server owner.

+ +

Data Retention

+

Data is retained for as long as the server owner maintains the database. You can delete your messages or account at any time through the app. Server owners may also set message retention limits. Uploaded files persist until explicitly removed.

+ +

Your Rights

+

Depending on your jurisdiction, you may have the right to:

+ +

To exercise these rights, contact the operator of the dumpsterChat instance you use, or use the account management tools available within the app.

+ +

Account Deletion Requests

+

To request deletion of your account and all associated data:

+ +

We will process your request within 30 days. Deletion removes your account, messages, uploaded files, and all personal data from the server.

+ +

Security

+

We take reasonable measures to protect your data:

+ + +

Children's Privacy

+

dumpsterChat is not directed at children under 13. We do not knowingly collect personal information from children.

+ +

Changes to This Policy

+

We may update this privacy policy from time to time. Changes will be posted at this URL. Continued use of the app after changes constitutes acceptance of the updated policy.

+ +

Contact

+

If you have questions about this privacy policy, contact the operator of the dumpsterChat instance you use, or open an issue on our project repository.

+ +
This privacy policy applies to the dumpsterChat mobile app and web app. The specific data practices of each instance may vary based on the server operator's configuration.
+
+ +` diff --git a/internal/auth/handlers.go b/internal/auth/handlers.go index cc2c314..973dbd8 100644 --- a/internal/auth/handlers.go +++ b/internal/auth/handlers.go @@ -50,6 +50,7 @@ func (h *Handler) RegisterProtectedRoutes(r chi.Router) { r.Get("/auth/me", h.Me) r.Patch("/auth/me", h.UpdateProfile) r.Put("/auth/me/password", h.ChangePassword) + r.Delete("/auth/me", h.DeleteAccount) r.Get("/users/{userID}/profile", h.GetPublicProfile) r.Get("/users/me/blocks", h.ListBlocks) r.Post("/users/me/blocks", h.BlockUser) @@ -662,3 +663,32 @@ func (h *Handler) ChangePassword(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"message": "password updated"}) } + +// DeleteAccount deletes the authenticated user and all associated data. +// ponytail: cascading FK handles all DB cleanup — push subscriptions cleaned manually. +func (h *Handler) DeleteAccount(w http.ResponseWriter, r *http.Request) { + userID, ok := middleware.UserIDFromContext(r.Context()) + if !ok { + http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized) + return + } + + // Clean up push subscriptions + h.db.ExecContext(r.Context(), `DELETE FROM push_subscriptions WHERE user_id = $1`, userID) + + // ON DELETE CASCADE handles everything else: sessions, tokens, messages, + // reactions, memberships, invites, bots, etc. + result, err := h.db.ExecContext(r.Context(), `DELETE FROM users WHERE id = $1`, userID) + if err != nil { + http.Error(w, `{"error":"failed to delete account"}`, http.StatusInternalServerError) + return + } + rows, _ := result.RowsAffected() + if rows == 0 { + http.Error(w, `{"error":"user not found"}`, http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"message": "account deleted"}) +} diff --git a/web/src-tauri/gen/android/.editorconfig b/web/src-tauri/gen/android/.editorconfig new file mode 100644 index 0000000..ebe51d3 --- /dev/null +++ b/web/src-tauri/gen/android/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file diff --git a/web/src-tauri/gen/android/.gitignore b/web/src-tauri/gen/android/.gitignore new file mode 100644 index 0000000..1c636c3 --- /dev/null +++ b/web/src-tauri/gen/android/.gitignore @@ -0,0 +1,20 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +build +/captures +.externalNativeBuild +.cxx +local.properties +key.properties +keystore.properties + +/.tauri +/tauri.settings.gradle \ No newline at end of file diff --git a/web/src-tauri/gen/android/app/.gitignore b/web/src-tauri/gen/android/app/.gitignore new file mode 100644 index 0000000..6c4d56b --- /dev/null +++ b/web/src-tauri/gen/android/app/.gitignore @@ -0,0 +1,6 @@ +/src/main/**/generated +/src/main/jniLibs/**/*.so +/src/main/assets/tauri.conf.json +/tauri.build.gradle.kts +/proguard-tauri.pro +/tauri.properties \ No newline at end of file diff --git a/web/src-tauri/gen/android/app/build.gradle.kts b/web/src-tauri/gen/android/app/build.gradle.kts new file mode 100644 index 0000000..dc5608c --- /dev/null +++ b/web/src-tauri/gen/android/app/build.gradle.kts @@ -0,0 +1,90 @@ +import java.util.Properties + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("rust") +} + +val tauriProperties = Properties().apply { + val propFile = file("tauri.properties") + if (propFile.exists()) { + propFile.inputStream().use { load(it) } + } +} + +val keystorePropertiesFile = rootProject.file("keystore.properties") +val keystoreProperties = Properties() +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(keystorePropertiesFile.inputStream()) +} + +android { + compileSdk = 36 + namespace = "coffee.dustin.dumpster" + defaultConfig { + manifestPlaceholders["usesCleartextTraffic"] = "false" + applicationId = "coffee.dustin.dumpster" + minSdk = 24 + targetSdk = 36 + versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() + versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") + } + signingConfigs { + if (keystorePropertiesFile.exists()) { + create("release") { + storeFile = file(keystoreProperties["storeFile"] as String) + storePassword = keystoreProperties["storePassword"] as String + keyAlias = keystoreProperties["keyAlias"] as String + keyPassword = keystoreProperties["keyPassword"] as String + } + } + } + buildTypes { + getByName("debug") { + manifestPlaceholders["usesCleartextTraffic"] = "true" + isDebuggable = true + isJniDebuggable = true + isMinifyEnabled = false + packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so") + jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so") + jniLibs.keepDebugSymbols.add("*/x86/*.so") + jniLibs.keepDebugSymbols.add("*/x86_64/*.so") + } + } + getByName("release") { + if (keystorePropertiesFile.exists()) { + signingConfig = signingConfigs.getByName("release") + } + isMinifyEnabled = true + proguardFiles( + *fileTree(".") { include("**/*.pro") } + .plus(getDefaultProguardFile("proguard-android-optimize.txt")) + .toList().toTypedArray() + ) + } + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + } +} + +rust { + rootDirRel = "../../../" +} + +dependencies { + implementation("androidx.webkit:webkit:1.14.0") + implementation("androidx.appcompat:appcompat:1.7.1") + implementation("androidx.activity:activity-ktx:1.10.1") + implementation("com.google.android.material:material:1.12.0") + implementation("androidx.lifecycle:lifecycle-process:2.10.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.4") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") +} + +apply(from = "tauri.build.gradle.kts") \ No newline at end of file diff --git a/web/src-tauri/gen/android/app/proguard-rules.pro b/web/src-tauri/gen/android/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/web/src-tauri/gen/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/web/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/web/src-tauri/gen/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ea86fc6 --- /dev/null +++ b/web/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src-tauri/gen/android/app/src/main/java/coffee/dustin/dumpster/MainActivity.kt b/web/src-tauri/gen/android/app/src/main/java/coffee/dustin/dumpster/MainActivity.kt new file mode 100644 index 0000000..3546796 --- /dev/null +++ b/web/src-tauri/gen/android/app/src/main/java/coffee/dustin/dumpster/MainActivity.kt @@ -0,0 +1,11 @@ +package coffee.dustin.dumpster + +import android.os.Bundle +import androidx.activity.enableEdgeToEdge + +class MainActivity : TauriActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + } +} diff --git a/web/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/web/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/web/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/web/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml b/web/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/web/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml b/web/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..4fc2444 --- /dev/null +++ b/web/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..28f1aa1 Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..85d0c88 Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..28f1aa1 Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..73e48db Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..13dd214 Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..73e48db Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..1d98044 Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a888b33 Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..1d98044 Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..0818324 Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a2a838e Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..0818324 Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b18bceb Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3f8a57f Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b18bceb Binary files /dev/null and b/web/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/web/src-tauri/gen/android/app/src/main/res/values-night/themes.xml b/web/src-tauri/gen/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..f49d1a6 --- /dev/null +++ b/web/src-tauri/gen/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/web/src-tauri/gen/android/app/src/main/res/values/colors.xml b/web/src-tauri/gen/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/web/src-tauri/gen/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/web/src-tauri/gen/android/app/src/main/res/values/strings.xml b/web/src-tauri/gen/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..87ce59a --- /dev/null +++ b/web/src-tauri/gen/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + "dumpsterChat" + "dumpsterChat" + \ No newline at end of file diff --git a/web/src-tauri/gen/android/app/src/main/res/values/themes.xml b/web/src-tauri/gen/android/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..f49d1a6 --- /dev/null +++ b/web/src-tauri/gen/android/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/web/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml b/web/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..782d63b --- /dev/null +++ b/web/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/web/src-tauri/gen/android/build.gradle.kts b/web/src-tauri/gen/android/build.gradle.kts new file mode 100644 index 0000000..607240b --- /dev/null +++ b/web/src-tauri/gen/android/build.gradle.kts @@ -0,0 +1,22 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.11.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +tasks.register("clean").configure { + delete("build") +} + diff --git a/web/src-tauri/gen/android/buildSrc/build.gradle.kts b/web/src-tauri/gen/android/buildSrc/build.gradle.kts new file mode 100644 index 0000000..5c55bba --- /dev/null +++ b/web/src-tauri/gen/android/buildSrc/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + `kotlin-dsl` +} + +gradlePlugin { + plugins { + create("pluginsForCoolKids") { + id = "rust" + implementationClass = "RustPlugin" + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + compileOnly(gradleApi()) + implementation("com.android.tools.build:gradle:8.11.0") +} + diff --git a/web/src-tauri/gen/android/buildSrc/src/main/java/coffee/dustin/dumpster/kotlin/BuildTask.kt b/web/src-tauri/gen/android/buildSrc/src/main/java/coffee/dustin/dumpster/kotlin/BuildTask.kt new file mode 100644 index 0000000..a3de125 --- /dev/null +++ b/web/src-tauri/gen/android/buildSrc/src/main/java/coffee/dustin/dumpster/kotlin/BuildTask.kt @@ -0,0 +1,68 @@ +import java.io.File +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.logging.LogLevel +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction + +open class BuildTask : DefaultTask() { + @Input + var rootDirRel: String? = null + @Input + var target: String? = null + @Input + var release: Boolean? = null + + @TaskAction + fun assemble() { + val executable = """npm"""; + try { + runTauriCli(executable) + } catch (e: Exception) { + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + // Try different Windows-specific extensions + val fallbacks = listOf( + "$executable.exe", + "$executable.cmd", + "$executable.bat", + ) + + var lastException: Exception = e + for (fallback in fallbacks) { + try { + runTauriCli(fallback) + return + } catch (fallbackException: Exception) { + lastException = fallbackException + } + } + throw lastException + } else { + throw e; + } + } + } + + fun runTauriCli(executable: String) { + val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") + val target = target ?: throw GradleException("target cannot be null") + val release = release ?: throw GradleException("release cannot be null") + val args = listOf("run", "--", "tauri", "android", "android-studio-script"); + + project.exec { + workingDir(File(project.projectDir, rootDirRel)) + executable(executable) + args(args) + if (project.logger.isEnabled(LogLevel.DEBUG)) { + args("-vv") + } else if (project.logger.isEnabled(LogLevel.INFO)) { + args("-v") + } + if (release) { + args("--release") + } + args(listOf("--target", target)) + }.assertNormalExitValue() + } +} \ No newline at end of file diff --git a/web/src-tauri/gen/android/buildSrc/src/main/java/coffee/dustin/dumpster/kotlin/RustPlugin.kt b/web/src-tauri/gen/android/buildSrc/src/main/java/coffee/dustin/dumpster/kotlin/RustPlugin.kt new file mode 100644 index 0000000..4aa7fca --- /dev/null +++ b/web/src-tauri/gen/android/buildSrc/src/main/java/coffee/dustin/dumpster/kotlin/RustPlugin.kt @@ -0,0 +1,85 @@ +import com.android.build.api.dsl.ApplicationExtension +import org.gradle.api.DefaultTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.get + +const val TASK_GROUP = "rust" + +open class Config { + lateinit var rootDirRel: String +} + +open class RustPlugin : Plugin { + private lateinit var config: Config + + override fun apply(project: Project) = with(project) { + config = extensions.create("rust", Config::class.java) + + val defaultAbiList = listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64"); + val abiList = (findProperty("abiList") as? String)?.split(',') ?: defaultAbiList + + val defaultArchList = listOf("arm64", "arm", "x86", "x86_64"); + val archList = (findProperty("archList") as? String)?.split(',') ?: defaultArchList + + val targetsList = (findProperty("targetList") as? String)?.split(',') ?: listOf("aarch64", "armv7", "i686", "x86_64") + + extensions.configure { + @Suppress("UnstableApiUsage") + flavorDimensions.add("abi") + productFlavors { + create("universal") { + dimension = "abi" + ndk { + abiFilters += abiList + } + } + defaultArchList.forEachIndexed { index, arch -> + create(arch) { + dimension = "abi" + ndk { + abiFilters.add(defaultAbiList[index]) + } + } + } + } + } + + afterEvaluate { + for (profile in listOf("debug", "release")) { + val profileCapitalized = profile.replaceFirstChar { it.uppercase() } + val buildTask = tasks.maybeCreate( + "rustBuildUniversal$profileCapitalized", + DefaultTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for all targets" + } + + tasks["mergeUniversal${profileCapitalized}JniLibFolders"].dependsOn(buildTask) + + for (targetPair in targetsList.withIndex()) { + val targetName = targetPair.value + val targetArch = archList[targetPair.index] + val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() } + val targetBuildTask = project.tasks.maybeCreate( + "rustBuild$targetArchCapitalized$profileCapitalized", + BuildTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for $targetArch" + rootDirRel = config.rootDirRel + target = targetName + release = profile == "release" + } + + buildTask.dependsOn(targetBuildTask) + tasks["merge$targetArchCapitalized${profileCapitalized}JniLibFolders"].dependsOn( + targetBuildTask + ) + } + } + } + } +} \ No newline at end of file diff --git a/web/src-tauri/gen/android/gradle.properties b/web/src-tauri/gen/android/gradle.properties new file mode 100644 index 0000000..2a7ec69 --- /dev/null +++ b/web/src-tauri/gen/android/gradle.properties @@ -0,0 +1,24 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/web/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar b/web/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/web/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/web/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/web/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c5f9a53 --- /dev/null +++ b/web/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue May 10 19:22:52 CST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/web/src-tauri/gen/android/gradlew b/web/src-tauri/gen/android/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/web/src-tauri/gen/android/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/web/src-tauri/gen/android/gradlew.bat b/web/src-tauri/gen/android/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/web/src-tauri/gen/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/web/src-tauri/gen/android/native-debug-symbols.zip b/web/src-tauri/gen/android/native-debug-symbols.zip new file mode 100644 index 0000000..361fd8f Binary files /dev/null and b/web/src-tauri/gen/android/native-debug-symbols.zip differ diff --git a/web/src-tauri/gen/android/settings.gradle b/web/src-tauri/gen/android/settings.gradle new file mode 100644 index 0000000..3939116 --- /dev/null +++ b/web/src-tauri/gen/android/settings.gradle @@ -0,0 +1,3 @@ +include ':app' + +apply from: 'tauri.settings.gradle' diff --git a/web/src-tauri/tauri.conf.json b/web/src-tauri/tauri.conf.json index a6f3192..cc8dbe5 100644 --- a/web/src-tauri/tauri.conf.json +++ b/web/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "dumpsterChat", "version": "0.2.6", - "identifier": "coffee.dustin.dumpsterchat", + "identifier": "coffee.dustin.dumpster", "build": { "frontendDist": "../dist", "devUrl": "http://localhost:5173", diff --git a/web/src/components/UserSettings.tsx b/web/src/components/UserSettings.tsx index cd1b304..079d42c 100644 --- a/web/src/components/UserSettings.tsx +++ b/web/src/components/UserSettings.tsx @@ -5,7 +5,7 @@ import { usePushStore } from '../stores/push.ts'; import { ThemeToggle } from './ThemeToggle.tsx'; export function UserSettings() { - const { user, updateProfile, changePassword, isLoading, error, clearError } = useAuthStore(); + const { user, updateProfile, changePassword, deleteAccount, isLoading, error, clearError } = useAuthStore(); const { isSupported, isSubscribed, subscribe, unsubscribe, checkSubscription } = usePushStore(); const navigate = useNavigate(); @@ -440,6 +440,31 @@ export function UserSettings() { + {/* Account Deletion */} +
+
+ + [DELETE ACCOUNT] + +
+

+ This action is permanent and cannot be undone. All your messages, servers you own, and uploaded files will be deleted. +

+ +
+
+
+ {/* Footer info */}

diff --git a/web/src/stores/auth.ts b/web/src/stores/auth.ts index 43d1dbf..933337d 100644 --- a/web/src/stores/auth.ts +++ b/web/src/stores/auth.ts @@ -69,6 +69,7 @@ interface AuthState { blockUser: (userId: string) => Promise; unblockUser: (userId: string) => Promise; clearError: () => void; + deleteAccount: () => Promise; } // ponytail: auto-subscribe to push notifications on auth @@ -249,4 +250,8 @@ export const useAuthStore = create((set) => ({ unblockUser: async (userId) => { await api.delete(`/users/me/blocks/${userId}`); }, + + deleteAccount: async () => { + await api.delete("/auth/me"); + }, }));